Commit 46e50e10 authored by Andrei Mihu's avatar Andrei Mihu
Browse files

Matchmaking feature. Merge #69

parent c6e3521a
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -6,6 +6,8 @@ The format is based on [keep a changelog](http://keepachangelog.com/) and this p
## [Unreleased]
### Added
- Node status now also reports a startup timestamp.
- New matchmaking feature.
- Optionally send match data to only a subset of match participants.

### Fixed
- Set correct initial group member count when group is created.
+3 −2
Original line number Diff line number Diff line
@@ -95,11 +95,12 @@ func main() {

	trackerService := server.NewTrackerService(config.GetName())
	statsService := server.NewStatsService(jsonLogger, config, semver, trackerService, startedAt)
	sessionRegistry := server.NewSessionRegistry(jsonLogger, config, trackerService)
	matchmakerService := server.NewMatchmakerService(config.GetName())
	sessionRegistry := server.NewSessionRegistry(jsonLogger, config, trackerService, matchmakerService)
	messageRouter := server.NewMessageRouterService(sessionRegistry)
	presenceNotifier := server.NewPresenceNotifier(jsonLogger, config.GetName(), trackerService, messageRouter)
	trackerService.AddDiffListener(presenceNotifier.HandleDiff)
	authService := server.NewAuthenticationService(jsonLogger, config, db, statsService, sessionRegistry, trackerService, messageRouter)
	authService := server.NewAuthenticationService(jsonLogger, config, db, statsService, sessionRegistry, trackerService, matchmakerService, messageRouter)
	opsService := server.NewOpsService(jsonLogger, multiLogger, semver, config, statsService)

	gaenabled := len(os.Getenv("NAKAMA_TELEMETRY")) < 1
+29 −3
Original line number Diff line number Diff line
@@ -138,7 +138,7 @@ message Envelope {
    TMatchCreate match_create = 41;
    TMatchJoin match_join = 42;
    TMatchLeave match_leave = 43;
    TMatchDataSend match_data_send = 44;
    MatchDataSend match_data_send = 44;
    TMatch match = 45;
    MatchData match_data = 46;
    MatchPresence match_presence = 47;
@@ -156,6 +156,11 @@ message Envelope {
    TLeaderboards leaderboards = 57;
    TLeaderboardRecord leaderboard_record = 58;
    TLeaderboardRecords leaderboard_records = 59;

    TMatchmakeAdd matchmake_add = 60;
    TMatchmakeRemove matchmake_remove = 61;
    TMatchmakeTicket matchmake_ticket = 62;
    MatchmakeMatched matchmake_matched = 63;
  }
}

@@ -438,9 +443,29 @@ message TopicPresence {
  repeated UserPresence leaves = 3;
}

message TMatchmakeAdd {
  int64 requiredCount = 1;
}
message TMatchmakeTicket {
  bytes ticket = 1;
}
message TMatchmakeRemove {
  bytes ticket = 1;
}

message MatchmakeMatched {
  bytes ticket = 1;
  bytes token = 2;
  repeated UserPresence presences = 3;
  UserPresence self = 4;
}

message TMatchCreate {}
message TMatchJoin {
  oneof id {
    bytes match_id = 1;
    bytes token = 2;
  }
}
message TMatch {
  bytes match_id = 1;
@@ -448,10 +473,11 @@ message TMatch {
  UserPresence self = 3;
}

message TMatchDataSend {
message MatchDataSend {
  bytes match_id = 1;
  int64 op_code = 2;
  bytes data = 3;
  repeated UserPresence presences = 4;
}

message MatchData {

server/matchmaker.go

0 → 100644
+120 −0
Original line number Diff line number Diff line
// Copyright 2017 The Nakama Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package server

import (
	"errors"
	"github.com/satori/go.uuid"
	"sync"
)

type Matchmaker interface {
	Add(sessionID uuid.UUID, userID uuid.UUID, meta PresenceMeta, requiredCount int64) (uuid.UUID, map[MatchmakerKey]*MatchmakerProfile)
	Remove(sessionID uuid.UUID, userID uuid.UUID, ticket uuid.UUID) error
	RemoveAll(sessionID uuid.UUID)
	UpdateAll(sessionID uuid.UUID, meta PresenceMeta)
}

type MatchmakerKey struct {
	ID     PresenceID
	UserID uuid.UUID
	Ticket uuid.UUID
}

type MatchmakerProfile struct {
	Meta          PresenceMeta
	RequiredCount int64
}

type MatchmakerService struct {
	sync.Mutex
	name   string
	values map[MatchmakerKey]*MatchmakerProfile
}

func NewMatchmakerService(name string) *MatchmakerService {
	return &MatchmakerService{
		name:   name,
		values: make(map[MatchmakerKey]*MatchmakerProfile),
	}
}

func (m *MatchmakerService) Add(sessionID uuid.UUID, userID uuid.UUID, meta PresenceMeta, requiredCount int64) (uuid.UUID, map[MatchmakerKey]*MatchmakerProfile) {
	ticket := uuid.NewV4()
	selected := make(map[MatchmakerKey]*MatchmakerProfile, requiredCount-1)
	qmk := MatchmakerKey{ID: PresenceID{SessionID: sessionID, Node: m.name}, UserID: userID, Ticket: ticket}
	qmp := &MatchmakerProfile{Meta: meta, RequiredCount: requiredCount}

	m.Lock()
	for mk, mp := range m.values {
		if mk.ID.SessionID != sessionID && mk.UserID != userID && mp.RequiredCount == requiredCount {
			selected[mk] = mp
			if int64(len(selected)) == requiredCount-1 {
				break
			}
		}
	}
	if int64(len(selected)) == requiredCount-1 {
		for mk, _ := range selected {
			delete(m.values, mk)
		}
		selected[qmk] = qmp
	} else {
		m.values[qmk] = qmp
	}
	m.Unlock()

	if int64(len(selected)) != requiredCount {
		return ticket, nil
	} else {
		return ticket, selected
	}
}

func (m *MatchmakerService) Remove(sessionID uuid.UUID, userID uuid.UUID, ticket uuid.UUID) error {
	mk := MatchmakerKey{ID: PresenceID{SessionID: sessionID, Node: m.name}, UserID: userID, Ticket: ticket}
	var e error

	m.Lock()
	_, ok := m.values[mk]
	if ok {
		delete(m.values, mk)
	} else {
		e = errors.New("ticket not found")
	}
	m.Unlock()

	return e
}

func (m *MatchmakerService) RemoveAll(sessionID uuid.UUID) {
	m.Lock()
	for mk, _ := range m.values {
		if mk.ID.SessionID == sessionID {
			delete(m.values, mk)
		}
	}
	m.Unlock()
}

func (m *MatchmakerService) UpdateAll(sessionID uuid.UUID, meta PresenceMeta) {
	m.Lock()
	for mk, mp := range m.values {
		if mk.ID.SessionID == sessionID {
			mp.Meta = meta
		}
	}
	m.Unlock()
}
+10 −1
Original line number Diff line number Diff line
@@ -28,17 +28,21 @@ type pipeline struct {
	db              *sql.DB
	socialClient    *social.Client
	tracker         Tracker
	matchmaker      Matchmaker
	hmacSecretByte  []byte
	messageRouter   MessageRouter
	sessionRegistry *SessionRegistry
}

// NewPipeline creates a new Pipeline
func NewPipeline(config Config, db *sql.DB, socialClient *social.Client, tracker Tracker, messageRouter MessageRouter, registry *SessionRegistry) *pipeline {
func NewPipeline(config Config, db *sql.DB, socialClient *social.Client, tracker Tracker, matchmaker Matchmaker, messageRouter MessageRouter, registry *SessionRegistry) *pipeline {
	return &pipeline{
		config:          config,
		db:              db,
		socialClient:    socialClient,
		tracker:         tracker,
		matchmaker:      matchmaker,
		hmacSecretByte:  []byte(config.GetSession().EncryptionKey),
		messageRouter:   messageRouter,
		sessionRegistry: registry,
	}
@@ -117,6 +121,11 @@ func (p *pipeline) processRequest(logger *zap.Logger, session *session, envelope
	case *Envelope_MatchDataSend:
		p.matchDataSend(logger, session, envelope)

	case *Envelope_MatchmakeAdd:
		p.matchmakeAdd(logger, session, envelope)
	case *Envelope_MatchmakeRemove:
		p.matchmakeRemove(logger, session, envelope)

	case *Envelope_StorageFetch:
		p.storageFetch(logger, session, envelope)
	case *Envelope_StorageWrite:
Loading