Thread safety and more events.

This commit is contained in:
Chris Rhodes 2016-02-14 13:41:56 -08:00
parent fb6ae92555
commit 8ffaa85b0b
4 changed files with 66 additions and 122 deletions

View file

@ -123,6 +123,9 @@ func New(args ...interface{}) (s *Session, err error) {
}
func (s *Session) AddHandler(handler interface{}) {
s.Lock()
defer s.Unlock()
handlerType := reflect.TypeOf(handler)
if handlerType.NumIn() != 2 {
@ -133,12 +136,14 @@ func (s *Session) AddHandler(handler interface{}) {
panic("Unable to add event handler, first argument must be of type *discordgo.Session.")
}
eventType := handlerType.In(1)
if s.Handlers == nil {
s.Unlock()
s.initialize()
s.Lock()
}
eventType := handlerType.In(1)
handlers := s.Handlers[eventType]
if handlers == nil {
handlers = []interface{}{}
@ -149,6 +154,9 @@ func (s *Session) AddHandler(handler interface{}) {
}
func (s *Session) Handle(event interface{}) (handled bool) {
s.RLock()
defer s.RUnlock()
eventType := reflect.TypeOf(event)
handlers, ok := s.Handlers[eventType]
@ -164,17 +172,23 @@ func (s *Session) Handle(event interface{}) (handled bool) {
return
}
// initialize adds internal handlers such as onEvent and state tracking
// handlers.
// initialize adds all internal handlers and state tracking handlers.
func (s *Session) initialize() {
s.Lock()
defer s.Unlock()
s.Handlers = map[interface{}][]interface{}{}
s.AddHandler(s.ready)
s.AddHandler(s.State.ready)
s.AddHandler(s.State.messageCreate)
s.AddHandler(s.State.messageUpdate)
s.AddHandler(s.State.messageDelete)
s.AddHandler(s.onReady)
s.AddHandler(s.onVoiceServerUpdate)
s.AddHandler(s.onVoiceStateUpdate)
s.AddHandler(s.State.onReady)
s.AddHandler(s.State.onMessageCreate)
s.AddHandler(s.State.onMessageUpdate)
s.AddHandler(s.State.onMessageDelete)
}
func (s *Session) ready(se *Session, r *Ready) {
// onReady handles the ready event.
func (s *Session) onReady(se *Session, r *Ready) {
go s.heartbeat(s.wsConn, s.listening, r.HeartbeatInterval)
}

View file

@ -469,30 +469,30 @@ func (s *State) Message(channelID, messageID string) (*Message, error) {
return nil, errors.New("Message not found.")
}
// ready is an event handler.
func (s *State) ready(se *Session, r *Ready) {
// onReady handles the ready event.
func (s *State) onReady(se *Session, r *Ready) {
if se.StateEnabled {
s.OnReady(r)
}
}
// messageCreate is an event handler.
func (s *State) messageCreate(se *Session, m *MessageCreate) {
// onMessageCreate handles the messageCreate event.
func (s *State) onMessageCreate(se *Session, m *MessageCreate) {
if se.StateEnabled {
s.MessageAdd(&m.Message)
s.MessageAdd(m.Message)
}
}
// messageUpdate is an event handler.
func (s *State) messageUpdate(se *Session, m *MessageUpdate) {
// onMessageUpdate handles the messageUpdate event.
func (s *State) onMessageUpdate(se *Session, m *MessageUpdate) {
if se.StateEnabled {
s.MessageAdd(&m.Message)
s.MessageAdd(m.Message)
}
}
// messageDelete is an event handler.
func (s *State) messageDelete(se *Session, m *MessageDelete) {
// onMessageDelete handles the messageDelete event.
func (s *State) onMessageDelete(se *Session, m *MessageDelete) {
if se.StateEnabled {
s.MessageRemove(&m.Message)
s.MessageRemove(m.Message)
}
}

View file

@ -29,46 +29,8 @@ type Session struct {
Token string // Authentication token for this session
Debug bool // Debug for printing JSON request/responses
// Settable Callback functions for Internal Events
// OnConnect is called when the websocket connection opens.
OnConnect func(*Session)
// OnDisconnect is called when the websocket connection closes.
// This is a good handler to add reconnection logic to.
OnDisconnect func(*Session)
Handlers map[interface{}][]interface{}
// Settable Callback functions for Websocket Events
OnEvent func(*Session, *Event)
OnReady func(*Session, *Ready)
OnTypingStart func(*Session, *TypingStart)
OnMessageCreate func(*Session, *Message)
OnMessageUpdate func(*Session, *Message)
OnMessageDelete func(*Session, *Message)
OnMessageAck func(*Session, *MessageAck)
OnUserUpdate func(*Session, *User)
OnPresenceUpdate func(*Session, *PresenceUpdate)
OnVoiceServerUpdate func(*Session, *VoiceServerUpdate)
OnVoiceStateUpdate func(*Session, *VoiceState)
OnChannelCreate func(*Session, *Channel)
OnChannelUpdate func(*Session, *Channel)
OnChannelDelete func(*Session, *Channel)
OnGuildCreate func(*Session, *Guild)
OnGuildUpdate func(*Session, *Guild)
OnGuildDelete func(*Session, *Guild)
OnGuildMemberAdd func(*Session, *Member)
OnGuildMemberRemove func(*Session, *Member)
OnGuildMemberDelete func(*Session, *Member)
OnGuildMemberUpdate func(*Session, *Member)
OnGuildRoleCreate func(*Session, *GuildRole)
OnGuildRoleUpdate func(*Session, *GuildRole)
OnGuildRoleDelete func(*Session, *GuildRoleDelete)
OnGuildIntegrationsUpdate func(*Session, *GuildIntegrationsUpdate)
OnGuildBanAdd func(*Session, *GuildBan)
OnGuildBanRemove func(*Session, *GuildBan)
OnGuildEmojisUpdate func(*Session, *GuildEmojisUpdate)
OnUserSettingsUpdate func(*Session, map[string]interface{}) // TODO: Find better way?
// Exposed but should not be modified by User.
SessionID string // from websocket READY packet
DataReady bool // Set to true when Data Websocket is ready

View file

@ -86,9 +86,7 @@ func (s *Session) Open() (err error) {
s.Unlock()
if s.OnConnect != nil {
s.OnConnect(s)
}
s.Handle(&Connect{})
return
}
@ -112,9 +110,7 @@ func (s *Session) Close() (err error) {
s.Unlock()
if s.OnDisconnect != nil {
s.OnDisconnect(s)
}
s.Handle(&Disconnect{})
return
}
@ -255,8 +251,12 @@ func (s *Session) UpdateStatus(idle int, game string) (err error) {
// Events will be handled by any implemented handler in Session.
// All unhandled events will then be handled by OnEvent.
func (s *Session) event(messageType int, message []byte) {
s.RLock()
if s.Handlers == nil {
s.RUnlock()
s.initialize()
} else {
s.RUnlock()
}
var err error
@ -291,6 +291,9 @@ func (s *Session) event(messageType int, message []byte) {
var i interface{}
// TODO(iopred): Figure out a clean way to do this with a map, simply
// creating a map[string]interface{} will not work, as that will reuse
// the same instance for each event.
switch e.Type {
case "READY":
i = &Ready{}
@ -304,63 +307,28 @@ func (s *Session) event(messageType int, message []byte) {
i = &PresenceUpdate{}
case "TYPING_START":
i = &TypingStart{}
case "VOICE_SERVER_UPDATE":
i = &VoiceServerUpdate{}
case "VOICE_STATE_UPDATE":
i = &VoiceStateUpdate{}
case "USER_UPDATE":
i = &UserUpdate{}
case "MESSAGE_ACK":
i = &MessageAck{}
case "GUILD_ROLE_CREATE":
i = &GuildRoleCreate{}
case "GUILD_ROLE_UPDATE":
i = &GuildRoleUpdate{}
case "GUILD_ROLE_DELETE":
i = &GuildRoleDelete{}
case "GUILD_INTEGRATIONS_UPDATE":
i = &GuildIntegrationsUpdate{}
case "GUILD_BAN_ADD":
i = &GuildBanAdd{}
case "GUILD_BAN_REMOVE":
i = &GuildBanRemove{}
}
// case "VOICE_SERVER_UPDATE":
// if s.Voice == nil && s.OnVoiceServerUpdate == nil {
// break
// }
// var st *VoiceServerUpdate
// if err = unmarshalEvent(e, &st); err == nil {
// if s.Voice != nil {
// s.onVoiceServerUpdate(st)
// }
// if s.OnVoiceServerUpdate != nil {
// s.OnVoiceServerUpdate(s, st)
// }
// }
// if s.OnVoiceServerUpdate != nil {
// return
// }
// case "VOICE_STATE_UPDATE":
// if s.Voice == nil && s.OnVoiceStateUpdate == nil {
// break
// }
// var st *VoiceState
// if err = unmarshalEvent(e, &st); err == nil {
// if s.Voice != nil {
// s.onVoiceStateUpdate(st)
// }
// if s.OnVoiceStateUpdate != nil {
// s.OnVoiceStateUpdate(s, st)
// }
// }
// if s.OnVoiceStateUpdate != nil {
// return
// }
// case "USER_UPDATE":
// if s.OnUserUpdate != nil {
// var st *User
// if err = unmarshalEvent(e, &st); err == nil {
// s.OnUserUpdate(s, st)
// }
// return
// }
// /* Never seen this come in but saw it in another Library.
// case "MESSAGE_ACK":
// if s.OnMessageAck != nil {
// }
// */
// case "MESSAGE_ACK":
// if s.OnMessageAck != nil {
// var st *MessageAck
// if err = unmarshalEvent(e, &st); err == nil {
// s.OnMessageAck(s, st)
// }
// return
// }
// case "CHANNEL_CREATE":
// if !s.StateEnabled && s.OnChannelCreate == nil {
// break
@ -696,7 +664,7 @@ func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool) (err error)
// onVoiceStateUpdate handles Voice State Update events on the data
// websocket. This comes immediately after the call to VoiceChannelJoin
// for the session user.
func (s *Session) onVoiceStateUpdate(st *VoiceState) {
func (s *Session) onVoiceStateUpdate(se *Session, st *VoiceStateUpdate) {
// Need to have this happen at login and store it in the Session
// TODO : This should be done upon connecting to Discord, or
@ -722,7 +690,7 @@ func (s *Session) onVoiceStateUpdate(st *VoiceState) {
// onVoiceServerUpdate handles the Voice Server Update data websocket event.
// This event tells us the information needed to open a voice websocket
// connection and should happen after the VOICE_STATE event.
func (s *Session) onVoiceServerUpdate(st *VoiceServerUpdate) {
func (s *Session) onVoiceServerUpdate(se *Session, st *VoiceServerUpdate) {
// Store values for later use
s.Voice.token = st.Token