From 6272ebe31b0e07f35e822a89245b2077e562719c Mon Sep 17 00:00:00 2001 From: Andrei Mihu Date: Tue, 12 Jan 2021 14:18:26 +0000 Subject: [PATCH] Matchmaker improvements, support for party matchmaking. (#521) --- CHANGELOG.md | 5 + go.mod | 2 +- go.sum | 2 + main.go | 3 +- server/config.go | 32 + server/match_registry_test.go | 5 +- server/matchmaker.go | 573 ++++++++++++++---- server/pipeline_matchmaker.go | 77 +-- server/session_ws.go | 2 +- .../heroiclabs/nakama-common/api/api.proto | 2 - .../nakama-common/rtapi/realtime.pb.go | 231 +++---- .../nakama-common/rtapi/realtime.proto | 4 +- .../nakama-common/runtime/runtime.go | 1 + vendor/modules.txt | 2 +- 14 files changed, 636 insertions(+), 305 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac4c9b5fa..0ff7e3feb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,12 @@ All notable changes to this project are documented below. The format is based on [keep a changelog](http://keepachangelog.com) and this project uses [semantic versioning](http://semver.org). ## [Unreleased] +### Added +- Add party matching support to matchmaker. +- Add options to matchmaker to control how long tickets are allowed to wait for their preferred match. + ### Changed +- Matchmaker improvements to quality of matching and player count range handling. - Build with Go 1.15.6 release. ## [2.15.0] - 2020-11-28 diff --git a/go.mod b/go.mod index 93815d6c8..6f32f01d9 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/gorilla/mux v1.7.4 github.com/gorilla/websocket v1.4.2 github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.1 - github.com/heroiclabs/nakama-common v1.10.1-0.20201216142705-136e6c0d214f + github.com/heroiclabs/nakama-common v1.10.1-0.20210112124918-da701a0f7020 github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect github.com/jackc/pgx v3.5.0+incompatible github.com/jmhodges/levigo v1.0.0 // indirect diff --git a/go.sum b/go.sum index 35aa7793f..11765c81f 100644 --- a/go.sum +++ b/go.sum @@ -207,6 +207,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/heroiclabs/nakama-common v1.10.1-0.20201216142705-136e6c0d214f h1:RTKN7L8sWQjsyBEv4fW65MsHbLxnO/NbgR/l+M3uaF4= github.com/heroiclabs/nakama-common v1.10.1-0.20201216142705-136e6c0d214f/go.mod h1:li7bMQwOYA0NjT3DM4NKQBNruULPa2hrqdiSaaTwui4= +github.com/heroiclabs/nakama-common v1.10.1-0.20210112124918-da701a0f7020 h1:6jaVk2j7QSdSb4APKMQjDDa/s6FgxcZF+vAKszoCbkg= +github.com/heroiclabs/nakama-common v1.10.1-0.20210112124918-da701a0f7020/go.mod h1:li7bMQwOYA0NjT3DM4NKQBNruULPa2hrqdiSaaTwui4= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= diff --git a/main.go b/main.go index 2539c3565..80c858809 100644 --- a/main.go +++ b/main.go @@ -127,7 +127,6 @@ func main() { // Start up server components. metrics := server.NewMetrics(logger, startupLogger, config) - matchmaker := server.NewLocalMatchmaker(startupLogger, config.GetName()) sessionRegistry := server.NewLocalSessionRegistry(metrics) tracker := server.StartLocalTracker(logger, config, sessionRegistry, metrics, jsonpbMarshaler) router := server.NewLocalMessageRouter(sessionRegistry, tracker, jsonpbMarshaler) @@ -142,6 +141,7 @@ func main() { if err != nil { startupLogger.Fatal("Failed initializing runtime modules", zap.Error(err)) } + matchmaker := server.NewLocalMatchmaker(logger, startupLogger, config, router, runtime) leaderboardScheduler.Start(runtime) @@ -207,6 +207,7 @@ func main() { apiServer.Stop() consoleServer.Stop() metrics.Stop(logger) + matchmaker.Stop() leaderboardScheduler.Stop() tracker.Stop() sessionRegistry.Stop() diff --git a/server/config.go b/server/config.go index fe3c6af8e..4382ff3d0 100644 --- a/server/config.go +++ b/server/config.go @@ -48,6 +48,7 @@ type Config interface { GetTracker() *TrackerConfig GetConsole() *ConsoleConfig GetLeaderboard() *LeaderboardConfig + GetMatchmaker() *MatchmakerConfig Clone() (Config, error) } @@ -238,6 +239,15 @@ func CheckConfig(logger *zap.Logger, config Config) map[string]string { if config.GetLeaderboard().CallbackQueueWorkers < 1 { logger.Fatal("Leaderboard callback queue workers must be >= 1", zap.Int("leaderboard.callback_queue_workers", config.GetLeaderboard().CallbackQueueWorkers)) } + if config.GetMatchmaker().MaxTickets < 1 { + logger.Fatal("Matchmaker maximum ticket count must be >= 1", zap.Int("matchmaker.max_tickets", config.GetMatchmaker().MaxTickets)) + } + if config.GetMatchmaker().IntervalSec < 1 { + logger.Fatal("Matchmaker interval time seconds must be >= 1", zap.Int("matchmaker.interval_sec", config.GetMatchmaker().IntervalSec)) + } + if config.GetMatchmaker().MaxIntervals < 1 { + logger.Fatal("Matchmaker max intervals must be >= 1", zap.Int("matchmaker.max_intervals", config.GetMatchmaker().MaxIntervals)) + } // If the runtime path is not overridden, set it to `datadir/modules`. if config.GetRuntime().Path == "" { @@ -358,6 +368,7 @@ type config struct { Tracker *TrackerConfig `yaml:"tracker" json:"tracker" usage:"Presence tracker properties."` Console *ConsoleConfig `yaml:"console" json:"console" usage:"Console settings."` Leaderboard *LeaderboardConfig `yaml:"leaderboard" json:"leaderboard" usage:"Leaderboard settings."` + Matchmaker *MatchmakerConfig `yaml:"matchmaker" json:"matchmaker" usage:"Matchmaker settings."` } // NewConfig constructs a Config struct which represents server settings, and populates it with default values. @@ -381,6 +392,7 @@ func NewConfig(logger *zap.Logger) *config { Tracker: NewTrackerConfig(), Console: NewConsoleConfig(), Leaderboard: NewLeaderboardConfig(), + Matchmaker: NewMatchmakerConfig(), } } @@ -396,6 +408,7 @@ func (c *config) Clone() (Config, error) { configTracker := *(c.Tracker) configConsole := *(c.Console) configLeaderboard := *(c.Leaderboard) + configMatchmaker := *(c.Matchmaker) nc := &config{ Name: c.Name, Datadir: c.Datadir, @@ -411,6 +424,7 @@ func (c *config) Clone() (Config, error) { Tracker: &configTracker, Console: &configConsole, Leaderboard: &configLeaderboard, + Matchmaker: &configMatchmaker, } nc.Socket.CertPEMBlock = make([]byte, len(c.Socket.CertPEMBlock)) copy(nc.Socket.CertPEMBlock, c.Socket.CertPEMBlock) @@ -493,6 +507,10 @@ func (c *config) GetLeaderboard() *LeaderboardConfig { return c.Leaderboard } +func (c *config) GetMatchmaker() *MatchmakerConfig { + return c.Matchmaker +} + // LoggerConfig is configuration relevant to logging levels and output. type LoggerConfig struct { Level string `yaml:"level" json:"level" usage:"Log level to set. Valid values are 'debug', 'info', 'warn', 'error'. Default 'info'."` @@ -823,3 +841,17 @@ func NewLeaderboardConfig() *LeaderboardConfig { CallbackQueueWorkers: 8, } } + +type MatchmakerConfig struct { + MaxTickets int `yaml:"max_tickets" json:"max_tickets" usage:"Maximum number of concurrent matchmaking tickets allowed per session. Default 3."` + IntervalSec int `yaml:"interval_sec" json:"interval_sec" usage:"How quickly the matchmaker attempts to form matches, in seconds. Default 15."` + MaxIntervals int `yaml:"max_intervals" json:"max_intervals" usage:"How many intervals the matchmaker attempts to find matches at the max player count, before allowing min count. Default 2."` +} + +func NewMatchmakerConfig() *MatchmakerConfig { + return &MatchmakerConfig{ + MaxTickets: 3, + IntervalSec: 15, + MaxIntervals: 2, + } +} diff --git a/server/match_registry_test.go b/server/match_registry_test.go index fd41f5aa2..2d81b8456 100644 --- a/server/match_registry_test.go +++ b/server/match_registry_test.go @@ -17,15 +17,14 @@ package server import ( "bytes" "encoding/gob" - "github.com/gofrs/uuid" "github.com/heroiclabs/nakama-common/runtime" "testing" ) func TestEncode(t *testing.T) { entries := []runtime.MatchmakerEntry{ - &MatchmakerEntry{Ticket: "123", Presence: &MatchmakerPresence{Username: "a"}, SessionID: uuid.Must(uuid.NewV4())}, - &MatchmakerEntry{Ticket: "456", Presence: &MatchmakerPresence{Username: "b"}, SessionID: uuid.Must(uuid.NewV4())}, + &MatchmakerEntry{Ticket: "123", Presence: &MatchmakerPresence{Username: "a"}}, + &MatchmakerEntry{Ticket: "456", Presence: &MatchmakerPresence{Username: "b"}}, } var buf bytes.Buffer if err := gob.NewEncoder(&buf).Encode(map[string]interface{}{"foo": entries}); err != nil { diff --git a/server/matchmaker.go b/server/matchmaker.go index 4a81008ff..15cbc9459 100644 --- a/server/matchmaker.go +++ b/server/matchmaker.go @@ -15,24 +15,38 @@ package server import ( + "context" + "errors" + "fmt" + "github.com/dgrijalva/jwt-go" + "github.com/heroiclabs/nakama-common/rtapi" + "github.com/heroiclabs/nakama-common/runtime" + "go.uber.org/atomic" "sync" - - "github.com/blevesearch/bleve/analysis/analyzer/keyword" + "time" "github.com/blevesearch/bleve" + "github.com/blevesearch/bleve/analysis/analyzer/keyword" "github.com/gofrs/uuid" - "github.com/heroiclabs/nakama-common/runtime" - "github.com/pkg/errors" "go.uber.org/zap" ) -var ErrMatchmakerTicketNotFound = errors.New("ticket not found") +var ( + ErrMatchmakerQueryInvalid = errors.New("matchmaker query invalid") + ErrMatchmakerDuplicateSession = errors.New("matchmaker duplicate session") + ErrMatchmakerIndex = errors.New("matchmaker index error") + ErrMatchmakerDelete = errors.New("matchmaker delete error") + ErrMatchmakerNotAvailable = errors.New("matchmaker not available") + ErrMatchmakerTooManyTickets = errors.New("matchmaker too many tickets") + ErrMatchmakerTicketNotFound = errors.New("matchmaker ticket not found") +) type MatchmakerPresence struct { - UserId string `json:"user_id"` - SessionId string `json:"session_id"` - Username string `json:"username"` - Node string `json:"node"` + UserId string `json:"user_id"` + SessionId string `json:"session_id"` + Username string `json:"username"` + Node string `json:"node"` + SessionID uuid.UUID `json:"-"` } func (p *MatchmakerPresence) GetUserId() string { @@ -61,10 +75,10 @@ type MatchmakerEntry struct { Ticket string `json:"ticket"` Presence *MatchmakerPresence `json:"presence"` Properties map[string]interface{} `json:"properties"` - // Cached for when we need them returned to clients, but not indexed. + PartyId string `json:"party_id"` + StringProperties map[string]string `json:"-"` NumericProperties map[string]float64 `json:"-"` - SessionID uuid.UUID `json:"-"` } func (m *MatchmakerEntry) GetPresence() runtime.Presence { @@ -76,21 +90,53 @@ func (m *MatchmakerEntry) GetTicket() string { func (m *MatchmakerEntry) GetProperties() map[string]interface{} { return m.Properties } +func (m *MatchmakerEntry) GetPartyId() string { + return m.PartyId +} + +type MatchmakerIndex struct { + Ticket string `json:"ticket"` + Properties map[string]interface{} `json:"properties"` + MinCount int `json:"min_count"` + MaxCount int `json:"max_count"` + PartyId string `json:"party_id"` + CreatedAt int64 `json:"created_at"` + + // Parameters used for correctly processing various matchmaker operations, but not indexed for searching. + Query string `json:"-"` + Count int `json:"-"` + SessionID string `json:"-"` + Intervals int `json:"-"` + SessionIDs map[string]struct{} `json:"-"` +} type Matchmaker interface { - Add(session Session, query string, minCount int, maxCount int, stringProperties map[string]string, numericProperties map[string]float64) (string, []*MatchmakerEntry, error) - Remove(sessionID uuid.UUID, ticket string) error - RemoveAll(sessionID uuid.UUID) error + Stop() + Add(sessionID string, presences []*MatchmakerPresence, partyId, query string, minCount, maxCount int, stringProperties map[string]string, numericProperties map[string]float64) (string, error) + Remove(sessionID, ticket string) error + RemoveAll(sessionID string) error } type LocalMatchmaker struct { sync.Mutex + logger *zap.Logger node string - entries map[string]*MatchmakerEntry - index bleve.Index + config Config + router MessageRouter + runtime *Runtime + + stopped *atomic.Bool + ctx context.Context + ctxCancelFn context.CancelFunc + + index bleve.Index + sessionTickets map[string]map[string]struct{} + entries map[string][]*MatchmakerEntry + indexes map[string]*MatchmakerIndex + activeIndexes map[string]*MatchmakerIndex } -func NewLocalMatchmaker(startupLogger *zap.Logger, node string) Matchmaker { +func NewLocalMatchmaker(logger, startupLogger *zap.Logger, config Config, router MessageRouter, runtime *Runtime) Matchmaker { mapping := bleve.NewIndexMapping() mapping.DefaultAnalyzer = keyword.Name @@ -99,14 +145,281 @@ func NewLocalMatchmaker(startupLogger *zap.Logger, node string) Matchmaker { startupLogger.Fatal("Failed to create matchmaker index", zap.Error(err)) } - return &LocalMatchmaker{ - node: node, - entries: make(map[string]*MatchmakerEntry), - index: index, + ctx, ctxCancelFn := context.WithCancel(context.Background()) + + m := &LocalMatchmaker{ + logger: logger, + node: config.GetName(), + config: config, + router: router, + runtime: runtime, + + stopped: atomic.NewBool(false), + ctx: ctx, + ctxCancelFn: ctxCancelFn, + + index: index, + sessionTickets: make(map[string]map[string]struct{}), + entries: make(map[string][]*MatchmakerEntry), + indexes: make(map[string]*MatchmakerIndex), + activeIndexes: make(map[string]*MatchmakerIndex), + } + + go func() { + ticker := time.NewTicker(time.Duration(config.GetMatchmaker().IntervalSec) * time.Second) + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + m.process() + } + } + }() + + return m +} + +func (m *LocalMatchmaker) Stop() { + m.stopped.Store(true) + m.ctxCancelFn() +} + +func (m *LocalMatchmaker) process() { + matchedEntries := make([][]*MatchmakerEntry, 0, 5) + + m.Lock() + + // No active matchmaking tickets, the pool may be non-empty but there are no new tickets to check/query with. + if len(m.activeIndexes) == 0 { + m.Unlock() + return + } + + for ticket, index := range m.activeIndexes { + index.Intervals++ + lastInterval := index.Intervals > m.config.GetMatchmaker().MaxIntervals || index.MinCount == index.MaxCount + if lastInterval { + // Drop from active indexes if it has reached its max intervals, or if its min/max counts are equal. In the + // latter case keeping it active would have the same result as leaving it in the pool, so this saves work. + delete(m.activeIndexes, ticket) + } + + indexQuery := bleve.NewBooleanQuery() + // Results must match the query string. + indexQuery.AddMust(bleve.NewQueryStringQuery(index.Query)) + // Results must also have compatible min/max ranges, for example 2-4 must not match with 6-8. + indexQuery.AddMust(bleve.NewQueryStringQuery(fmt.Sprintf("+min_count:<=%d +max_count:>=%d", index.MaxCount, index.MinCount))) + // Results must not include the current ticket. + ticketQuery := bleve.NewTermQuery(ticket) + ticketQuery.SetField("ticket") + indexQuery.AddMustNot(ticketQuery) + // Results must not include the current party, if any. + if index.PartyId != "" { + partyIdQuery := bleve.NewTermQuery(index.PartyId) + partyIdQuery.SetField("party_id") + indexQuery.AddMustNot(partyIdQuery) + } + + searchRequest := bleve.NewSearchRequestOptions(indexQuery, len(m.indexes), 0, false) + // Sort indexes to try and select the longest waiting tickets first. + searchRequest.SortBy([]string{"created_at"}) + result, err := m.index.SearchInContext(m.ctx, searchRequest) + if err != nil { + m.logger.Error("error searching index", zap.Error(err)) + continue + } + + // Form possible combinations, in case multiple matches might be suitable. + entryCombos := make([][]*MatchmakerEntry, 0, 5) + for _, hit := range result.Hits { + if hit.ID == ticket { + // Skip the current ticket. + continue + } + + hitIndex, ok := m.indexes[hit.ID] + if !ok { + // Ticket did not exist, should not happen. + m.logger.Warn("matchmaker process missing index", zap.String("ticket", hit.ID)) + continue + } + + if index.MaxCount < hitIndex.MaxCount && hitIndex.Intervals <= m.config.GetMatchmaker().MaxIntervals { + // This match would be less than the search hit's preferred max, and they can still wait. Let them wait more. + continue + } + + // Check if there are overlapping session IDs, and if so these tickets are ineligible to match together. + var sessionIdConflict bool + for sessionID := range index.SessionIDs { + if _, found := hitIndex.SessionIDs[sessionID]; found { + sessionIdConflict = true + break + } + } + if sessionIdConflict { + continue + } + + entries, ok := m.entries[hit.ID] + if !ok { + // Ticket did not exist, should not happen. + m.logger.Warn("matchmaker process missing entries", zap.String("ticket", hit.ID)) + continue + } + + var foundCombo []*MatchmakerEntry + for _, entryCombo := range entryCombos { + if len(entryCombo)+len(entries)+index.Count <= index.MaxCount { + // There is room in this combo for these entries. Check if there are session ID conflicts with current combo. + for _, entry := range entryCombo { + if _, found := hitIndex.SessionIDs[entry.Presence.SessionId]; found { + sessionIdConflict = true + break + } + } + if sessionIdConflict { + continue + } + + entryCombo = append(entryCombo, entries...) + foundCombo = entryCombo + break + } + } + if foundCombo == nil { + entryCombo := make([]*MatchmakerEntry, len(entries)) + copy(entryCombo, entries) + entryCombos = append(entryCombos, entryCombo) + foundCombo = entryCombo + } + + if l := len(foundCombo) + index.Count; l == index.MaxCount || (lastInterval && l >= index.MinCount) { + // Check that the minimum count that satisfies the current index is also good enough for all matched entries. + var minCountFailed bool + for _, e := range foundCombo { + if foundIndex, ok := m.indexes[e.Ticket]; ok && foundIndex.MinCount > l { + minCountFailed = true + break + } + } + if minCountFailed { + continue + } + + // Found a suitable match. + entries, ok := m.entries[ticket] + if !ok { + // Ticket did not exist, should not happen. + m.logger.Warn("matchmaker process missing entries", zap.String("ticket", hit.ID)) + break + } + currentMatchedEntries := append(foundCombo, entries...) + matchedEntries = append(matchedEntries, currentMatchedEntries) + + // Remove all entries/indexes that have just matched. It must be done here so any following process iterations + // cannot pick up the same tickets to match against. + batch := m.index.NewBatch() + ticketsToDelete := make(map[string]struct{}, len(currentMatchedEntries)) + for _, entry := range currentMatchedEntries { + if _, ok := ticketsToDelete[entry.Ticket]; !ok { + batch.Delete(entry.Ticket) + ticketsToDelete[entry.Ticket] = struct{}{} + } + delete(m.entries, entry.Ticket) + delete(m.indexes, entry.Ticket) + delete(m.activeIndexes, entry.Ticket) + sessionTickets, ok := m.sessionTickets[entry.Presence.SessionId] + if !ok { + continue + } + if l := len(sessionTickets); l <= 1 { + delete(m.sessionTickets, entry.Presence.SessionId) + } else { + delete(sessionTickets, ticket) + } + } + if err := m.index.Batch(batch); err != nil { + m.logger.Error("error deleting matchmaker process entries batch", zap.Error(err)) + } + + break + } + } + } + + m.Unlock() + + for _, entries := range matchedEntries { + var tokenOrMatchID string + var isMatchID bool + var err error + + // Check if there's a matchmaker matched runtime callback, call it, and see if it returns a match ID. + fn := m.runtime.MatchmakerMatched() + if fn != nil { + tokenOrMatchID, isMatchID, err = fn(context.Background(), entries) + if err != nil { + m.logger.Error("Error running Matchmaker Matched hook.", zap.Error(err)) + } + } + + if !isMatchID { + // If there was no callback or it didn't return a valid match ID always return at least a token. + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "mid": fmt.Sprintf("%v.", uuid.Must(uuid.NewV4()).String()), + "exp": time.Now().UTC().Add(30 * time.Second).Unix(), + }) + tokenOrMatchID, _ = token.SignedString([]byte(m.config.GetSession().EncryptionKey)) + } + + users := make([]*rtapi.MatchmakerMatched_MatchmakerUser, 0, len(entries)) + for _, entry := range entries { + users = append(users, &rtapi.MatchmakerMatched_MatchmakerUser{ + Presence: &rtapi.UserPresence{ + UserId: entry.Presence.UserId, + SessionId: entry.Presence.SessionId, + Username: entry.Presence.Username, + }, + StringProperties: entry.StringProperties, + NumericProperties: entry.NumericProperties, + PartyId: entry.PartyId, + }) + } + outgoing := &rtapi.Envelope{Message: &rtapi.Envelope_MatchmakerMatched{MatchmakerMatched: &rtapi.MatchmakerMatched{ + // Ticket is set individually below for each recipient. + // Id set below to account for token or match ID case. + Users: users, + // Self is set individually below for each recipient. + }}} + if isMatchID { + outgoing.GetMatchmakerMatched().Id = &rtapi.MatchmakerMatched_MatchId{MatchId: tokenOrMatchID} + } else { + outgoing.GetMatchmakerMatched().Id = &rtapi.MatchmakerMatched_Token{Token: tokenOrMatchID} + } + + for i, entry := range entries { + // Set per-recipient fields. + outgoing.GetMatchmakerMatched().Self = users[i] + outgoing.GetMatchmakerMatched().Ticket = entry.Ticket + + // Route outgoing message. + m.router.SendToPresenceIDs(m.logger, []*PresenceID{{Node: entry.Presence.Node, SessionID: entry.Presence.SessionID}}, outgoing, true) + } } } -func (m *LocalMatchmaker) Add(session Session, query string, minCount int, maxCount int, stringProperties map[string]string, numericProperties map[string]float64) (string, []*MatchmakerEntry, error) { +func (m *LocalMatchmaker) Add(sessionID string, presences []*MatchmakerPresence, partyId, query string, minCount, maxCount int, stringProperties map[string]string, numericProperties map[string]float64) (string, error) { + // Check if the matchmaker has been stopped. + if m.stopped.Load() { + return "", ErrMatchmakerNotAvailable + } + + if bleve.NewQueryStringQuery(query).Validate() != nil { + return "", ErrMatchmakerQueryInvalid + } + // Merge incoming properties. properties := make(map[string]interface{}, len(stringProperties)+len(numericProperties)) for k, v := range stringProperties { @@ -115,140 +428,166 @@ func (m *LocalMatchmaker) Add(session Session, query string, minCount int, maxCo for k, v := range numericProperties { properties[k] = v } - - filterQuery := bleve.NewTermQuery(session.ID().String()) - filterQuery.SetField("presence.session_id") - indexQuery := bleve.NewBooleanQuery() - indexQuery.AddMust(bleve.NewQueryStringQuery(query)) - indexQuery.AddMustNot(filterQuery) - - searchRequest := bleve.NewSearchRequestOptions(indexQuery, maxCount-1, 0, false) - + // Generate a ticket ID. ticket := uuid.Must(uuid.NewV4()).String() - entry := &MatchmakerEntry{ - Ticket: ticket, - Presence: &MatchmakerPresence{ - UserId: session.UserID().String(), - SessionId: session.ID().String(), - Username: session.Username(), - Node: m.node, - }, - Properties: properties, - StringProperties: stringProperties, - NumericProperties: numericProperties, - SessionID: session.ID(), + // Unique session IDs. + sessionIDs := make(map[string]struct{}, len(presences)) + for _, presence := range presences { + if _, found := sessionIDs[presence.SessionId]; found { + return "", ErrMatchmakerDuplicateSession + } + sessionIDs[presence.SessionId] = struct{}{} + } + // Prepare index data. + index := &MatchmakerIndex{ + Ticket: ticket, + Properties: properties, + MinCount: minCount, + MaxCount: maxCount, + PartyId: partyId, + CreatedAt: time.Now().UTC().UnixNano(), + + Query: query, + Count: len(presences), + SessionID: sessionID, + Intervals: 0, + SessionIDs: sessionIDs, } m.Lock() - result, err := m.index.SearchInContext(session.Context(), searchRequest) - if err != nil { - m.Unlock() - return ticket, nil, err - } - // Check if we have enough results to return them, or if we just add a new entry to the matchmaker. - resultCount := result.Hits.Len() - if resultCount < minCount-1 { - if err := m.index.Index(ticket, entry); err != nil { + // Check if all presences are allowed to create more tickets. + for _, presence := range presences { + if existingTickets := m.sessionTickets[presence.SessionId]; len(existingTickets) >= m.config.GetMatchmaker().MaxTickets { m.Unlock() - return ticket, nil, err + return "", ErrMatchmakerTooManyTickets } - m.entries[ticket] = entry + } + if err := m.index.Index(ticket, index); err != nil { m.Unlock() - return ticket, nil, nil + m.logger.Error("error indexing matchmaker entries", zap.Error(err)) + return "", ErrMatchmakerIndex } - // We have enough entries to satisfy the request. - entries := make([]*MatchmakerEntry, 0, resultCount+1) - tickets := make([]string, 0, resultCount) - batch := m.index.NewBatch() - for _, hit := range result.Hits { - entry, ok := m.entries[hit.ID] - if !ok { - // Index and entries map are out of sync, should not happen but check to be sure. - m.Unlock() - return ticket, nil, ErrMatchmakerTicketNotFound + entries := make([]*MatchmakerEntry, 0, len(presences)) + for _, presence := range presences { + if _, ok := m.sessionTickets[presence.SessionId]; ok { + m.sessionTickets[presence.SessionId][ticket] = struct{}{} + } else { + m.sessionTickets[presence.SessionId] = map[string]struct{}{ticket: {}} } - entries = append(entries, entry) - tickets = append(tickets, hit.ID) - batch.Delete(hit.ID) - } - - // Only remove the entries after we've processed each one to make sure - // there were no sync issues between the index and the entries map. - if err := m.index.Batch(batch); err != nil { - m.Unlock() - return ticket, nil, err - } - for _, ticket := range tickets { - delete(m.entries, ticket) + entries = append(entries, &MatchmakerEntry{ + Ticket: ticket, + Presence: presence, + Properties: properties, + PartyId: partyId, + StringProperties: stringProperties, + NumericProperties: numericProperties, + }) } + m.entries[ticket] = entries + m.indexes[ticket] = index + m.activeIndexes[ticket] = index m.Unlock() - - // Add the current user. - entries = append(entries, entry) - - return ticket, entries, nil + return ticket, nil } -func (m *LocalMatchmaker) Remove(sessionID uuid.UUID, ticket string) error { +func (m *LocalMatchmaker) Remove(sessionID, ticket string) error { m.Lock() - if entry, ok := m.entries[ticket]; !ok || entry.Presence.SessionId != sessionID.String() { - // Ticket does not exist or does not belong to this session. + index, ok := m.indexes[ticket] + if !ok || index.SessionID != sessionID { + // Ticket did not exist, or the caller was not the ticket owner. m.Unlock() return ErrMatchmakerTicketNotFound } + delete(m.indexes, ticket) + + entries, ok := m.entries[ticket] + if !ok { + m.logger.Warn("matchmaker remove found ticket with no entries", zap.String("ticket", ticket)) + } + delete(m.entries, ticket) + + for _, entry := range entries { + sessionTickets, ok := m.sessionTickets[entry.Presence.SessionId] + if !ok { + continue + } + if l := len(sessionTickets); l <= 1 { + delete(m.sessionTickets, entry.Presence.SessionId) + } else { + delete(sessionTickets, ticket) + } + } + + delete(m.activeIndexes, ticket) + if err := m.index.Delete(ticket); err != nil { m.Unlock() - return err + m.logger.Error("error deleting matchmaker entries", zap.Error(err)) + return ErrMatchmakerDelete } - delete(m.entries, ticket) m.Unlock() return nil } -func (m *LocalMatchmaker) RemoveAll(sessionID uuid.UUID) error { - query := bleve.NewMatchQuery(sessionID.String()) - query.SetField("presence.session_id") - queuedRemoves := 0 - batch := m.index.NewBatch() - tickets := make([]string, 0, 10) - +func (m *LocalMatchmaker) RemoveAll(sessionID string) error { m.Lock() - // Look up and accumulate all required removes to be executed as a batch later. - for { - // Load a set of matchmaker entries for the given session. - search := bleve.NewSearchRequestOptions(query, 10, queuedRemoves, false) - result, err := m.index.Search(search) - if err != nil { - m.Unlock() - return err + sessionTickets, ok := m.sessionTickets[sessionID] + if !ok { + // Session does not have any active matchmaking tickets. + m.Unlock() + return nil + } + delete(m.sessionTickets, sessionID) + + batch := m.index.NewBatch() + for ticket := range sessionTickets { + batch.Delete(ticket) + + _, ok := m.indexes[ticket] + if !ok { + // Ticket did not exist, should not happen. + m.logger.Warn("matchmaker remove all found ticket with no index", zap.String("ticket", ticket)) + continue } - // Queue each hit up to be removed. - for _, hit := range result.Hits { - batch.Delete(hit.ID) - tickets = append(tickets, hit.ID) - queuedRemoves++ + delete(m.indexes, ticket) + + delete(m.activeIndexes, ticket) + + entries, ok := m.entries[ticket] + if !ok { + m.logger.Warn("matchmaker remove all found ticket with no entries", zap.String("ticket", ticket)) } - // Check if we've accumulated all available hits. - if result.Hits.Len() == 0 || uint64(queuedRemoves) >= result.Total { - break + delete(m.entries, ticket) + + for _, entry := range entries { + if entry.Presence.SessionId == sessionID { + // Already deleted above. + continue + } + sessionTickets, ok := m.sessionTickets[entry.Presence.SessionId] + if !ok { + continue + } + if l := len(sessionTickets); l <= 1 { + delete(m.sessionTickets, entry.Presence.SessionId) + } else { + delete(sessionTickets, ticket) + } } } - // Execute the batch and delete from the entries map, if any removes are present. - if queuedRemoves > 0 { + if batch.Size() > 0 { if err := m.index.Batch(batch); err != nil { m.Unlock() - return err - } - for _, ticket := range tickets { - delete(m.entries, ticket) + m.logger.Error("error deleting matchmaker entries batch", zap.Error(err)) + return ErrMatchmakerDelete } } diff --git a/server/pipeline_matchmaker.go b/server/pipeline_matchmaker.go index 7d45a3cf4..22a0b7a0d 100644 --- a/server/pipeline_matchmaker.go +++ b/server/pipeline_matchmaker.go @@ -15,12 +15,6 @@ package server import ( - "context" - "fmt" - "time" - - "github.com/dgrijalva/jwt-go" - "github.com/gofrs/uuid" "github.com/heroiclabs/nakama-common/rtapi" "go.uber.org/zap" ) @@ -53,8 +47,16 @@ func (p *Pipeline) matchmakerAdd(logger *zap.Logger, session Session, envelope * query = "*" } + presences := []*MatchmakerPresence{{ + UserId: session.UserID().String(), + SessionId: session.ID().String(), + Username: session.Username(), + Node: p.node, + SessionID: session.ID(), + }} + // Run matchmaker add. - ticket, entries, err := p.matchmaker.Add(session, query, minCount, maxCount, incoming.StringProperties, incoming.NumericProperties) + ticket, err := p.matchmaker.Add(session.ID().String(), presences, "", query, minCount, maxCount, incoming.StringProperties, incoming.NumericProperties) if err != nil { logger.Error("Error adding to matchmaker", zap.Error(err)) session.Send(&rtapi.Envelope{Cid: envelope.Cid, Message: &rtapi.Envelope_Error{Error: &rtapi.Error{ @@ -68,65 +70,6 @@ func (p *Pipeline) matchmakerAdd(logger *zap.Logger, session Session, envelope * session.Send(&rtapi.Envelope{Cid: envelope.Cid, Message: &rtapi.Envelope_MatchmakerTicket{MatchmakerTicket: &rtapi.MatchmakerTicket{ Ticket: ticket, }}}, true) - - if entries == nil { - // Matchmaking was unsuccessful, no further messages to send out. - return - } - - var tokenOrMatchID string - var isMatchID bool - - // Check if there's a matchmaker matched runtime callback, call it, and see if it returns a match ID. - fn := p.runtime.MatchmakerMatched() - if fn != nil { - tokenOrMatchID, isMatchID, err = fn(context.Background(), entries) - if err != nil { - p.logger.Error("Error running Matchmaker Matched hook.", zap.Error(err)) - } - } - - if !isMatchID { - // If there was no callback or it didn't return a valid match ID always return at least a token. - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "mid": fmt.Sprintf("%v.", uuid.Must(uuid.NewV4()).String()), - "exp": time.Now().UTC().Add(30 * time.Second).Unix(), - }) - tokenOrMatchID, _ = token.SignedString([]byte(p.config.GetSession().EncryptionKey)) - } - - users := make([]*rtapi.MatchmakerMatched_MatchmakerUser, 0, len(entries)) - for _, entry := range entries { - users = append(users, &rtapi.MatchmakerMatched_MatchmakerUser{ - Presence: &rtapi.UserPresence{ - UserId: entry.Presence.UserId, - SessionId: entry.Presence.SessionId, - Username: entry.Presence.Username, - }, - StringProperties: entry.StringProperties, - NumericProperties: entry.NumericProperties, - }) - } - outgoing := &rtapi.Envelope{Message: &rtapi.Envelope_MatchmakerMatched{MatchmakerMatched: &rtapi.MatchmakerMatched{ - // Ticket is set individually below for each recipient. - // Id set below to account for token or match ID case. - Users: users, - // Self is set individually below for each recipient. - }}} - if isMatchID { - outgoing.GetMatchmakerMatched().Id = &rtapi.MatchmakerMatched_MatchId{MatchId: tokenOrMatchID} - } else { - outgoing.GetMatchmakerMatched().Id = &rtapi.MatchmakerMatched_Token{Token: tokenOrMatchID} - } - - for i, entry := range entries { - // Set per-recipient fields. - outgoing.GetMatchmakerMatched().Self = users[i] - outgoing.GetMatchmakerMatched().Ticket = entry.Ticket - - // Route outgoing message. - p.router.SendToPresenceIDs(logger, []*PresenceID{{Node: entry.Presence.Node, SessionID: entry.SessionID}}, outgoing, true) - } } func (p *Pipeline) matchmakerRemove(logger *zap.Logger, session Session, envelope *rtapi.Envelope) { @@ -142,7 +85,7 @@ func (p *Pipeline) matchmakerRemove(logger *zap.Logger, session Session, envelop } // Run matchmaker remove. - if err := p.matchmaker.Remove(session.ID(), incoming.Ticket); err != nil { + if err := p.matchmaker.Remove(session.ID().String(), incoming.Ticket); err != nil { if err == ErrMatchmakerTicketNotFound { session.Send(&rtapi.Envelope{Cid: envelope.Cid, Message: &rtapi.Envelope_Error{Error: &rtapi.Error{ Code: int32(rtapi.Error_BAD_INPUT), diff --git a/server/session_ws.go b/server/session_ws.go index 564a37f37..d79899555 100644 --- a/server/session_ws.go +++ b/server/session_ws.go @@ -434,7 +434,7 @@ func (s *sessionWS) Close(reason string) { } // When connection close originates internally in the session, ensure cleanup of external resources and references. - if err := s.matchmaker.RemoveAll(s.id); err != nil { + if err := s.matchmaker.RemoveAll(s.id.String()); err != nil { s.logger.Warn("Failed to remove all matchmaking tickets", zap.Error(err)) } if s.logger.Core().Enabled(zap.DebugLevel) { diff --git a/vendor/github.com/heroiclabs/nakama-common/api/api.proto b/vendor/github.com/heroiclabs/nakama-common/api/api.proto index 43b33ec37..37a6d48e0 100644 --- a/vendor/github.com/heroiclabs/nakama-common/api/api.proto +++ b/vendor/github.com/heroiclabs/nakama-common/api/api.proto @@ -30,8 +30,6 @@ option java_package = "com.heroiclabs.nakama.api"; option csharp_namespace = "Nakama.Protobuf"; -option objc_class_prefix = "NKPB"; - // A user with additional account details. Always the current user. message Account { // The user object. diff --git a/vendor/github.com/heroiclabs/nakama-common/rtapi/realtime.pb.go b/vendor/github.com/heroiclabs/nakama-common/rtapi/realtime.pb.go index 68b3bb325..959673b62 100644 --- a/vendor/github.com/heroiclabs/nakama-common/rtapi/realtime.pb.go +++ b/vendor/github.com/heroiclabs/nakama-common/rtapi/realtime.pb.go @@ -2943,6 +2943,8 @@ type MatchmakerMatched_MatchmakerUser struct { // User info. Presence *UserPresence `protobuf:"bytes,1,opt,name=presence,proto3" json:"presence,omitempty"` + // Party identifier, if this user was matched as a party member. + PartyId string `protobuf:"bytes,2,opt,name=party_id,json=partyId,proto3" json:"party_id,omitempty"` // String properties. StringProperties map[string]string `protobuf:"bytes,5,rep,name=string_properties,json=stringProperties,proto3" json:"string_properties,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // Numeric properties. @@ -2988,6 +2990,13 @@ func (x *MatchmakerMatched_MatchmakerUser) GetPresence() *UserPresence { return nil } +func (x *MatchmakerMatched_MatchmakerUser) GetPartyId() string { + if x != nil { + return x.PartyId + } + return "" +} + func (x *MatchmakerMatched_MatchmakerUser) GetStringProperties() map[string]string { if x != nil { return x.StringProperties @@ -3370,7 +3379,7 @@ var file_realtime_proto_rawDesc = []byte{ 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xbe, 0x05, 0x0a, 0x11, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd9, 0x05, 0x0a, 0x11, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x6d, 0x61, 0x6b, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x08, 0x6d, 0x61, 0x74, @@ -3385,120 +3394,122 @@ var file_realtime_proto_rawDesc = []byte{ 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x6d, 0x61, 0x6b, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x6d, - 0x61, 0x6b, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x73, 0x65, 0x6c, 0x66, 0x1a, 0xc5, + 0x61, 0x6b, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x73, 0x65, 0x6c, 0x66, 0x1a, 0xe0, 0x03, 0x0a, 0x0e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x6d, 0x61, 0x6b, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, - 0x63, 0x65, 0x52, 0x08, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x74, 0x0a, 0x11, - 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, - 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x47, 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, - 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x6d, - 0x61, 0x6b, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x2e, 0x4d, 0x61, 0x74, 0x63, - 0x68, 0x6d, 0x61, 0x6b, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, - 0x67, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x10, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, - 0x65, 0x73, 0x12, 0x77, 0x0a, 0x12, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x70, 0x72, - 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x48, - 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, - 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x6d, 0x61, 0x6b, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, - 0x65, 0x64, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x6d, 0x61, 0x6b, 0x65, 0x72, 0x55, 0x73, 0x65, - 0x72, 0x2e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, - 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, - 0x63, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x1a, 0x43, 0x0a, 0x15, 0x53, - 0x74, 0x72, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x1a, 0x44, 0x0a, 0x16, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x50, 0x72, 0x6f, 0x70, 0x65, - 0x72, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x04, 0x0a, 0x02, 0x69, 0x64, 0x22, 0x2a, 0x0a, 0x10, - 0x4d, 0x61, 0x74, 0x63, 0x68, 0x6d, 0x61, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, - 0x12, 0x16, 0x0a, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x22, 0x2a, 0x0a, 0x10, 0x4d, 0x61, 0x74, 0x63, - 0x68, 0x6d, 0x61, 0x6b, 0x65, 0x72, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x69, - 0x63, 0x6b, 0x65, 0x74, 0x22, 0x4f, 0x0a, 0x0d, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3e, 0x0a, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6e, - 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x06, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x22, 0x06, 0x0a, - 0x04, 0x50, 0x6f, 0x6e, 0x67, 0x22, 0x45, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x3b, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x6c, - 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, - 0x65, 0x52, 0x09, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x47, 0x0a, 0x0c, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x12, 0x19, 0x0a, 0x08, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x81, 0x01, 0x0a, 0x13, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x33, 0x0a, - 0x05, 0x6a, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6e, - 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x05, 0x6a, 0x6f, 0x69, - 0x6e, 0x73, 0x12, 0x35, 0x0a, 0x06, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x6c, - 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, - 0x65, 0x52, 0x06, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x22, 0x2b, 0x0a, 0x0e, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x55, 0x6e, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x12, 0x19, 0x0a, 0x08, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x75, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, 0x44, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x6c, 0x0a, 0x06, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x63, 0x6f, 0x6e, 0x74, 0x65, - 0x78, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x63, 0x6f, 0x6e, - 0x74, 0x65, 0x78, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0xa4, 0x01, 0x0a, 0x0a, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6e, 0x61, 0x6b, 0x61, - 0x6d, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x52, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x35, 0x0a, 0x06, 0x73, 0x65, - 0x6e, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6e, 0x61, 0x6b, + 0x63, 0x65, 0x52, 0x08, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, + 0x70, 0x61, 0x72, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x70, 0x61, 0x72, 0x74, 0x79, 0x49, 0x64, 0x12, 0x74, 0x0a, 0x11, 0x73, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x47, 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x6c, + 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x6d, 0x61, 0x6b, 0x65, 0x72, 0x4d, + 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x6d, 0x61, 0x6b, 0x65, + 0x72, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, 0x70, + 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x10, 0x73, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x12, 0x77, 0x0a, + 0x12, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, + 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x48, 0x2e, 0x6e, 0x61, 0x6b, 0x61, + 0x6d, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x4d, 0x61, 0x74, 0x63, + 0x68, 0x6d, 0x61, 0x6b, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x2e, 0x4d, 0x61, + 0x74, 0x63, 0x68, 0x6d, 0x61, 0x6b, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x4e, 0x75, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x50, 0x72, 0x6f, 0x70, + 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x1a, 0x43, 0x0a, 0x15, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x44, 0x0a, 0x16, 0x4e, + 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x42, 0x04, 0x0a, 0x02, 0x69, 0x64, 0x22, 0x2a, 0x0a, 0x10, 0x4d, 0x61, 0x74, 0x63, 0x68, + 0x6d, 0x61, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, + 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x69, 0x63, + 0x6b, 0x65, 0x74, 0x22, 0x2a, 0x0a, 0x10, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x6d, 0x61, 0x6b, 0x65, + 0x72, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x22, + 0x4f, 0x0a, 0x0d, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x3e, 0x0a, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x22, 0x06, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x22, 0x06, 0x0a, 0x04, 0x50, 0x6f, 0x6e, 0x67, + 0x22, 0x45, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x3b, 0x0a, 0x09, 0x70, 0x72, + 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, + 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x09, 0x70, 0x72, + 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x47, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x46, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x73, + 0x22, 0x81, 0x01, 0x0a, 0x13, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x50, 0x72, 0x65, 0x73, 0x65, + 0x6e, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x05, 0x6a, 0x6f, 0x69, 0x6e, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, + 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, + 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x05, 0x6a, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x35, 0x0a, + 0x06, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, + 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x06, 0x6c, 0x65, + 0x61, 0x76, 0x65, 0x73, 0x22, 0x2b, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x6e, + 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, + 0x73, 0x22, 0x44, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x12, 0x34, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x6c, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, + 0x1e, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, + 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0xa4, 0x01, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x44, 0x61, 0x74, 0x61, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, 0x65, + 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x06, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x35, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, + 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x73, + 0x65, 0x6e, 0x63, 0x65, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, + 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, + 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x22, 0xb2, 0x01, 0x0a, + 0x13, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, 0x65, + 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x06, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x33, 0x0a, 0x05, 0x6a, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, 0x65, + 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x73, 0x65, + 0x6e, 0x63, 0x65, 0x52, 0x05, 0x6a, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x35, 0x0a, 0x06, 0x6c, 0x65, + 0x61, 0x76, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x55, 0x73, 0x65, - 0x72, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, - 0x72, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x69, 0x61, 0x62, 0x6c, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x69, 0x61, 0x62, 0x6c, - 0x65, 0x22, 0xb2, 0x01, 0x0a, 0x13, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x50, 0x72, 0x65, 0x73, - 0x65, 0x6e, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6e, 0x61, 0x6b, 0x61, - 0x6d, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x52, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x33, 0x0a, 0x05, 0x6a, 0x6f, - 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6e, 0x61, 0x6b, 0x61, - 0x6d, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, - 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x05, 0x6a, 0x6f, 0x69, 0x6e, 0x73, 0x12, - 0x35, 0x0a, 0x06, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1d, 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, - 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x06, - 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x22, 0xba, 0x01, 0x0a, 0x0c, 0x55, 0x73, 0x65, 0x72, 0x50, - 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, - 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, - 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0b, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x34, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x42, 0x6a, 0x0a, 0x1b, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x65, 0x72, 0x6f, 0x69, - 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, 0x74, 0x61, - 0x70, 0x69, 0x42, 0x0e, 0x4e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x52, 0x65, 0x61, 0x6c, 0x74, 0x69, - 0x6d, 0x65, 0x50, 0x01, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x68, 0x65, 0x72, 0x6f, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6e, 0x61, 0x6b, 0x61, - 0x6d, 0x61, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x72, 0x74, 0x61, 0x70, 0x69, 0xa2, - 0x02, 0x04, 0x4e, 0x4b, 0x50, 0x42, 0xaa, 0x02, 0x06, 0x4e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x06, 0x6c, 0x65, 0x61, 0x76, 0x65, + 0x73, 0x22, 0xba, 0x01, 0x0a, 0x0c, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, + 0x63, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, + 0x74, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x70, 0x65, 0x72, + 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x73, + 0x0a, 0x1b, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x65, 0x72, 0x6f, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, + 0x2e, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x72, 0x74, 0x61, 0x70, 0x69, 0x42, 0x0e, 0x4e, + 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x52, 0x65, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x50, 0x01, 0x5a, + 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x65, 0x72, 0x6f, + 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2d, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x72, 0x74, 0x61, 0x70, 0x69, 0xa2, 0x02, 0x04, 0x4e, 0x4b, 0x50, + 0x42, 0xaa, 0x02, 0x0f, 0x4e, 0x61, 0x6b, 0x61, 0x6d, 0x61, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/vendor/github.com/heroiclabs/nakama-common/rtapi/realtime.proto b/vendor/github.com/heroiclabs/nakama-common/rtapi/realtime.proto index a4859caff..c0bd3a528 100644 --- a/vendor/github.com/heroiclabs/nakama-common/rtapi/realtime.proto +++ b/vendor/github.com/heroiclabs/nakama-common/rtapi/realtime.proto @@ -31,8 +31,6 @@ option java_package = "com.heroiclabs.nakama.rtapi"; option csharp_namespace = "Nakama.Protobuf"; -option objc_class_prefix = "NKPB"; - // An envelope for a realtime message. message Envelope { string cid = 1; @@ -346,6 +344,8 @@ message MatchmakerMatched { message MatchmakerUser { // User info. UserPresence presence = 1; + // Party identifier, if this user was matched as a party member. + string party_id = 2; // String properties. map string_properties = 5; // Numeric properties. diff --git a/vendor/github.com/heroiclabs/nakama-common/runtime/runtime.go b/vendor/github.com/heroiclabs/nakama-common/runtime/runtime.go index c68b900b7..c223b1bb3 100644 --- a/vendor/github.com/heroiclabs/nakama-common/runtime/runtime.go +++ b/vendor/github.com/heroiclabs/nakama-common/runtime/runtime.go @@ -701,6 +701,7 @@ type MatchmakerEntry interface { GetPresence() Presence GetTicket() string GetProperties() map[string]interface{} + GetPartyId() string } type MatchData interface { diff --git a/vendor/modules.txt b/vendor/modules.txt index 477d570b5..21c160b79 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -135,7 +135,7 @@ github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/internal/genopena github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options github.com/grpc-ecosystem/grpc-gateway/v2/runtime github.com/grpc-ecosystem/grpc-gateway/v2/utilities -# github.com/heroiclabs/nakama-common v1.10.1-0.20201216142705-136e6c0d214f +# github.com/heroiclabs/nakama-common v1.10.1-0.20210112124918-da701a0f7020 github.com/heroiclabs/nakama-common/api github.com/heroiclabs/nakama-common/rtapi github.com/heroiclabs/nakama-common/runtime -- GitLab