Loading CHANGELOG.md +2 −0 Original line number Diff line number Diff line Loading @@ -6,6 +6,8 @@ The format is based on [keep a changelog](http://keepachangelog.com/) and this p ## [Unreleased] ### Added - Node status now also reports a startup timestamp. - New matchmaking feature. - Optionally send match data to only a subset of match participants. ### Fixed - Set correct initial group member count when group is created. Loading main.go +3 −2 Original line number Diff line number Diff line Loading @@ -95,11 +95,12 @@ func main() { trackerService := server.NewTrackerService(config.GetName()) statsService := server.NewStatsService(jsonLogger, config, semver, trackerService, startedAt) sessionRegistry := server.NewSessionRegistry(jsonLogger, config, trackerService) matchmakerService := server.NewMatchmakerService(config.GetName()) sessionRegistry := server.NewSessionRegistry(jsonLogger, config, trackerService, matchmakerService) messageRouter := server.NewMessageRouterService(sessionRegistry) presenceNotifier := server.NewPresenceNotifier(jsonLogger, config.GetName(), trackerService, messageRouter) trackerService.AddDiffListener(presenceNotifier.HandleDiff) authService := server.NewAuthenticationService(jsonLogger, config, db, statsService, sessionRegistry, trackerService, messageRouter) authService := server.NewAuthenticationService(jsonLogger, config, db, statsService, sessionRegistry, trackerService, matchmakerService, messageRouter) opsService := server.NewOpsService(jsonLogger, multiLogger, semver, config, statsService) gaenabled := len(os.Getenv("NAKAMA_TELEMETRY")) < 1 Loading server/api.proto +29 −3 Original line number Diff line number Diff line Loading @@ -138,7 +138,7 @@ message Envelope { TMatchCreate match_create = 41; TMatchJoin match_join = 42; TMatchLeave match_leave = 43; TMatchDataSend match_data_send = 44; MatchDataSend match_data_send = 44; TMatch match = 45; MatchData match_data = 46; MatchPresence match_presence = 47; Loading @@ -156,6 +156,11 @@ message Envelope { TLeaderboards leaderboards = 57; TLeaderboardRecord leaderboard_record = 58; TLeaderboardRecords leaderboard_records = 59; TMatchmakeAdd matchmake_add = 60; TMatchmakeRemove matchmake_remove = 61; TMatchmakeTicket matchmake_ticket = 62; MatchmakeMatched matchmake_matched = 63; } } Loading Loading @@ -438,9 +443,29 @@ message TopicPresence { repeated UserPresence leaves = 3; } message TMatchmakeAdd { int64 requiredCount = 1; } message TMatchmakeTicket { bytes ticket = 1; } message TMatchmakeRemove { bytes ticket = 1; } message MatchmakeMatched { bytes ticket = 1; bytes token = 2; repeated UserPresence presences = 3; UserPresence self = 4; } message TMatchCreate {} message TMatchJoin { oneof id { bytes match_id = 1; bytes token = 2; } } message TMatch { bytes match_id = 1; Loading @@ -448,10 +473,11 @@ message TMatch { UserPresence self = 3; } message TMatchDataSend { message MatchDataSend { bytes match_id = 1; int64 op_code = 2; bytes data = 3; repeated UserPresence presences = 4; } message MatchData { Loading server/matchmaker.go 0 → 100644 +120 −0 Original line number Diff line number Diff line // Copyright 2017 The Nakama Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package server import ( "errors" "github.com/satori/go.uuid" "sync" ) type Matchmaker interface { Add(sessionID uuid.UUID, userID uuid.UUID, meta PresenceMeta, requiredCount int64) (uuid.UUID, map[MatchmakerKey]*MatchmakerProfile) Remove(sessionID uuid.UUID, userID uuid.UUID, ticket uuid.UUID) error RemoveAll(sessionID uuid.UUID) UpdateAll(sessionID uuid.UUID, meta PresenceMeta) } type MatchmakerKey struct { ID PresenceID UserID uuid.UUID Ticket uuid.UUID } type MatchmakerProfile struct { Meta PresenceMeta RequiredCount int64 } type MatchmakerService struct { sync.Mutex name string values map[MatchmakerKey]*MatchmakerProfile } func NewMatchmakerService(name string) *MatchmakerService { return &MatchmakerService{ name: name, values: make(map[MatchmakerKey]*MatchmakerProfile), } } func (m *MatchmakerService) Add(sessionID uuid.UUID, userID uuid.UUID, meta PresenceMeta, requiredCount int64) (uuid.UUID, map[MatchmakerKey]*MatchmakerProfile) { ticket := uuid.NewV4() selected := make(map[MatchmakerKey]*MatchmakerProfile, requiredCount-1) qmk := MatchmakerKey{ID: PresenceID{SessionID: sessionID, Node: m.name}, UserID: userID, Ticket: ticket} qmp := &MatchmakerProfile{Meta: meta, RequiredCount: requiredCount} m.Lock() for mk, mp := range m.values { if mk.ID.SessionID != sessionID && mk.UserID != userID && mp.RequiredCount == requiredCount { selected[mk] = mp if int64(len(selected)) == requiredCount-1 { break } } } if int64(len(selected)) == requiredCount-1 { for mk, _ := range selected { delete(m.values, mk) } selected[qmk] = qmp } else { m.values[qmk] = qmp } m.Unlock() if int64(len(selected)) != requiredCount { return ticket, nil } else { return ticket, selected } } func (m *MatchmakerService) Remove(sessionID uuid.UUID, userID uuid.UUID, ticket uuid.UUID) error { mk := MatchmakerKey{ID: PresenceID{SessionID: sessionID, Node: m.name}, UserID: userID, Ticket: ticket} var e error m.Lock() _, ok := m.values[mk] if ok { delete(m.values, mk) } else { e = errors.New("ticket not found") } m.Unlock() return e } func (m *MatchmakerService) RemoveAll(sessionID uuid.UUID) { m.Lock() for mk, _ := range m.values { if mk.ID.SessionID == sessionID { delete(m.values, mk) } } m.Unlock() } func (m *MatchmakerService) UpdateAll(sessionID uuid.UUID, meta PresenceMeta) { m.Lock() for mk, mp := range m.values { if mk.ID.SessionID == sessionID { mp.Meta = meta } } m.Unlock() } server/pipeline.go +10 −1 Original line number Diff line number Diff line Loading @@ -28,17 +28,21 @@ type pipeline struct { db *sql.DB socialClient *social.Client tracker Tracker matchmaker Matchmaker hmacSecretByte []byte messageRouter MessageRouter sessionRegistry *SessionRegistry } // NewPipeline creates a new Pipeline func NewPipeline(config Config, db *sql.DB, socialClient *social.Client, tracker Tracker, messageRouter MessageRouter, registry *SessionRegistry) *pipeline { func NewPipeline(config Config, db *sql.DB, socialClient *social.Client, tracker Tracker, matchmaker Matchmaker, messageRouter MessageRouter, registry *SessionRegistry) *pipeline { return &pipeline{ config: config, db: db, socialClient: socialClient, tracker: tracker, matchmaker: matchmaker, hmacSecretByte: []byte(config.GetSession().EncryptionKey), messageRouter: messageRouter, sessionRegistry: registry, } Loading Loading @@ -117,6 +121,11 @@ func (p *pipeline) processRequest(logger *zap.Logger, session *session, envelope case *Envelope_MatchDataSend: p.matchDataSend(logger, session, envelope) case *Envelope_MatchmakeAdd: p.matchmakeAdd(logger, session, envelope) case *Envelope_MatchmakeRemove: p.matchmakeRemove(logger, session, envelope) case *Envelope_StorageFetch: p.storageFetch(logger, session, envelope) case *Envelope_StorageWrite: Loading Loading
CHANGELOG.md +2 −0 Original line number Diff line number Diff line Loading @@ -6,6 +6,8 @@ The format is based on [keep a changelog](http://keepachangelog.com/) and this p ## [Unreleased] ### Added - Node status now also reports a startup timestamp. - New matchmaking feature. - Optionally send match data to only a subset of match participants. ### Fixed - Set correct initial group member count when group is created. Loading
main.go +3 −2 Original line number Diff line number Diff line Loading @@ -95,11 +95,12 @@ func main() { trackerService := server.NewTrackerService(config.GetName()) statsService := server.NewStatsService(jsonLogger, config, semver, trackerService, startedAt) sessionRegistry := server.NewSessionRegistry(jsonLogger, config, trackerService) matchmakerService := server.NewMatchmakerService(config.GetName()) sessionRegistry := server.NewSessionRegistry(jsonLogger, config, trackerService, matchmakerService) messageRouter := server.NewMessageRouterService(sessionRegistry) presenceNotifier := server.NewPresenceNotifier(jsonLogger, config.GetName(), trackerService, messageRouter) trackerService.AddDiffListener(presenceNotifier.HandleDiff) authService := server.NewAuthenticationService(jsonLogger, config, db, statsService, sessionRegistry, trackerService, messageRouter) authService := server.NewAuthenticationService(jsonLogger, config, db, statsService, sessionRegistry, trackerService, matchmakerService, messageRouter) opsService := server.NewOpsService(jsonLogger, multiLogger, semver, config, statsService) gaenabled := len(os.Getenv("NAKAMA_TELEMETRY")) < 1 Loading
server/api.proto +29 −3 Original line number Diff line number Diff line Loading @@ -138,7 +138,7 @@ message Envelope { TMatchCreate match_create = 41; TMatchJoin match_join = 42; TMatchLeave match_leave = 43; TMatchDataSend match_data_send = 44; MatchDataSend match_data_send = 44; TMatch match = 45; MatchData match_data = 46; MatchPresence match_presence = 47; Loading @@ -156,6 +156,11 @@ message Envelope { TLeaderboards leaderboards = 57; TLeaderboardRecord leaderboard_record = 58; TLeaderboardRecords leaderboard_records = 59; TMatchmakeAdd matchmake_add = 60; TMatchmakeRemove matchmake_remove = 61; TMatchmakeTicket matchmake_ticket = 62; MatchmakeMatched matchmake_matched = 63; } } Loading Loading @@ -438,9 +443,29 @@ message TopicPresence { repeated UserPresence leaves = 3; } message TMatchmakeAdd { int64 requiredCount = 1; } message TMatchmakeTicket { bytes ticket = 1; } message TMatchmakeRemove { bytes ticket = 1; } message MatchmakeMatched { bytes ticket = 1; bytes token = 2; repeated UserPresence presences = 3; UserPresence self = 4; } message TMatchCreate {} message TMatchJoin { oneof id { bytes match_id = 1; bytes token = 2; } } message TMatch { bytes match_id = 1; Loading @@ -448,10 +473,11 @@ message TMatch { UserPresence self = 3; } message TMatchDataSend { message MatchDataSend { bytes match_id = 1; int64 op_code = 2; bytes data = 3; repeated UserPresence presences = 4; } message MatchData { Loading
server/matchmaker.go 0 → 100644 +120 −0 Original line number Diff line number Diff line // Copyright 2017 The Nakama Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package server import ( "errors" "github.com/satori/go.uuid" "sync" ) type Matchmaker interface { Add(sessionID uuid.UUID, userID uuid.UUID, meta PresenceMeta, requiredCount int64) (uuid.UUID, map[MatchmakerKey]*MatchmakerProfile) Remove(sessionID uuid.UUID, userID uuid.UUID, ticket uuid.UUID) error RemoveAll(sessionID uuid.UUID) UpdateAll(sessionID uuid.UUID, meta PresenceMeta) } type MatchmakerKey struct { ID PresenceID UserID uuid.UUID Ticket uuid.UUID } type MatchmakerProfile struct { Meta PresenceMeta RequiredCount int64 } type MatchmakerService struct { sync.Mutex name string values map[MatchmakerKey]*MatchmakerProfile } func NewMatchmakerService(name string) *MatchmakerService { return &MatchmakerService{ name: name, values: make(map[MatchmakerKey]*MatchmakerProfile), } } func (m *MatchmakerService) Add(sessionID uuid.UUID, userID uuid.UUID, meta PresenceMeta, requiredCount int64) (uuid.UUID, map[MatchmakerKey]*MatchmakerProfile) { ticket := uuid.NewV4() selected := make(map[MatchmakerKey]*MatchmakerProfile, requiredCount-1) qmk := MatchmakerKey{ID: PresenceID{SessionID: sessionID, Node: m.name}, UserID: userID, Ticket: ticket} qmp := &MatchmakerProfile{Meta: meta, RequiredCount: requiredCount} m.Lock() for mk, mp := range m.values { if mk.ID.SessionID != sessionID && mk.UserID != userID && mp.RequiredCount == requiredCount { selected[mk] = mp if int64(len(selected)) == requiredCount-1 { break } } } if int64(len(selected)) == requiredCount-1 { for mk, _ := range selected { delete(m.values, mk) } selected[qmk] = qmp } else { m.values[qmk] = qmp } m.Unlock() if int64(len(selected)) != requiredCount { return ticket, nil } else { return ticket, selected } } func (m *MatchmakerService) Remove(sessionID uuid.UUID, userID uuid.UUID, ticket uuid.UUID) error { mk := MatchmakerKey{ID: PresenceID{SessionID: sessionID, Node: m.name}, UserID: userID, Ticket: ticket} var e error m.Lock() _, ok := m.values[mk] if ok { delete(m.values, mk) } else { e = errors.New("ticket not found") } m.Unlock() return e } func (m *MatchmakerService) RemoveAll(sessionID uuid.UUID) { m.Lock() for mk, _ := range m.values { if mk.ID.SessionID == sessionID { delete(m.values, mk) } } m.Unlock() } func (m *MatchmakerService) UpdateAll(sessionID uuid.UUID, meta PresenceMeta) { m.Lock() for mk, mp := range m.values { if mk.ID.SessionID == sessionID { mp.Meta = meta } } m.Unlock() }
server/pipeline.go +10 −1 Original line number Diff line number Diff line Loading @@ -28,17 +28,21 @@ type pipeline struct { db *sql.DB socialClient *social.Client tracker Tracker matchmaker Matchmaker hmacSecretByte []byte messageRouter MessageRouter sessionRegistry *SessionRegistry } // NewPipeline creates a new Pipeline func NewPipeline(config Config, db *sql.DB, socialClient *social.Client, tracker Tracker, messageRouter MessageRouter, registry *SessionRegistry) *pipeline { func NewPipeline(config Config, db *sql.DB, socialClient *social.Client, tracker Tracker, matchmaker Matchmaker, messageRouter MessageRouter, registry *SessionRegistry) *pipeline { return &pipeline{ config: config, db: db, socialClient: socialClient, tracker: tracker, matchmaker: matchmaker, hmacSecretByte: []byte(config.GetSession().EncryptionKey), messageRouter: messageRouter, sessionRegistry: registry, } Loading Loading @@ -117,6 +121,11 @@ func (p *pipeline) processRequest(logger *zap.Logger, session *session, envelope case *Envelope_MatchDataSend: p.matchDataSend(logger, session, envelope) case *Envelope_MatchmakeAdd: p.matchmakeAdd(logger, session, envelope) case *Envelope_MatchmakeRemove: p.matchmakeRemove(logger, session, envelope) case *Envelope_StorageFetch: p.storageFetch(logger, session, envelope) case *Envelope_StorageWrite: Loading