Handler updates, no backwards incompatible API changes.

AddHandler now returns a func that can remove the handler.
The handlers map is now guarded by its own mutex.
Moved eventMap to events.go for readability.
Improved documentation.
This commit is contained in:
Chris Rhodes 2016-02-19 22:28:21 -08:00
parent f832d3da4a
commit 79247272ff
5 changed files with 137 additions and 60 deletions

View file

@ -122,11 +122,11 @@ func New(args ...interface{}) (s *Session, err error) {
return return
} }
// AddHandler allows you to add an event handler that will be fired anytime // validateHandler takes an event handler func, and returns the type of event.
// the given event is triggered. // eg.
func (s *Session) AddHandler(handler interface{}) { // Session.validateHandler(func (s *discordgo.Session, m *discordgo.MessageCreate))
s.initialize() // will return the reflect.Type of *discordgo.MessageCreate
func (s *Session) validateHandler(handler interface{}) reflect.Type {
handlerType := reflect.TypeOf(handler) handlerType := reflect.TypeOf(handler)
if handlerType.NumIn() != 2 { if handlerType.NumIn() != 2 {
@ -137,9 +137,6 @@ func (s *Session) AddHandler(handler interface{}) {
panic("Unable to add event handler, first argument must be of type *discordgo.Session.") panic("Unable to add event handler, first argument must be of type *discordgo.Session.")
} }
s.Lock()
defer s.Unlock()
eventType := handlerType.In(1) eventType := handlerType.In(1)
// Support handlers of type interface{}, this is a special handler, which is triggered on every event. // Support handlers of type interface{}, this is a special handler, which is triggered on every event.
@ -147,18 +144,62 @@ func (s *Session) AddHandler(handler interface{}) {
eventType = nil eventType = nil
} }
return eventType
}
// AddHandler allows you to add an event handler that will be fired anytime
// the Discord WSAPI event that matches the interface fires.
// eventToInterface in events.go has a list of all the Discord WSAPI events
// and their respective interface.
// eg:
// Session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
// })
//
// or:
// Session.AddHandler(func(s *discordgo.Session, m *discordgo.PresenceUpdate) {
// })
// The return value of this method is a function, that when called will remove the
// event handler.
func (s *Session) AddHandler(handler interface{}) func() {
s.initialize()
eventType := s.validateHandler(handler)
s.handlersMu.Lock()
defer s.handlersMu.Unlock()
h := reflect.ValueOf(handler)
handlers := s.handlers[eventType] handlers := s.handlers[eventType]
if handlers == nil { if handlers == nil {
handlers = []reflect.Value{} handlers = []reflect.Value{}
} }
s.handlers[eventType] = append(handlers, reflect.ValueOf(handler)) s.handlers[eventType] = append(handlers, h)
// This must be done as we need a consistent reference to the
// reflected value, otherwise a RemoveHandler method would have
// been nice.
return func() {
s.handlersMu.Lock()
defer s.handlersMu.Unlock()
handlers := s.handlers[eventType]
for i, v := range handlers {
if h == v {
s.handlers[eventType] = append(handlers[:i], handlers[i+1:]...)
return
}
}
}
} }
// handle calls any handlers that match the event type and any handlers of
// interface{}.
func (s *Session) handle(event interface{}) { func (s *Session) handle(event interface{}) {
s.initialize() s.initialize()
s.RLock() s.handlersMu.RLock()
defer s.RUnlock() defer s.handlersMu.RUnlock()
handlerParameters := []reflect.Value{reflect.ValueOf(s), reflect.ValueOf(event)} handlerParameters := []reflect.Value{reflect.ValueOf(s), reflect.ValueOf(event)}
@ -177,16 +218,16 @@ func (s *Session) handle(event interface{}) {
// initialize adds all internal handlers and state tracking handlers. // initialize adds all internal handlers and state tracking handlers.
func (s *Session) initialize() { func (s *Session) initialize() {
s.RLock() s.handlersMu.RLock()
if s.handlers != nil { if s.handlers != nil {
s.RUnlock() s.handlersMu.RUnlock()
return return
} }
s.RUnlock() s.handlersMu.RUnlock()
s.Lock() s.handlersMu.Lock()
s.handlers = map[interface{}][]reflect.Value{} s.handlers = map[interface{}][]reflect.Value{}
s.Unlock() s.handlersMu.Unlock()
s.AddHandler(s.onEvent) s.AddHandler(s.onEvent)
s.AddHandler(s.onReady) s.AddHandler(s.onReady)

View file

@ -225,15 +225,15 @@ func TestOpenClose(t *testing.T) {
} }
} }
func TestHandlers(t *testing.T) { func TestAddHandler(t *testing.T) {
testHandlerCalled := false testHandlerCalled := 0
testHandler := func(s *Session, t *testing.T) { testHandler := func(s *Session, m *MessageCreate) {
testHandlerCalled = true testHandlerCalled++
} }
interfaceHandlerCalled := false interfaceHandlerCalled := 0
interfaceHandler := func(s *Session, i interface{}) { interfaceHandler := func(s *Session, i interface{}) {
interfaceHandlerCalled = true interfaceHandlerCalled++
} }
bogusHandlerCalled := false bogusHandlerCalled := false
@ -243,20 +243,45 @@ func TestHandlers(t *testing.T) {
d := Session{} d := Session{}
d.AddHandler(testHandler) d.AddHandler(testHandler)
d.AddHandler(testHandler)
d.AddHandler(interfaceHandler) d.AddHandler(interfaceHandler)
d.AddHandler(bogusHandler) d.AddHandler(bogusHandler)
d.handle(t) d.handle(&MessageCreate{})
d.handle(&MessageDelete{})
if !testHandlerCalled { // testHandler will be called twice because it was added twice.
t.Fatalf("testHandler was not called.") if testHandlerCalled != 2 {
t.Fatalf("testHandler was not called twice.")
} }
if !interfaceHandlerCalled { // interfaceHandler will be called twice for each event.
t.Fatalf("interfaceHandler was not called.") if interfaceHandlerCalled != 2 {
t.Fatalf("interfaceHandler was not called twice.")
} }
if bogusHandlerCalled { if bogusHandlerCalled {
t.Fatalf("bogusHandler was called.") t.Fatalf("bogusHandler was called.")
} }
} }
func TestRemoveHandler(t *testing.T) {
testHandlerCalled := 0
testHandler := func(s *Session, m *MessageCreate) {
testHandlerCalled++
}
d := Session{}
r := d.AddHandler(testHandler)
d.handle(&MessageCreate{})
r()
d.handle(&MessageCreate{})
if testHandlerCalled != 1 {
t.Fatalf("testHandler was not called once.")
}
}

View file

@ -1,5 +1,47 @@
package discordgo package discordgo
// eventToInterface is a mapping of Discord WSAPI events to their
// DiscordGo event container.
// Each Discord WSAPI event maps to a unique interface.
// Use Session.AddHandler with one of these types to handle that
// type of event.
// eg:
// Session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
// })
//
// or:
// Session.AddHandler(func(s *discordgo.Session, m *discordgo.PresenceUpdate) {
// })
var eventToInterface = map[string]interface{}{
"CHANNEL_CREATE": ChannelCreate{},
"CHANNEL_UPDATE": ChannelUpdate{},
"CHANNEL_DELETE": ChannelDelete{},
"GUILD_CREATE": GuildCreate{},
"GUILD_UPDATE": GuildUpdate{},
"GUILD_DELETE": GuildDelete{},
"GUILD_BAN_ADD": GuildBanAdd{},
"GUILD_BAN_REMOVE": GuildBanRemove{},
"GUILD_MEMBER_ADD": GuildMemberAdd{},
"GUILD_MEMBER_UPDATE": GuildMemberUpdate{},
"GUILD_MEMBER_REMOVE": GuildMemberRemove{},
"GUILD_ROLE_CREATE": GuildRoleCreate{},
"GUILD_ROLE_UPDATE": GuildRoleUpdate{},
"GUILD_ROLE_DELETE": GuildRoleDelete{},
"GUILD_INTEGRATIONS_UPDATE": GuildIntegrationsUpdate{},
"GUILD_EMOJIS_UPDATE": GuildEmojisUpdate{},
"MESSAGE_ACK": MessageAck{},
"MESSAGE_CREATE": MessageCreate{},
"MESSAGE_UPDATE": MessageUpdate{},
"MESSAGE_DELETE": MessageDelete{},
"PRESENCE_UPDATE": PresenceUpdate{},
"READY": Ready{},
"USER_UPDATE": UserUpdate{},
"USER_SETTINGS_UPDATE": UserSettingsUpdate{},
"TYPING_START": TypingStart{},
"VOICE_SERVER_UPDATE": VoiceServerUpdate{},
"VOICE_STATE_UPDATE": VoiceStateUpdate{},
}
// Connect is an empty struct for an event. // Connect is an empty struct for an event.
type Connect struct{} type Connect struct{}

View file

@ -61,7 +61,8 @@ type Session struct {
// StateEnabled is true. // StateEnabled is true.
State *State State *State
// This is a mapping of event structs to a reflected value handlersMu sync.RWMutex
// This is a mapping of event struct to a reflected value
// for event handlers. // for event handlers.
// We store the reflected value instead of the function // We store the reflected value instead of the function
// reference as it is more performant, instead of re-reflecting // reference as it is more performant, instead of re-reflecting

View file

@ -245,38 +245,6 @@ func (s *Session) UpdateStatus(idle int, game string) (err error) {
return return
} }
// eventToInterface is a mapping of Discord WSAPI events to their
// DiscordGo event container.
var eventToInterface = map[string]interface{}{
"CHANNEL_CREATE": ChannelCreate{},
"CHANNEL_UPDATE": ChannelUpdate{},
"CHANNEL_DELETE": ChannelDelete{},
"GUILD_CREATE": GuildCreate{},
"GUILD_UPDATE": GuildUpdate{},
"GUILD_DELETE": GuildDelete{},
"GUILD_BAN_ADD": GuildBanAdd{},
"GUILD_BAN_REMOVE": GuildBanRemove{},
"GUILD_MEMBER_ADD": GuildMemberAdd{},
"GUILD_MEMBER_UPDATE": GuildMemberUpdate{},
"GUILD_MEMBER_REMOVE": GuildMemberRemove{},
"GUILD_ROLE_CREATE": GuildRoleCreate{},
"GUILD_ROLE_UPDATE": GuildRoleUpdate{},
"GUILD_ROLE_DELETE": GuildRoleDelete{},
"GUILD_INTEGRATIONS_UPDATE": GuildIntegrationsUpdate{},
"GUILD_EMOJIS_UPDATE": GuildEmojisUpdate{},
"MESSAGE_ACK": MessageAck{},
"MESSAGE_CREATE": MessageCreate{},
"MESSAGE_UPDATE": MessageUpdate{},
"MESSAGE_DELETE": MessageDelete{},
"PRESENCE_UPDATE": PresenceUpdate{},
"READY": Ready{},
"USER_UPDATE": UserUpdate{},
"USER_SETTINGS_UPDATE": UserSettingsUpdate{},
"TYPING_START": TypingStart{},
"VOICE_SERVER_UPDATE": VoiceServerUpdate{},
"VOICE_STATE_UPDATE": VoiceStateUpdate{},
}
// Front line handler for all Websocket Events. Determines the // Front line handler for all Websocket Events. Determines the
// event type and passes the message along to the next handler. // event type and passes the message along to the next handler.