Skip to content

Commit

Permalink
Remove use of reflect.
Browse files Browse the repository at this point in the history
This introduces gogenerate'ed EventHandlers from the files in events.go

This also adds support for AddHandlerOnce.
  • Loading branch information
iopred committed Dec 4, 2016
1 parent 24a51f6 commit 3660125
Show file tree
Hide file tree
Showing 9 changed files with 1,505 additions and 389 deletions.
172 changes: 1 addition & 171 deletions discord.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@
// Package discordgo provides Discord binding for Go
package discordgo

import (
"fmt"
"reflect"
)
import "fmt"

// VERSION of Discordgo, follows Symantic Versioning. (http://semver.org/)
const VERSION = "0.14.0-dev"
Expand Down Expand Up @@ -126,170 +123,3 @@ func New(args ...interface{}) (s *Session, err error) {

return
}

// 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 {
panic("Unable to add event handler, handler must be of the type func(*discordgo.Session, *discordgo.EventType).")
}

if handlerType.In(0) != reflect.TypeOf(s) {
panic("Unable to add event handler, first argument must be of type *discordgo.Session.")
}

eventType := handlerType.In(1)

// Support handlers of type interface{}, this is a special handler, which is triggered on every event.
if eventType.Kind() == reflect.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)

s.handlers[eventType] = append(s.handlers[eventType], 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.handlersMu.RLock()
defer s.handlersMu.RUnlock()

if s.handlers == nil {
return
}

handlerParameters := []reflect.Value{reflect.ValueOf(s), reflect.ValueOf(event)}

s.onInterface(event)

if handlers, ok := s.handlers[nil]; ok {
for _, handler := range handlers {
go handler.Call(handlerParameters)
}
}

if handlers, ok := s.handlers[reflect.TypeOf(event)]; ok {
for _, handler := range handlers {
go handler.Call(handlerParameters)
}
}
}

// initialize adds all internal handlers and state tracking handlers.
func (s *Session) initialize() {

s.log(LogInformational, "called")

s.handlersMu.Lock()
defer s.handlersMu.Unlock()

if s.handlers != nil {
return
}

s.handlers = map[interface{}][]reflect.Value{}
}

// setGuildIds will set the GuildID on all the members of a guild.
// This is done as event data does not have it set.
func setGuildIds(g *Guild) {
for _, c := range g.Channels {
c.GuildID = g.ID
}

for _, m := range g.Members {
m.GuildID = g.ID
}

for _, vs := range g.VoiceStates {
vs.GuildID = g.ID
}
}

// onInterface handles all internal events and routes them to the appropriate internal handler.
func (s *Session) onInterface(i interface{}) {
switch t := i.(type) {
case *Ready:
for _, g := range t.Guilds {
setGuildIds(g)
}
s.onReady(t)
case *GuildCreate:
setGuildIds(t.Guild)
case *GuildUpdate:
setGuildIds(t.Guild)
case *Resumed:
s.onResumed(t)
case *VoiceServerUpdate:
go s.onVoiceServerUpdate(t)
case *VoiceStateUpdate:
go s.onVoiceStateUpdate(t)
}
s.State.onInterface(s, i)
}

// onReady handles the ready event.
func (s *Session) onReady(r *Ready) {

// Store the SessionID within the Session struct.
s.sessionID = r.SessionID

// Start the heartbeat to keep the connection alive.
go s.heartbeat(s.wsConn, s.listening, r.HeartbeatInterval)
}

// onResumed handles the resumed event.
func (s *Session) onResumed(r *Resumed) {

// Start the heartbeat to keep the connection alive.
go s.heartbeat(s.wsConn, s.listening, r.HeartbeatInterval)
}
8 changes: 4 additions & 4 deletions discord_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,8 @@ func TestAddHandler(t *testing.T) {
d.AddHandler(interfaceHandler)
d.AddHandler(bogusHandler)

d.handle(&MessageCreate{})
d.handle(&MessageDelete{})
d.handleEvent(messageCreateEventType, &MessageCreate{})
d.handleEvent(messageDeleteEventType, &MessageDelete{})

<-time.After(500 * time.Millisecond)

Expand Down Expand Up @@ -253,11 +253,11 @@ func TestRemoveHandler(t *testing.T) {
d := Session{}
r := d.AddHandler(testHandler)

d.handle(&MessageCreate{})
d.handleEvent(messageCreateEventType, &MessageCreate{})

r()

d.handle(&MessageCreate{})
d.handleEvent(messageCreateEventType, &MessageCreate{})

<-time.After(500 * time.Millisecond)

Expand Down
Loading

0 comments on commit 3660125

Please sign in to comment.