Skip to content

Commit

Permalink
Added StopListen and ListenOnce to the MessageManager
Browse files Browse the repository at this point in the history
This allows users to discontinue listening to certain events in cases
where a system only needs to react to a certain event for a limited
amount of times.
Existing tests have been slightly altered, and new tests added
  • Loading branch information
MrTrustworthy committed Oct 8, 2018
1 parent be39957 commit 9a913f1
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 20 deletions.
62 changes: 55 additions & 7 deletions message.go
@@ -1,23 +1,43 @@
package engo

import "sync"
import (
"errors"
"sync"
)

//A MessageHandler is used to dispatch a message to the subscribed handler.
type MessageHandler func(msg Message)

// in order to track handlers, each handler will get a unique ID
type MessageHandlerId uint64

var currentHandlerId MessageHandlerId

func init() {
currentHandlerId = 0
}
func getNewHandlerId() MessageHandlerId {
currentHandlerId++
return currentHandlerId
}

type HandlerIDPair struct {
MessageHandlerId
MessageHandler
}

// A Message is used to send messages within the MessageManager
type Message interface {
Type() string
}

// MessageManager manages messages and subscribed handlers
type MessageManager struct {

// this mutex will prevent race
// conditions on listeners and
// sync its state across the game
sync.RWMutex
listeners map[string][]MessageHandler
listeners map[string][]HandlerIDPair
}

// Dispatch sends a message to all subscribed handlers of the message's type
Expand All @@ -29,19 +49,47 @@ func (mm *MessageManager) Dispatch(message Message) {
mm.RLock()
defer mm.RUnlock()
for _, handler := range handlers {
handler(message)
handler.MessageHandler(message)
}

}

// Listen subscribes to the specified message type and calls the specified handler when fired
func (mm *MessageManager) Listen(messageType string, handler MessageHandler) {
func (mm *MessageManager) Listen(messageType string, handler MessageHandler) MessageHandlerId {
mm.Lock()
defer mm.Unlock()
if mm.listeners == nil {
mm.listeners = make(map[string][]MessageHandler)
mm.listeners = make(map[string][]HandlerIDPair)
}
handlerID := getNewHandlerId()
newHandlerIdPair := HandlerIDPair{MessageHandlerId: handlerID, MessageHandler: handler}
mm.listeners[messageType] = append(mm.listeners[messageType], newHandlerIdPair)
return handlerID
}

// StopListen removes a previously added handler from the listener queue
func (mm *MessageManager) StopListen(messageType string, handlerId MessageHandlerId) error {
// first, we need to find the handler to remove or return an error if that's not possible
indexOfHandler := -1
for i, activeHandler := range mm.listeners[messageType] {
if activeHandler.MessageHandlerId == handlerId {
indexOfHandler = i
break
}
}
mm.listeners[messageType] = append(mm.listeners[messageType], handler)
if indexOfHandler == -1 {
return errors.New("Trying to remove handler that does not exist")
}
mm.listeners[messageType] = append(mm.listeners[messageType][:indexOfHandler], mm.listeners[messageType][indexOfHandler+1:]...)
return nil
}

func (mm *MessageManager) ListenOnce(messageType string, handler MessageHandler) {
handlerId := MessageHandlerId(0)
handlerId = mm.Listen(messageType, func(msg Message) {
handler(msg)
mm.StopListen(messageType, handlerId)
})
}

// WindowResizeMessage is a message that's being dispatched whenever the game window is being resized by the gamer
Expand Down
81 changes: 69 additions & 12 deletions message_test.go
Expand Up @@ -2,25 +2,82 @@ package engo

import "testing"

type testMessage struct {
success bool
type testMessageCounter struct {
counter int
}

func (testMessage) Type() string {
return "testMessage"
func (testMessageCounter) Type() string {
return "testMessageCounter"
}

func TestMessages(t *testing.T) {
func TestMessageCounterSimple(t *testing.T) {
mailbox := &MessageManager{}
msg := testMessage{}
mailbox.Listen("testMessage", func(message Message) {
m, ok := message.(*testMessage)
if ok {
m.success = true
msg := testMessageCounter{}
mailbox.Listen("testMessageCounter", func(message Message) {
m, ok := message.(*testMessageCounter)
if !ok {
t.Error("Message should be of type testMessageCounter")
}
m.counter++
})
mailbox.Dispatch(&msg)
if !msg.success {
t.Error("huh")
if msg.counter != 1 {
t.Error("Message should have been received 1 times by now")
}
mailbox.Dispatch(&msg)
if msg.counter != 2 {
t.Error("Message should have been received 2 times by now")
}
}

func TestMessageCounterWithRemoval(t *testing.T) {
mailbox := &MessageManager{}
msg := testMessageCounter{}
handlerId := mailbox.Listen("testMessageCounter", func(message Message) {
m, ok := message.(*testMessageCounter)
if !ok {
t.Error("Message should be of type testMessageCounter")
}
m.counter++
})
mailbox.Dispatch(&msg)
if msg.counter != 1 {
t.Error("Message should have been received 1 times by now")
}

mailbox.StopListen("testMessageCounter", handlerId)

mailbox.Dispatch(&msg)
if msg.counter != 1 {
t.Error("Message should have been received exactly 1 times since its handler was removed from listeners")
}
}

func TestRemovalOfNonexistentHandler(t *testing.T) {
mailbox := &MessageManager{}
err := mailbox.StopListen("testMessageCounter", MessageHandlerId(42))
if err == nil {
t.Error("StopListen should return an error in case the handler to remove doesn't exist")
}
}

func TestMessageListenOnce(t *testing.T) {
mailbox := &MessageManager{}
msg := testMessageCounter{}
mailbox.ListenOnce("testMessageCounter", func(message Message) {
m, ok := message.(*testMessageCounter)
if !ok {
t.Error("Message should be of type testMessageCounter")
}
m.counter++
})
mailbox.Dispatch(&msg)
if msg.counter != 1 {
t.Error("Message should have been received 1 times by now")
}

mailbox.Dispatch(&msg)
if msg.counter != 1 {
t.Error("Message should have been received exactly 1 times since its been added by ListenOnce()")
}
}
2 changes: 1 addition & 1 deletion scene.go
Expand Up @@ -107,7 +107,7 @@ func SetScene(s Scene, forceNewWorld bool) {
if doSetup {
s.Preload()

wrapper.mailbox.listeners = make(map[string][]MessageHandler)
wrapper.mailbox.listeners = make(map[string][]HandlerIDPair)

s.Setup(wrapper.update)
} else {
Expand Down

0 comments on commit 9a913f1

Please sign in to comment.