Loading CHANGELOG.md +1 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr - Add Groups page and associated endpoints to the developer console. - Add NotificationSendAll function to the runtimes, for sending a notification to all users. - Log a warning when client IP address cannot be resolved. - Add matchmaker option to enforce a multiple of resulting matched count. - Add tagged Prometheus stats containing RPC function identifiers. ### Changed Loading server/matchmaker.go +109 −31 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ import ( "context" "fmt" "math" "sort" "sync" "time" Loading Loading @@ -98,14 +99,57 @@ type MatchmakerIndex struct { // Parameters used for correctly processing various matchmaker operations, but not indexed for searching. Query string `json:"-"` Count int `json:"-"` CountMultiple int `json:"-"` SessionID string `json:"-"` Intervals int `json:"-"` SessionIDs map[string]struct{} `json:"-"` } type MatchmakerIndexGroup struct { indexes []*MatchmakerIndex avgCreatedAt int64 } func groupIndexes(indexes []*MatchmakerIndex, required int) []*MatchmakerIndexGroup { if len(indexes) == 0 || required == 0 { return nil } var results []*MatchmakerIndexGroup for i := 0; i < len(indexes); i++ { // Grab index combination not including the current index. current, before, after := indexes[i], indexes[:i], indexes[i+1:] others := make([]*MatchmakerIndex, len(before)+len(after)) copy(others, before) copy(others[len(before):], after) if current.Count == required { // 1. The current index by itself satisfies the requirement. results = append(results, &MatchmakerIndexGroup{ indexes: []*MatchmakerIndex{current}, avgCreatedAt: current.CreatedAt, }) } else { // 2. The current index plus some combination(s) of the others. fillResults := groupIndexes(others, required-current.Count) for _, fillResult := range fillResults { indexesCount := int64(len(fillResult.indexes)) fillResult.avgCreatedAt = (fillResult.avgCreatedAt*indexesCount + current.CreatedAt) / (indexesCount + 1) fillResult.indexes = append(fillResult.indexes, current) results = append(results, fillResult) } } // 3. Other combinations not including the current index. results = append(results, groupIndexes(others, required)...) } return results } type Matchmaker interface { Stop() Add(presences []*MatchmakerPresence, sessionID, partyId, query string, minCount, maxCount int, stringProperties map[string]string, numericProperties map[string]float64) (string, error) Add(presences []*MatchmakerPresence, sessionID, partyId, query string, minCount, maxCount, countMultiple int, stringProperties map[string]string, numericProperties map[string]float64) (string, error) RemoveSession(sessionID, ticket string) error RemoveSessionAll(sessionID string) error RemoveParty(partyID, ticket string) error Loading Loading @@ -260,6 +304,7 @@ func (m *LocalMatchmaker) process(batch *index.Batch) { // Form possible combinations, in case multiple matches might be suitable. entryCombos := make([][]*MatchmakerEntry, 0, 5) lastHitCounter := len(blugeMatches.Hits) - 1 for hitCounter, hit := range blugeMatches.Hits { if hit.ID == ticket { // Skip the current ticket. Loading Loading @@ -324,10 +369,10 @@ func (m *LocalMatchmaker) process(batch *index.Batch) { break } else if !entryMatchesSearchHitQuery { mutualMatchConflict = true // this search hit is not a mutual match with the outer ticket // This search hit is not a mutual match with the outer ticket. break } // MatchmakerEntry's do not have the query, have to dig it back out of indexes // MatchmakerEntry does not have the query,read it out of indexes. if entriesIndexEntry, ok := m.indexes[entry.Ticket]; ok { searchHitMatchesEntryQuery, err := validateMatch(m.ctx, indexReader, entriesIndexEntry.Query, hit.ID) if err != nil { Loading @@ -336,7 +381,7 @@ func (m *LocalMatchmaker) process(batch *index.Batch) { break } else if !searchHitMatchesEntryQuery { mutualMatchConflict = true // this search hit is not a mutual match with the outer ticket // This search hit is not a mutual match with the outer ticket. break } } else { Loading Loading @@ -369,29 +414,61 @@ func (m *LocalMatchmaker) process(batch *index.Batch) { // * It is the last interval for this active index. // * The combo at least satisfies the min count. // * The combo does not exceed the max count. // * There are no further hits that may further fill the found combo, so we get as close as possible to the max count. if l := len(foundCombo) + index.Count; l == index.MaxCount || (lastInterval && l >= index.MinCount && l <= index.MaxCount && hitCounter >= len(blugeMatches.Hits)-1) { // Check that the minimum count that satisfies the current index is also good enough for all matched entries. var minCountFailed bool // * There are no more hits that may further fill the found combo, so we get as close as possible to the max count. if l := len(foundCombo) + index.Count; l == index.MaxCount || (lastInterval && l >= index.MinCount && l <= index.MaxCount && hitCounter >= lastHitCounter) { if rem := l % index.CountMultiple; rem != 0 { // The size of the combination being considered does not satisfy the count multiple. // Attempt to adjust the combo by removing the smallest possible number of entries. // Prefer keeping entries that have been in the matchmaker the longest, if possible. eligibleIndexes := make([]*MatchmakerIndex, 0, len(foundCombo)) for _, e := range foundCombo { if foundIndex, ok := m.indexes[e.Ticket]; ok && foundIndex.MinCount > l { minCountFailed = true // Only tickets individually less <= the removable size are considered. // For example removing a party of 3 when we're only looking to remove 2 is not allowed. if foundIndex, ok := m.indexes[e.Ticket]; ok && foundIndex.Count <= rem { eligibleIndexes = append(eligibleIndexes, foundIndex) } } eligibleGroups := groupIndexes(eligibleIndexes, rem) if len(eligibleGroups) <= 0 { // No possible combination to remove, unlikely but guard. continue } // Sort to ensure we keep as many of the longest-waiting tickets as possible. sort.Slice(eligibleGroups, func(i, j int) bool { return eligibleGroups[i].avgCreatedAt < eligibleGroups[j].avgCreatedAt }) // The most eligible group is removed from the combo. for _, egIndex := range eligibleGroups[0].indexes { for i := 0; i < len(foundCombo); i++ { if egIndex.Ticket == foundCombo[i].Ticket { foundCombo[i] = foundCombo[len(foundCombo)-1] foundCombo[len(foundCombo)-1] = nil foundCombo = foundCombo[:len(foundCombo)-1] break } } if minCountFailed { } if (len(foundCombo)+index.Count)%index.CountMultiple != 0 { // Removal was insufficient, the combo is still not valid for the required multiple. continue } } // Check that the maximum count that satisfies the current index is also good enough for all matched entries. var maxCountFailed bool // Check that ALL of these conditions are true for ALL matched entries: // * The found combo size satisfies the minimum count. // * The found combo size satisfies the maximum count. // * The found combo size satisfies the count multiple. // For any condition failures it does not matter which specific condition is not met. var conditionFailed bool for _, e := range foundCombo { if foundIndex, ok := m.indexes[e.Ticket]; ok && foundIndex.MaxCount < l { maxCountFailed = true if foundIndex, ok := m.indexes[e.Ticket]; ok && (foundIndex.MinCount > l || foundIndex.MaxCount < l || l%foundIndex.CountMultiple != 0) { conditionFailed = true break } } if maxCountFailed { if conditionFailed { continue } Loading Loading @@ -507,7 +584,7 @@ func (m *LocalMatchmaker) process(batch *index.Batch) { } } func (m *LocalMatchmaker) Add(presences []*MatchmakerPresence, sessionID, partyId, query string, minCount, maxCount int, stringProperties map[string]string, numericProperties map[string]float64) (string, error) { func (m *LocalMatchmaker) Add(presences []*MatchmakerPresence, sessionID, partyId, query string, minCount, maxCount, countMultiple int, stringProperties map[string]string, numericProperties map[string]float64) (string, error) { // Check if the matchmaker has been stopped. if m.stopped.Load() { return "", runtime.ErrMatchmakerNotAvailable Loading Loading @@ -552,6 +629,7 @@ func (m *LocalMatchmaker) Add(presences []*MatchmakerPresence, sessionID, partyI Query: query, Count: len(presences), CountMultiple: countMultiple, SessionID: sessionID, Intervals: 0, SessionIDs: sessionIDs, Loading server/matchmaker_test.go +68 −58 Original line number Diff line number Diff line Loading @@ -48,7 +48,7 @@ func TestMatchmakerAddOnly(t *testing.T) { Node: "a", SessionID: sessionID, }, }, sessionID.String(), "", "properties.a1:foo", 2, 2, map[string]string{ }, sessionID.String(), "", "properties.a1:foo", 2, 2, 1, map[string]string{ "a1": "bar", }, map[string]float64{}) if err != nil { Loading Loading @@ -77,7 +77,7 @@ func TestMatchmakerAddAndRemove(t *testing.T) { Node: "a", SessionID: sessionID, }, }, sessionID.String(), "", "properties.a1:foo", 2, 2, map[string]string{ }, sessionID.String(), "", "properties.a1:foo", 2, 2, 1, map[string]string{ "a1": "bar", }, map[string]float64{}) if err != nil { Loading Loading @@ -117,7 +117,7 @@ func TestMatchmakerAddWithBasicMatch(t *testing.T) { Node: "a", SessionID: sessionID, }, }, sessionID.String(), "", "properties.a3:bar", 2, 2, map[string]string{ }, sessionID.String(), "", "properties.a3:bar", 2, 2, 1, map[string]string{ "a3": "baz", }, map[string]float64{}) if err != nil { Loading @@ -136,7 +136,7 @@ func TestMatchmakerAddWithBasicMatch(t *testing.T) { Node: "b", SessionID: sessionID2, }, }, sessionID2.String(), "", "properties.a3:baz", 2, 2, map[string]string{ }, sessionID2.String(), "", "properties.a3:baz", 2, 2, 1, map[string]string{ "a3": "bar", }, map[string]float64{}) if err != nil { Loading @@ -161,7 +161,7 @@ func TestMatchmakerAddWithBasicMatch(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -189,7 +189,7 @@ func TestMatchmakerAddWithBasicMatch(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -231,7 +231,7 @@ func TestMatchmakerAddWithMatchOnStar(t *testing.T) { }, }, sessionID.String(), "", "*", 2, 2, map[string]string{}, 2, 2, 1, map[string]string{}, map[string]float64{ "b1": 15, }) Loading @@ -250,7 +250,7 @@ func TestMatchmakerAddWithMatchOnStar(t *testing.T) { }, }, sessionID2.String(), "", "*", 2, 2, map[string]string{}, 2, 2, 1, map[string]string{}, map[string]float64{ "b1": 15, }) Loading Loading @@ -279,7 +279,7 @@ func TestMatchmakerAddWithMatchOnStar(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -307,7 +307,7 @@ func TestMatchmakerAddWithMatchOnStar(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -349,7 +349,7 @@ func TestMatchmakerAddWithMatchOnRange(t *testing.T) { }, }, sessionID.String(), "", "+properties.b1:>=10 +properties.b1:<=20", 2, 2, map[string]string{}, 2, 2, 1, map[string]string{}, map[string]float64{ "b1": 15, }) Loading @@ -368,7 +368,7 @@ func TestMatchmakerAddWithMatchOnRange(t *testing.T) { }, }, sessionID2.String(), "", "+properties.b1:>=10 +properties.b1:<=20", 2, 2, map[string]string{}, 2, 2, 1, map[string]string{}, map[string]float64{ "b1": 15, }) Loading Loading @@ -397,7 +397,7 @@ func TestMatchmakerAddWithMatchOnRange(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -425,7 +425,7 @@ func TestMatchmakerAddWithMatchOnRange(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -467,7 +467,7 @@ func TestMatchmakerAddWithMatchOnRangeAndValue(t *testing.T) { }, }, sessionID.String(), "", "+properties.b1:>=10 +properties.b1:<=20", 2, 2, 2, 2, 1, map[string]string{ "c2": "foo", }, Loading @@ -492,7 +492,7 @@ func TestMatchmakerAddWithMatchOnRangeAndValue(t *testing.T) { }, }, sessionID2.String(), "", "+properties.c1:>=10 +properties.c1:<=20 +properties.c2:foo", 2, 2, 2, 2, 1, map[string]string{ "c2": "foo", }, Loading Loading @@ -521,7 +521,7 @@ func TestMatchmakerAddWithMatchOnRangeAndValue(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -549,7 +549,7 @@ func TestMatchmakerAddWithMatchOnRangeAndValue(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -594,7 +594,7 @@ func TestMatchmakerAddRemoveNotMatch(t *testing.T) { }, }, sessionID.String(), "", "properties.a3:bar", 2, 2, map[string]string{ 2, 2, 1, map[string]string{ "a3": "baz", }, map[string]float64{}) if err != nil { Loading Loading @@ -642,7 +642,7 @@ func TestMatchmakerAddButNotMatch(t *testing.T) { }, }, sessionID.String(), "", "properties.a5:bar", 2, 2, 2, 2, 1, map[string]string{ "a5": "baz", }, Loading @@ -665,7 +665,7 @@ func TestMatchmakerAddButNotMatch(t *testing.T) { }, }, sessionID2.String(), "", "properties.a5:bar", 2, 2, 2, 2, 1, map[string]string{ "a5": "baz", }, Loading Loading @@ -715,7 +715,7 @@ func TestMatchmakerAddButNotMatchOnRange(t *testing.T) { }, }, sessionID.String(), "", "+properties.b2:>=10 +properties.b2:<=20 +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), }, Loading @@ -740,7 +740,7 @@ func TestMatchmakerAddButNotMatchOnRange(t *testing.T) { }, }, sessionID2.String(), "", "+properties.b2:>=10 +properties.b2:<=20 +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), }, Loading Loading @@ -792,7 +792,7 @@ func TestMatchmakerAddButNotMatchOnRangeAndValue(t *testing.T) { }, }, sessionID.String(), "", "+properties.c3:>=10 +properties.c3:<=20 +properties.c4:foo +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "c4": "foo", Loading @@ -818,7 +818,7 @@ func TestMatchmakerAddButNotMatchOnRangeAndValue(t *testing.T) { }, }, sessionID2.String(), "", "+properties.c3:>=10 +properties.c3:<=20 +properties.c4:foo +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "c4": "foo", Loading Loading @@ -868,7 +868,7 @@ func TestMatchmakerAddMultipleAndSomeMatch(t *testing.T) { }, }, sessionID.String(), "", "properties.a6:bar +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "bar", Loading @@ -892,7 +892,7 @@ func TestMatchmakerAddMultipleAndSomeMatch(t *testing.T) { }, }, sessionID2.String(), "", "properties.a6:bar +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "bar", Loading @@ -916,7 +916,7 @@ func TestMatchmakerAddMultipleAndSomeMatch(t *testing.T) { }, }, sessionID3.String(), "", "properties.a6:bar +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "bar", Loading Loading @@ -971,7 +971,7 @@ func TestMatchmakerAddMultipleAndSomeMatchWithBoost(t *testing.T) { }, }, sessionID.String(), "", "properties.n1:<10^10 properties.a6:bar +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "bar", Loading @@ -997,7 +997,7 @@ func TestMatchmakerAddMultipleAndSomeMatchWithBoost(t *testing.T) { }, }, sessionID2.String(), "", "properties.n1:>10^10 properties.a6:bar +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "bar", Loading @@ -1023,7 +1023,7 @@ func TestMatchmakerAddMultipleAndSomeMatchWithBoost(t *testing.T) { }, }, sessionID3.String(), "", "properties.n1:<10^10 properties.a6:bar +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "bar", Loading Loading @@ -1090,7 +1090,7 @@ func TestMatchmakerAddMultipleAndSomeMatchOptionalTextAlteringScore(t *testing.T }, }, sessionID.String(), "", "properties.a6:bar properties.a6:foo +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "bar", Loading @@ -1114,7 +1114,7 @@ func TestMatchmakerAddMultipleAndSomeMatchOptionalTextAlteringScore(t *testing.T }, }, sessionID2.String(), "", "properties.a6:bar properties.a6:foo +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "foo", Loading @@ -1138,7 +1138,7 @@ func TestMatchmakerAddMultipleAndSomeMatchOptionalTextAlteringScore(t *testing.T }, }, sessionID3.String(), "", "properties.a6:bar properties.a6:foo +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "bar", Loading Loading @@ -1191,7 +1191,8 @@ func TestMatchmakerAddAndMatchAuthoritative(t *testing.T) { }, }, sessionID.String(), "", "properties.d1:foo", 2, 2, map[string]string{ 2, 2, 1, map[string]string{ "d1": "foo", "mode": "authoritative", }, map[string]float64{}) Loading @@ -1213,7 +1214,8 @@ func TestMatchmakerAddAndMatchAuthoritative(t *testing.T) { }, }, sessionID2.String(), "", "properties.d1:foo", 2, 2, map[string]string{ 2, 2, 1, map[string]string{ "d1": "foo", "mode": "authoritative", }, map[string]float64{}) Loading @@ -1239,7 +1241,7 @@ func TestMatchmakerAddAndMatchAuthoritative(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -1267,7 +1269,7 @@ func TestMatchmakerAddAndMatchAuthoritative(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -1391,7 +1393,8 @@ func TestMatchmakerRequireMutualMatch(t *testing.T) { }, }, sessionID.String(), "", "+properties.b1:>=10 +properties.b1:<=20", 2, 2, map[string]string{}, 2, 2, 1, map[string]string{}, map[string]float64{ "b1": 5, }) Loading @@ -1410,7 +1413,8 @@ func TestMatchmakerRequireMutualMatch(t *testing.T) { }, }, sessionID2.String(), "", "+properties.b1:>=10 +properties.b1:<=20", 2, 2, map[string]string{}, 2, 2, 1, map[string]string{}, map[string]float64{ "b1": 15, }) Loading Loading @@ -1472,7 +1476,8 @@ func TestMatchmakerRequireMutualMatchLarger(t *testing.T) { }, }, sessionID.String(), "", "+properties.foo:bar properties.b1:10^10", 3, 3, map[string]string{ 3, 3, 1, map[string]string{ "foo": "bar", }, map[string]float64{ Loading @@ -1493,7 +1498,8 @@ func TestMatchmakerRequireMutualMatchLarger(t *testing.T) { }, }, sessionID2.String(), "", "+properties.foo:bar properties.b1:20^10", 3, 3, map[string]string{ 3, 3, 1, map[string]string{ "foo": "bar", }, map[string]float64{ Loading @@ -1514,7 +1520,8 @@ func TestMatchmakerRequireMutualMatchLarger(t *testing.T) { }, }, sessionID3.String(), "", "+properties.foo:bar +properties.b1:<10", 3, 3, map[string]string{ 3, 3, 1, map[string]string{ "foo": "bar", }, map[string]float64{ Loading Loading @@ -1572,7 +1579,8 @@ func TestMatchmakerRequireMutualMatchLargerReversed(t *testing.T) { }, }, sessionID.String(), "", "+properties.foo:bar properties.b1:10^10", 3, 3, map[string]string{ 3, 3, 1, map[string]string{ "foo": "bar", }, map[string]float64{ Loading @@ -1593,7 +1601,8 @@ func TestMatchmakerRequireMutualMatchLargerReversed(t *testing.T) { }, }, sessionID2.String(), "", "+properties.foo:bar +properties.b1:<10 properties.b1:20^10", 3, 3, map[string]string{ 3, 3, 1, map[string]string{ "foo": "bar", }, map[string]float64{ Loading @@ -1614,7 +1623,8 @@ func TestMatchmakerRequireMutualMatchLargerReversed(t *testing.T) { }, }, sessionID3.String(), "", "+properties.foo:bar", 3, 3, map[string]string{ 3, 3, 1, map[string]string{ "foo": "bar", }, map[string]float64{ Loading Loading @@ -1650,7 +1660,7 @@ func isModeAuthoritative(props map[string]interface{}) bool { // - min/max count 2 // - all items are a mutual match func BenchmarkMatchmakerSmallProcessAllMutual(b *testing.B) { benchmarkMatchmakerHelper(b, 2, 2, 2, benchmarkMatchmakerHelper(b, 2, 2, 2, 1, func(i int) (string, map[string]string) { return benchmarkMatchQueryAny, benchmarkPropsAny }) Loading @@ -1662,7 +1672,7 @@ func BenchmarkMatchmakerSmallProcessAllMutual(b *testing.B) { // - min/max count 2 // - approx 50% items are a mutual match func BenchmarkMatchmakerSmallProcessSomeNotMutual(b *testing.B) { benchmarkMatchmakerHelper(b, 2, 2, 2, benchmarkMatchmakerHelper(b, 2, 2, 2, 1, func(i int) (string, map[string]string) { matchQuery := benchmarkMatchQueryAny props := benchmarkPropsAny Loading @@ -1680,7 +1690,7 @@ func BenchmarkMatchmakerSmallProcessSomeNotMutual(b *testing.B) { // - min/max count 2 // - all items are a mutual match func BenchmarkMatchmakerMediumProcessAllMutual(b *testing.B) { benchmarkMatchmakerHelper(b, 100, 2, 2, benchmarkMatchmakerHelper(b, 100, 2, 2, 1, func(i int) (string, map[string]string) { return benchmarkMatchQueryAny, benchmarkPropsAny }) Loading @@ -1692,7 +1702,7 @@ func BenchmarkMatchmakerMediumProcessAllMutual(b *testing.B) { // - min/max count 2 // - approx 50% items are a mutual match func BenchmarkMatchmakerMediumProcessSomeNonMutual(b *testing.B) { benchmarkMatchmakerHelper(b, 100, 2, 2, benchmarkMatchmakerHelper(b, 100, 2, 2, 1, func(i int) (string, map[string]string) { matchQuery := benchmarkMatchQueryAny props := benchmarkPropsAny Loading @@ -1710,7 +1720,7 @@ func BenchmarkMatchmakerMediumProcessSomeNonMutual(b *testing.B) { // - min/max count 6 // - approx 50% items are a mutual match func BenchmarkMatchmakerProcessMediumSomeNonMutualBiggerGroup(b *testing.B) { benchmarkMatchmakerHelper(b, 100, 6, 6, benchmarkMatchmakerHelper(b, 100, 6, 6, 1, func(i int) (string, map[string]string) { matchQuery := benchmarkMatchQueryAny props := benchmarkPropsAny Loading @@ -1729,7 +1739,7 @@ func BenchmarkMatchmakerProcessMediumSomeNonMutualBiggerGroup(b *testing.B) { // - docs are now in a 50/40/10 distribution // - 50% match all, 40% match some, and 10% match few func BenchmarkMatchmakerProcessMediumSomeNonMutualBiggerGroupAndDifficultMatch(b *testing.B) { benchmarkMatchmakerHelper(b, 100, 6, 6, benchmarkMatchmakerHelper(b, 100, 6, 6, 1, func(i int) (string, map[string]string) { matchQuery := benchmarkMatchQueryAny props := benchmarkPropsAny Loading @@ -1744,7 +1754,7 @@ func BenchmarkMatchmakerProcessMediumSomeNonMutualBiggerGroupAndDifficultMatch(b }) } func benchmarkMatchmakerHelper(b *testing.B, activeCount, minCount, maxCount int, func benchmarkMatchmakerHelper(b *testing.B, activeCount, minCount, maxCount, countMultiple int, withQueryAndProps func(i int) (string, map[string]string)) { consoleLogger := loggerForBenchmark(b) matchMaker, cleanup, err := createTestMatchmaker(b, consoleLogger, nil) Loading Loading @@ -1772,7 +1782,7 @@ func benchmarkMatchmakerHelper(b *testing.B, activeCount, minCount, maxCount int }, }, sessionID.String(), "", matchQuery, minCount, maxCount, minCount, maxCount, countMultiple, props, map[string]float64{}) if err != nil { Loading Loading @@ -1828,7 +1838,7 @@ func TestMatchmakerMaxPartyTracking(t *testing.T) { }, }, sessionID.String(), party, "properties.a5:bar", 2, 2, 2, 2, 1, map[string]string{ "a5": "bar", }, Loading Loading @@ -1909,7 +1919,7 @@ func TestMatchmakerMaxSessionTracking(t *testing.T) { }, }, sessionID.String(), "", "properties.a5:bar", 2, 2, 2, 2, 1, map[string]string{ "a5": "bar", }, Loading server/party_handler.go +2 −2 Original line number Diff line number Diff line Loading @@ -509,7 +509,7 @@ func (p *PartyHandler) JoinRequestList(sessionID, node string) ([]*rtapi.UserPre return joinRequestUserPresences, nil } func (p *PartyHandler) MatchmakerAdd(sessionID, node, query string, minCount, maxCount int, stringProperties map[string]string, numericProperties map[string]float64) (string, []*PresenceID, error) { func (p *PartyHandler) MatchmakerAdd(sessionID, node, query string, minCount, maxCount, countMultiple int, stringProperties map[string]string, numericProperties map[string]float64) (string, []*PresenceID, error) { p.RLock() if p.stopped { p.RUnlock() Loading Loading @@ -542,7 +542,7 @@ func (p *PartyHandler) MatchmakerAdd(sessionID, node, query string, minCount, ma p.RUnlock() ticket, err := p.matchmaker.Add(presences, "", p.IDStr, query, minCount, maxCount, stringProperties, numericProperties) ticket, err := p.matchmaker.Add(presences, "", p.IDStr, query, minCount, maxCount, countMultiple, stringProperties, numericProperties) if err != nil { return "", nil, err } Loading server/party_handler_test.go +1 −1 Original line number Diff line number Diff line Loading @@ -44,7 +44,7 @@ func TestPartyMatchmakerAddAndRemove(t *testing.T) { }, }}) ticket, _, err := partyHandler.MatchmakerAdd(sessionID.String(), node, "", 1, 1, nil, nil) ticket, _, err := partyHandler.MatchmakerAdd(sessionID.String(), node, "", 1, 1, 1, nil, nil) if err != nil { t.Fatalf("MatchmakerAdd error %s", err) } Loading Loading
CHANGELOG.md +1 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr - Add Groups page and associated endpoints to the developer console. - Add NotificationSendAll function to the runtimes, for sending a notification to all users. - Log a warning when client IP address cannot be resolved. - Add matchmaker option to enforce a multiple of resulting matched count. - Add tagged Prometheus stats containing RPC function identifiers. ### Changed Loading
server/matchmaker.go +109 −31 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ import ( "context" "fmt" "math" "sort" "sync" "time" Loading Loading @@ -98,14 +99,57 @@ type MatchmakerIndex struct { // Parameters used for correctly processing various matchmaker operations, but not indexed for searching. Query string `json:"-"` Count int `json:"-"` CountMultiple int `json:"-"` SessionID string `json:"-"` Intervals int `json:"-"` SessionIDs map[string]struct{} `json:"-"` } type MatchmakerIndexGroup struct { indexes []*MatchmakerIndex avgCreatedAt int64 } func groupIndexes(indexes []*MatchmakerIndex, required int) []*MatchmakerIndexGroup { if len(indexes) == 0 || required == 0 { return nil } var results []*MatchmakerIndexGroup for i := 0; i < len(indexes); i++ { // Grab index combination not including the current index. current, before, after := indexes[i], indexes[:i], indexes[i+1:] others := make([]*MatchmakerIndex, len(before)+len(after)) copy(others, before) copy(others[len(before):], after) if current.Count == required { // 1. The current index by itself satisfies the requirement. results = append(results, &MatchmakerIndexGroup{ indexes: []*MatchmakerIndex{current}, avgCreatedAt: current.CreatedAt, }) } else { // 2. The current index plus some combination(s) of the others. fillResults := groupIndexes(others, required-current.Count) for _, fillResult := range fillResults { indexesCount := int64(len(fillResult.indexes)) fillResult.avgCreatedAt = (fillResult.avgCreatedAt*indexesCount + current.CreatedAt) / (indexesCount + 1) fillResult.indexes = append(fillResult.indexes, current) results = append(results, fillResult) } } // 3. Other combinations not including the current index. results = append(results, groupIndexes(others, required)...) } return results } type Matchmaker interface { Stop() Add(presences []*MatchmakerPresence, sessionID, partyId, query string, minCount, maxCount int, stringProperties map[string]string, numericProperties map[string]float64) (string, error) Add(presences []*MatchmakerPresence, sessionID, partyId, query string, minCount, maxCount, countMultiple int, stringProperties map[string]string, numericProperties map[string]float64) (string, error) RemoveSession(sessionID, ticket string) error RemoveSessionAll(sessionID string) error RemoveParty(partyID, ticket string) error Loading Loading @@ -260,6 +304,7 @@ func (m *LocalMatchmaker) process(batch *index.Batch) { // Form possible combinations, in case multiple matches might be suitable. entryCombos := make([][]*MatchmakerEntry, 0, 5) lastHitCounter := len(blugeMatches.Hits) - 1 for hitCounter, hit := range blugeMatches.Hits { if hit.ID == ticket { // Skip the current ticket. Loading Loading @@ -324,10 +369,10 @@ func (m *LocalMatchmaker) process(batch *index.Batch) { break } else if !entryMatchesSearchHitQuery { mutualMatchConflict = true // this search hit is not a mutual match with the outer ticket // This search hit is not a mutual match with the outer ticket. break } // MatchmakerEntry's do not have the query, have to dig it back out of indexes // MatchmakerEntry does not have the query,read it out of indexes. if entriesIndexEntry, ok := m.indexes[entry.Ticket]; ok { searchHitMatchesEntryQuery, err := validateMatch(m.ctx, indexReader, entriesIndexEntry.Query, hit.ID) if err != nil { Loading @@ -336,7 +381,7 @@ func (m *LocalMatchmaker) process(batch *index.Batch) { break } else if !searchHitMatchesEntryQuery { mutualMatchConflict = true // this search hit is not a mutual match with the outer ticket // This search hit is not a mutual match with the outer ticket. break } } else { Loading Loading @@ -369,29 +414,61 @@ func (m *LocalMatchmaker) process(batch *index.Batch) { // * It is the last interval for this active index. // * The combo at least satisfies the min count. // * The combo does not exceed the max count. // * There are no further hits that may further fill the found combo, so we get as close as possible to the max count. if l := len(foundCombo) + index.Count; l == index.MaxCount || (lastInterval && l >= index.MinCount && l <= index.MaxCount && hitCounter >= len(blugeMatches.Hits)-1) { // Check that the minimum count that satisfies the current index is also good enough for all matched entries. var minCountFailed bool // * There are no more hits that may further fill the found combo, so we get as close as possible to the max count. if l := len(foundCombo) + index.Count; l == index.MaxCount || (lastInterval && l >= index.MinCount && l <= index.MaxCount && hitCounter >= lastHitCounter) { if rem := l % index.CountMultiple; rem != 0 { // The size of the combination being considered does not satisfy the count multiple. // Attempt to adjust the combo by removing the smallest possible number of entries. // Prefer keeping entries that have been in the matchmaker the longest, if possible. eligibleIndexes := make([]*MatchmakerIndex, 0, len(foundCombo)) for _, e := range foundCombo { if foundIndex, ok := m.indexes[e.Ticket]; ok && foundIndex.MinCount > l { minCountFailed = true // Only tickets individually less <= the removable size are considered. // For example removing a party of 3 when we're only looking to remove 2 is not allowed. if foundIndex, ok := m.indexes[e.Ticket]; ok && foundIndex.Count <= rem { eligibleIndexes = append(eligibleIndexes, foundIndex) } } eligibleGroups := groupIndexes(eligibleIndexes, rem) if len(eligibleGroups) <= 0 { // No possible combination to remove, unlikely but guard. continue } // Sort to ensure we keep as many of the longest-waiting tickets as possible. sort.Slice(eligibleGroups, func(i, j int) bool { return eligibleGroups[i].avgCreatedAt < eligibleGroups[j].avgCreatedAt }) // The most eligible group is removed from the combo. for _, egIndex := range eligibleGroups[0].indexes { for i := 0; i < len(foundCombo); i++ { if egIndex.Ticket == foundCombo[i].Ticket { foundCombo[i] = foundCombo[len(foundCombo)-1] foundCombo[len(foundCombo)-1] = nil foundCombo = foundCombo[:len(foundCombo)-1] break } } if minCountFailed { } if (len(foundCombo)+index.Count)%index.CountMultiple != 0 { // Removal was insufficient, the combo is still not valid for the required multiple. continue } } // Check that the maximum count that satisfies the current index is also good enough for all matched entries. var maxCountFailed bool // Check that ALL of these conditions are true for ALL matched entries: // * The found combo size satisfies the minimum count. // * The found combo size satisfies the maximum count. // * The found combo size satisfies the count multiple. // For any condition failures it does not matter which specific condition is not met. var conditionFailed bool for _, e := range foundCombo { if foundIndex, ok := m.indexes[e.Ticket]; ok && foundIndex.MaxCount < l { maxCountFailed = true if foundIndex, ok := m.indexes[e.Ticket]; ok && (foundIndex.MinCount > l || foundIndex.MaxCount < l || l%foundIndex.CountMultiple != 0) { conditionFailed = true break } } if maxCountFailed { if conditionFailed { continue } Loading Loading @@ -507,7 +584,7 @@ func (m *LocalMatchmaker) process(batch *index.Batch) { } } func (m *LocalMatchmaker) Add(presences []*MatchmakerPresence, sessionID, partyId, query string, minCount, maxCount int, stringProperties map[string]string, numericProperties map[string]float64) (string, error) { func (m *LocalMatchmaker) Add(presences []*MatchmakerPresence, sessionID, partyId, query string, minCount, maxCount, countMultiple int, stringProperties map[string]string, numericProperties map[string]float64) (string, error) { // Check if the matchmaker has been stopped. if m.stopped.Load() { return "", runtime.ErrMatchmakerNotAvailable Loading Loading @@ -552,6 +629,7 @@ func (m *LocalMatchmaker) Add(presences []*MatchmakerPresence, sessionID, partyI Query: query, Count: len(presences), CountMultiple: countMultiple, SessionID: sessionID, Intervals: 0, SessionIDs: sessionIDs, Loading
server/matchmaker_test.go +68 −58 Original line number Diff line number Diff line Loading @@ -48,7 +48,7 @@ func TestMatchmakerAddOnly(t *testing.T) { Node: "a", SessionID: sessionID, }, }, sessionID.String(), "", "properties.a1:foo", 2, 2, map[string]string{ }, sessionID.String(), "", "properties.a1:foo", 2, 2, 1, map[string]string{ "a1": "bar", }, map[string]float64{}) if err != nil { Loading Loading @@ -77,7 +77,7 @@ func TestMatchmakerAddAndRemove(t *testing.T) { Node: "a", SessionID: sessionID, }, }, sessionID.String(), "", "properties.a1:foo", 2, 2, map[string]string{ }, sessionID.String(), "", "properties.a1:foo", 2, 2, 1, map[string]string{ "a1": "bar", }, map[string]float64{}) if err != nil { Loading Loading @@ -117,7 +117,7 @@ func TestMatchmakerAddWithBasicMatch(t *testing.T) { Node: "a", SessionID: sessionID, }, }, sessionID.String(), "", "properties.a3:bar", 2, 2, map[string]string{ }, sessionID.String(), "", "properties.a3:bar", 2, 2, 1, map[string]string{ "a3": "baz", }, map[string]float64{}) if err != nil { Loading @@ -136,7 +136,7 @@ func TestMatchmakerAddWithBasicMatch(t *testing.T) { Node: "b", SessionID: sessionID2, }, }, sessionID2.String(), "", "properties.a3:baz", 2, 2, map[string]string{ }, sessionID2.String(), "", "properties.a3:baz", 2, 2, 1, map[string]string{ "a3": "bar", }, map[string]float64{}) if err != nil { Loading @@ -161,7 +161,7 @@ func TestMatchmakerAddWithBasicMatch(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -189,7 +189,7 @@ func TestMatchmakerAddWithBasicMatch(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -231,7 +231,7 @@ func TestMatchmakerAddWithMatchOnStar(t *testing.T) { }, }, sessionID.String(), "", "*", 2, 2, map[string]string{}, 2, 2, 1, map[string]string{}, map[string]float64{ "b1": 15, }) Loading @@ -250,7 +250,7 @@ func TestMatchmakerAddWithMatchOnStar(t *testing.T) { }, }, sessionID2.String(), "", "*", 2, 2, map[string]string{}, 2, 2, 1, map[string]string{}, map[string]float64{ "b1": 15, }) Loading Loading @@ -279,7 +279,7 @@ func TestMatchmakerAddWithMatchOnStar(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -307,7 +307,7 @@ func TestMatchmakerAddWithMatchOnStar(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -349,7 +349,7 @@ func TestMatchmakerAddWithMatchOnRange(t *testing.T) { }, }, sessionID.String(), "", "+properties.b1:>=10 +properties.b1:<=20", 2, 2, map[string]string{}, 2, 2, 1, map[string]string{}, map[string]float64{ "b1": 15, }) Loading @@ -368,7 +368,7 @@ func TestMatchmakerAddWithMatchOnRange(t *testing.T) { }, }, sessionID2.String(), "", "+properties.b1:>=10 +properties.b1:<=20", 2, 2, map[string]string{}, 2, 2, 1, map[string]string{}, map[string]float64{ "b1": 15, }) Loading Loading @@ -397,7 +397,7 @@ func TestMatchmakerAddWithMatchOnRange(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -425,7 +425,7 @@ func TestMatchmakerAddWithMatchOnRange(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -467,7 +467,7 @@ func TestMatchmakerAddWithMatchOnRangeAndValue(t *testing.T) { }, }, sessionID.String(), "", "+properties.b1:>=10 +properties.b1:<=20", 2, 2, 2, 2, 1, map[string]string{ "c2": "foo", }, Loading @@ -492,7 +492,7 @@ func TestMatchmakerAddWithMatchOnRangeAndValue(t *testing.T) { }, }, sessionID2.String(), "", "+properties.c1:>=10 +properties.c1:<=20 +properties.c2:foo", 2, 2, 2, 2, 1, map[string]string{ "c2": "foo", }, Loading Loading @@ -521,7 +521,7 @@ func TestMatchmakerAddWithMatchOnRangeAndValue(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -549,7 +549,7 @@ func TestMatchmakerAddWithMatchOnRangeAndValue(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -594,7 +594,7 @@ func TestMatchmakerAddRemoveNotMatch(t *testing.T) { }, }, sessionID.String(), "", "properties.a3:bar", 2, 2, map[string]string{ 2, 2, 1, map[string]string{ "a3": "baz", }, map[string]float64{}) if err != nil { Loading Loading @@ -642,7 +642,7 @@ func TestMatchmakerAddButNotMatch(t *testing.T) { }, }, sessionID.String(), "", "properties.a5:bar", 2, 2, 2, 2, 1, map[string]string{ "a5": "baz", }, Loading @@ -665,7 +665,7 @@ func TestMatchmakerAddButNotMatch(t *testing.T) { }, }, sessionID2.String(), "", "properties.a5:bar", 2, 2, 2, 2, 1, map[string]string{ "a5": "baz", }, Loading Loading @@ -715,7 +715,7 @@ func TestMatchmakerAddButNotMatchOnRange(t *testing.T) { }, }, sessionID.String(), "", "+properties.b2:>=10 +properties.b2:<=20 +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), }, Loading @@ -740,7 +740,7 @@ func TestMatchmakerAddButNotMatchOnRange(t *testing.T) { }, }, sessionID2.String(), "", "+properties.b2:>=10 +properties.b2:<=20 +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), }, Loading Loading @@ -792,7 +792,7 @@ func TestMatchmakerAddButNotMatchOnRangeAndValue(t *testing.T) { }, }, sessionID.String(), "", "+properties.c3:>=10 +properties.c3:<=20 +properties.c4:foo +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "c4": "foo", Loading @@ -818,7 +818,7 @@ func TestMatchmakerAddButNotMatchOnRangeAndValue(t *testing.T) { }, }, sessionID2.String(), "", "+properties.c3:>=10 +properties.c3:<=20 +properties.c4:foo +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "c4": "foo", Loading Loading @@ -868,7 +868,7 @@ func TestMatchmakerAddMultipleAndSomeMatch(t *testing.T) { }, }, sessionID.String(), "", "properties.a6:bar +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "bar", Loading @@ -892,7 +892,7 @@ func TestMatchmakerAddMultipleAndSomeMatch(t *testing.T) { }, }, sessionID2.String(), "", "properties.a6:bar +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "bar", Loading @@ -916,7 +916,7 @@ func TestMatchmakerAddMultipleAndSomeMatch(t *testing.T) { }, }, sessionID3.String(), "", "properties.a6:bar +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "bar", Loading Loading @@ -971,7 +971,7 @@ func TestMatchmakerAddMultipleAndSomeMatchWithBoost(t *testing.T) { }, }, sessionID.String(), "", "properties.n1:<10^10 properties.a6:bar +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "bar", Loading @@ -997,7 +997,7 @@ func TestMatchmakerAddMultipleAndSomeMatchWithBoost(t *testing.T) { }, }, sessionID2.String(), "", "properties.n1:>10^10 properties.a6:bar +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "bar", Loading @@ -1023,7 +1023,7 @@ func TestMatchmakerAddMultipleAndSomeMatchWithBoost(t *testing.T) { }, }, sessionID3.String(), "", "properties.n1:<10^10 properties.a6:bar +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "bar", Loading Loading @@ -1090,7 +1090,7 @@ func TestMatchmakerAddMultipleAndSomeMatchOptionalTextAlteringScore(t *testing.T }, }, sessionID.String(), "", "properties.a6:bar properties.a6:foo +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "bar", Loading @@ -1114,7 +1114,7 @@ func TestMatchmakerAddMultipleAndSomeMatchOptionalTextAlteringScore(t *testing.T }, }, sessionID2.String(), "", "properties.a6:bar properties.a6:foo +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "foo", Loading @@ -1138,7 +1138,7 @@ func TestMatchmakerAddMultipleAndSomeMatchOptionalTextAlteringScore(t *testing.T }, }, sessionID3.String(), "", "properties.a6:bar properties.a6:foo +properties.id:"+testID.String(), 2, 2, 2, 2, 1, map[string]string{ "id": testID.String(), "a6": "bar", Loading Loading @@ -1191,7 +1191,8 @@ func TestMatchmakerAddAndMatchAuthoritative(t *testing.T) { }, }, sessionID.String(), "", "properties.d1:foo", 2, 2, map[string]string{ 2, 2, 1, map[string]string{ "d1": "foo", "mode": "authoritative", }, map[string]float64{}) Loading @@ -1213,7 +1214,8 @@ func TestMatchmakerAddAndMatchAuthoritative(t *testing.T) { }, }, sessionID2.String(), "", "properties.d1:foo", 2, 2, map[string]string{ 2, 2, 1, map[string]string{ "d1": "foo", "mode": "authoritative", }, map[string]float64{}) Loading @@ -1239,7 +1241,7 @@ func TestMatchmakerAddAndMatchAuthoritative(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -1267,7 +1269,7 @@ func TestMatchmakerAddAndMatchAuthoritative(t *testing.T) { } self := mm.GetSelf() if self == nil { t.Fatal("expectd self to not be nil") t.Fatal("expected self to not be nil") } if self.Presence.GetSessionId() == "" { t.Fatalf("expected session id not to be empty") Loading Loading @@ -1391,7 +1393,8 @@ func TestMatchmakerRequireMutualMatch(t *testing.T) { }, }, sessionID.String(), "", "+properties.b1:>=10 +properties.b1:<=20", 2, 2, map[string]string{}, 2, 2, 1, map[string]string{}, map[string]float64{ "b1": 5, }) Loading @@ -1410,7 +1413,8 @@ func TestMatchmakerRequireMutualMatch(t *testing.T) { }, }, sessionID2.String(), "", "+properties.b1:>=10 +properties.b1:<=20", 2, 2, map[string]string{}, 2, 2, 1, map[string]string{}, map[string]float64{ "b1": 15, }) Loading Loading @@ -1472,7 +1476,8 @@ func TestMatchmakerRequireMutualMatchLarger(t *testing.T) { }, }, sessionID.String(), "", "+properties.foo:bar properties.b1:10^10", 3, 3, map[string]string{ 3, 3, 1, map[string]string{ "foo": "bar", }, map[string]float64{ Loading @@ -1493,7 +1498,8 @@ func TestMatchmakerRequireMutualMatchLarger(t *testing.T) { }, }, sessionID2.String(), "", "+properties.foo:bar properties.b1:20^10", 3, 3, map[string]string{ 3, 3, 1, map[string]string{ "foo": "bar", }, map[string]float64{ Loading @@ -1514,7 +1520,8 @@ func TestMatchmakerRequireMutualMatchLarger(t *testing.T) { }, }, sessionID3.String(), "", "+properties.foo:bar +properties.b1:<10", 3, 3, map[string]string{ 3, 3, 1, map[string]string{ "foo": "bar", }, map[string]float64{ Loading Loading @@ -1572,7 +1579,8 @@ func TestMatchmakerRequireMutualMatchLargerReversed(t *testing.T) { }, }, sessionID.String(), "", "+properties.foo:bar properties.b1:10^10", 3, 3, map[string]string{ 3, 3, 1, map[string]string{ "foo": "bar", }, map[string]float64{ Loading @@ -1593,7 +1601,8 @@ func TestMatchmakerRequireMutualMatchLargerReversed(t *testing.T) { }, }, sessionID2.String(), "", "+properties.foo:bar +properties.b1:<10 properties.b1:20^10", 3, 3, map[string]string{ 3, 3, 1, map[string]string{ "foo": "bar", }, map[string]float64{ Loading @@ -1614,7 +1623,8 @@ func TestMatchmakerRequireMutualMatchLargerReversed(t *testing.T) { }, }, sessionID3.String(), "", "+properties.foo:bar", 3, 3, map[string]string{ 3, 3, 1, map[string]string{ "foo": "bar", }, map[string]float64{ Loading Loading @@ -1650,7 +1660,7 @@ func isModeAuthoritative(props map[string]interface{}) bool { // - min/max count 2 // - all items are a mutual match func BenchmarkMatchmakerSmallProcessAllMutual(b *testing.B) { benchmarkMatchmakerHelper(b, 2, 2, 2, benchmarkMatchmakerHelper(b, 2, 2, 2, 1, func(i int) (string, map[string]string) { return benchmarkMatchQueryAny, benchmarkPropsAny }) Loading @@ -1662,7 +1672,7 @@ func BenchmarkMatchmakerSmallProcessAllMutual(b *testing.B) { // - min/max count 2 // - approx 50% items are a mutual match func BenchmarkMatchmakerSmallProcessSomeNotMutual(b *testing.B) { benchmarkMatchmakerHelper(b, 2, 2, 2, benchmarkMatchmakerHelper(b, 2, 2, 2, 1, func(i int) (string, map[string]string) { matchQuery := benchmarkMatchQueryAny props := benchmarkPropsAny Loading @@ -1680,7 +1690,7 @@ func BenchmarkMatchmakerSmallProcessSomeNotMutual(b *testing.B) { // - min/max count 2 // - all items are a mutual match func BenchmarkMatchmakerMediumProcessAllMutual(b *testing.B) { benchmarkMatchmakerHelper(b, 100, 2, 2, benchmarkMatchmakerHelper(b, 100, 2, 2, 1, func(i int) (string, map[string]string) { return benchmarkMatchQueryAny, benchmarkPropsAny }) Loading @@ -1692,7 +1702,7 @@ func BenchmarkMatchmakerMediumProcessAllMutual(b *testing.B) { // - min/max count 2 // - approx 50% items are a mutual match func BenchmarkMatchmakerMediumProcessSomeNonMutual(b *testing.B) { benchmarkMatchmakerHelper(b, 100, 2, 2, benchmarkMatchmakerHelper(b, 100, 2, 2, 1, func(i int) (string, map[string]string) { matchQuery := benchmarkMatchQueryAny props := benchmarkPropsAny Loading @@ -1710,7 +1720,7 @@ func BenchmarkMatchmakerMediumProcessSomeNonMutual(b *testing.B) { // - min/max count 6 // - approx 50% items are a mutual match func BenchmarkMatchmakerProcessMediumSomeNonMutualBiggerGroup(b *testing.B) { benchmarkMatchmakerHelper(b, 100, 6, 6, benchmarkMatchmakerHelper(b, 100, 6, 6, 1, func(i int) (string, map[string]string) { matchQuery := benchmarkMatchQueryAny props := benchmarkPropsAny Loading @@ -1729,7 +1739,7 @@ func BenchmarkMatchmakerProcessMediumSomeNonMutualBiggerGroup(b *testing.B) { // - docs are now in a 50/40/10 distribution // - 50% match all, 40% match some, and 10% match few func BenchmarkMatchmakerProcessMediumSomeNonMutualBiggerGroupAndDifficultMatch(b *testing.B) { benchmarkMatchmakerHelper(b, 100, 6, 6, benchmarkMatchmakerHelper(b, 100, 6, 6, 1, func(i int) (string, map[string]string) { matchQuery := benchmarkMatchQueryAny props := benchmarkPropsAny Loading @@ -1744,7 +1754,7 @@ func BenchmarkMatchmakerProcessMediumSomeNonMutualBiggerGroupAndDifficultMatch(b }) } func benchmarkMatchmakerHelper(b *testing.B, activeCount, minCount, maxCount int, func benchmarkMatchmakerHelper(b *testing.B, activeCount, minCount, maxCount, countMultiple int, withQueryAndProps func(i int) (string, map[string]string)) { consoleLogger := loggerForBenchmark(b) matchMaker, cleanup, err := createTestMatchmaker(b, consoleLogger, nil) Loading Loading @@ -1772,7 +1782,7 @@ func benchmarkMatchmakerHelper(b *testing.B, activeCount, minCount, maxCount int }, }, sessionID.String(), "", matchQuery, minCount, maxCount, minCount, maxCount, countMultiple, props, map[string]float64{}) if err != nil { Loading Loading @@ -1828,7 +1838,7 @@ func TestMatchmakerMaxPartyTracking(t *testing.T) { }, }, sessionID.String(), party, "properties.a5:bar", 2, 2, 2, 2, 1, map[string]string{ "a5": "bar", }, Loading Loading @@ -1909,7 +1919,7 @@ func TestMatchmakerMaxSessionTracking(t *testing.T) { }, }, sessionID.String(), "", "properties.a5:bar", 2, 2, 2, 2, 1, map[string]string{ "a5": "bar", }, Loading
server/party_handler.go +2 −2 Original line number Diff line number Diff line Loading @@ -509,7 +509,7 @@ func (p *PartyHandler) JoinRequestList(sessionID, node string) ([]*rtapi.UserPre return joinRequestUserPresences, nil } func (p *PartyHandler) MatchmakerAdd(sessionID, node, query string, minCount, maxCount int, stringProperties map[string]string, numericProperties map[string]float64) (string, []*PresenceID, error) { func (p *PartyHandler) MatchmakerAdd(sessionID, node, query string, minCount, maxCount, countMultiple int, stringProperties map[string]string, numericProperties map[string]float64) (string, []*PresenceID, error) { p.RLock() if p.stopped { p.RUnlock() Loading Loading @@ -542,7 +542,7 @@ func (p *PartyHandler) MatchmakerAdd(sessionID, node, query string, minCount, ma p.RUnlock() ticket, err := p.matchmaker.Add(presences, "", p.IDStr, query, minCount, maxCount, stringProperties, numericProperties) ticket, err := p.matchmaker.Add(presences, "", p.IDStr, query, minCount, maxCount, countMultiple, stringProperties, numericProperties) if err != nil { return "", nil, err } Loading
server/party_handler_test.go +1 −1 Original line number Diff line number Diff line Loading @@ -44,7 +44,7 @@ func TestPartyMatchmakerAddAndRemove(t *testing.T) { }, }}) ticket, _, err := partyHandler.MatchmakerAdd(sessionID.String(), node, "", 1, 1, nil, nil) ticket, _, err := partyHandler.MatchmakerAdd(sessionID.String(), node, "", 1, 1, 1, nil, nil) if err != nil { t.Fatalf("MatchmakerAdd error %s", err) } Loading