Authoritative Multiplayer Match Handler Execution Sequence

I want to understand something about this code snippet
This code snippet is from match_handler.go link → nakama/match_handler.go at master · heroiclabs/nakama · GitHub

        // Continuously run queued actions until the match stops.
        	go func() {
        		for {
        			select {
        			case <-mh.stopCh:
        				// Match has been stopped.
        				return
        			case <-mh.ticker.C:
        				// Tick, queue a match loop invocation.
        				if !mh.queueCall(loop) {
        					return
        				}
        			case call := <-mh.callCh:
        				// An invocation to one of the match functions, not including join attempts.
        				call(mh)
        			case joinAttempt := <-mh.joinAttemptCh:
        				// An invocation to the join attempt match function.
        				joinAttempt(mh)
        			}
        		}
        	}()

From A Tour of Go select means
A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.

so match_handler functions will be executed randomly.

what if I have a limit to the number of players in a match, lets say 50
is it possible that 100 players joined the match same time invoking join attempt match function

func (m *Match) MatchJoinAttempt(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, presence runtime.Presence, metadata map[string]string) (interface{}, bool, string) {
    mState, _ := state.(*MatchState)
    acceptUser := true
    reasonToReject := ""
	if len(mState.presencesJoined) >=50{
        acceptUser = false
        reasonToReject = "Match is full!"
	}

 
    return state, acceptUser,reasonToReject
}

Now because match_handler functions are randomly executed unluckily MatchJoinAttempt invoked 90 times before MatchJoin is executed once, so we accepted all 90 players before invoking MatchJoin and add them to the presencesJoined

func (m *Match) MatchJoin(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, presences []runtime.Presence) interface{} {
    mState, _ := state.(*MatchState)
    for _, p := range presences {
        mState.presences[p.GetUserId()] = p
        presencesJoined = append(presencesJoined, p)

    }
    return mState
}

@Mohammed Hi there! Generally you should expect that functions in the match handler might be called in any order the server requires. Do not rely on implementation details in the core server’s scheduling of these functions as these may change.

This means you should absolutely handle the possibility that your match handler will receive many join attempts before the corresponding joins.

Our typical pattern for this is to account for “in-flight” joins, so let’s assume your match state looks like this:

type MatchState struct {
  presences       map[string]runtime.Presence
  joinsInProgress int
}

Your join attempt and join pattern might then look like this:

func (m *Match) MatchJoinAttempt(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, presence runtime.Presence, metadata map[string]string) (interface{}, bool, string) {
  mState, _ := state.(*MatchState)
  if len(mState.presences)+mState.joinsInProgress >= 50 {
    return mState, false, "Match is full!"
  }
  mState.joinsInProgress++
  return mState, true, ""
}

func (m *Match) MatchJoin(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, presences []runtime.Presence) interface{} {
  mState, _ := state.(*MatchState)
  for _, presence := range presences {
    mState.presences[presenge.GetUserId()] = presence
    mState.joinsInProgress--
  }
  return mState
}

This ensures your join attempt handler takes into account both “fully connected” presences and presences that have been accepted but not yet completed their join process. Hope this helps!

3 Likes