Przeglądaj źródła

Thread safety and more events.

Chris Rhodes 9 lat temu
rodzic
commit
8ffaa85b0b
4 zmienionych plików z 66 dodań i 122 usunięć
  1. 24 10
      discord.go
  2. 11 11
      state.go
  3. 0 38
      structs.go
  4. 31 63
      wsapi.go

+ 24 - 10
discord.go

@@ -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)
 }

+ 11 - 11
state.go

@@ -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)
 	}
 }

+ 0 - 38
structs.go

@@ -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

+ 31 - 63
wsapi.go

@@ -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