Procházet zdrojové kódy

Merge pull request #125 from iopred/docs

Handler updates.
Bruce před 9 roky
rodič
revize
290e3c6b87
5 změnil soubory, kde provedl 136 přidání a 60 odebrání
  1. 55 16
      discord.go
  2. 37 11
      discord_test.go
  3. 42 0
      events.go
  4. 2 1
      structs.go
  5. 0 32
      wsapi.go

+ 55 - 16
discord.go

@@ -122,11 +122,11 @@ func New(args ...interface{}) (s *Session, err error) {
 	return
 }
 
-// AddHandler allows you to add an event handler that will be fired anytime
-// the given event is triggered.
-func (s *Session) AddHandler(handler interface{}) {
-	s.initialize()
-
+// validateHandler takes an event handler func, and returns the type of event.
+// eg.
+//     Session.validateHandler(func (s *discordgo.Session, m *discordgo.MessageCreate))
+//     will return the reflect.Type of *discordgo.MessageCreate
+func (s *Session) validateHandler(handler interface{}) reflect.Type {
 	handlerType := reflect.TypeOf(handler)
 
 	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.")
 	}
 
-	s.Lock()
-	defer s.Unlock()
-
 	eventType := handlerType.In(1)
 
 	// 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
 	}
 
+	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]
 	if handlers == nil {
 		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{}) {
 	s.initialize()
 
-	s.RLock()
-	defer s.RUnlock()
+	s.handlersMu.RLock()
+	defer s.handlersMu.RUnlock()
 
 	handlerParameters := []reflect.Value{reflect.ValueOf(s), reflect.ValueOf(event)}
 
@@ -177,16 +218,14 @@ func (s *Session) handle(event interface{}) {
 
 // initialize adds all internal handlers and state tracking handlers.
 func (s *Session) initialize() {
-	s.RLock()
+	s.handlersMu.Lock()
 	if s.handlers != nil {
-		s.RUnlock()
+		s.handlersMu.Unlock()
 		return
 	}
-	s.RUnlock()
 
-	s.Lock()
 	s.handlers = map[interface{}][]reflect.Value{}
-	s.Unlock()
+	s.handlersMu.Unlock()
 
 	s.AddHandler(s.onEvent)
 	s.AddHandler(s.onReady)

+ 37 - 11
discord_test.go

@@ -225,15 +225,15 @@ func TestOpenClose(t *testing.T) {
 	}
 }
 
-func TestHandlers(t *testing.T) {
-	testHandlerCalled := false
-	testHandler := func(s *Session, t *testing.T) {
-		testHandlerCalled = true
+func TestAddHandler(t *testing.T) {
+	testHandlerCalled := 0
+	testHandler := func(s *Session, m *MessageCreate) {
+		testHandlerCalled++
 	}
 
-	interfaceHandlerCalled := false
+	interfaceHandlerCalled := 0
 	interfaceHandler := func(s *Session, i interface{}) {
-		interfaceHandlerCalled = true
+		interfaceHandlerCalled++
 	}
 
 	bogusHandlerCalled := false
@@ -243,20 +243,46 @@ func TestHandlers(t *testing.T) {
 
 	d := Session{}
 	d.AddHandler(testHandler)
+	d.AddHandler(testHandler)
+
 	d.AddHandler(interfaceHandler)
 	d.AddHandler(bogusHandler)
 
-	d.handle(t)
+	d.handle(&MessageCreate{})
+	d.handle(&MessageDelete{})
 
-	if !testHandlerCalled {
-		t.Fatalf("testHandler was not called.")
+	// testHandler will be called twice because it was added twice.
+	if testHandlerCalled != 2 {
+		t.Fatalf("testHandler was not called twice.")
 	}
 
-	if !interfaceHandlerCalled {
-		t.Fatalf("interfaceHandler was not called.")
+	// interfaceHandler will be called twice, once for each event.
+	if interfaceHandlerCalled != 2 {
+		t.Fatalf("interfaceHandler was not called twice.")
 	}
 
 	if bogusHandlerCalled {
 		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{})
+
+	// testHandler will be called once, as it was removed in between calls.
+	if testHandlerCalled != 1 {
+		t.Fatalf("testHandler was not called once.")
+	}
+}

+ 42 - 0
events.go

@@ -1,5 +1,47 @@
 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.
 type Connect struct{}
 

+ 2 - 1
structs.go

@@ -61,7 +61,8 @@ type Session struct {
 	// StateEnabled is true.
 	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.
 	// We store the reflected value instead of the function
 	// reference as it is more performant, instead of re-reflecting

+ 0 - 32
wsapi.go

@@ -245,38 +245,6 @@ func (s *Session) UpdateStatus(idle int, game string) (err error) {
 	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
 // event type and passes the message along to the next handler.