Getting match state from RPC call in authoritative matches

Hi,

I’m running into a small conundrum while implementing an RPC call. Basically the RPC call needs to be able to access a match that the current player is in and mutate that state. I can’t seem to find any way to get the match state in my RPC callback. The callback is pretty basic and basically looks like:

func leaveMatch(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
    // the payload is some JSON data containing the match id
    var matchId = ...

    match, err := nk.MatchGet(ctx, matchId)
    if err != nil {
        // match not found
        return "", err
    }

    // now what? How can I get the custom match state that I created in MatchInit()?
}

There doesn’t seem to be anything that I can call to get my state stored in Nakama’s MatchHandler struct. So, one alternative is to signal the Match itself to do the mutating. For this I use the below code in my RPC handler:

    signalResult, err := nk.MatchSignal(ctx, matchId, common.MatchSignalLeave)

Here I get my own state interface{} which contains everything I need. In my callback for the signal I try to get the current user id so that I can map that to a player in the state:

func (m *Match) MatchSignal(ctx context.Context, logger runtime.Logger, _ *sql.DB, _ runtime.NakamaModule, _ runtime.MatchDispatcher, tick int64, state interface{}, data string) (interface{}, string) {
    userId, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
    if !ok {
        return nil, errors.New("invalid context, missing user id")
    }

    ....
}

Alas, it seems that the context.Context does not contain runtime.RUNTIME_CTX_USER_ID, probably because it’s a different context. So I can’t find the user/player that triggered the signal and can’t do my mutation here.

A final fugly kludge would be to encode the user id into the signal’s data some way and use that, but that reeks of Bad Design® and is a last resort.

What is a correct way to do this? It would seem to me to be a pretty common thing you’d want to do?

MatchSignal is a way to ask the match handler to do something from outside of a match. However, realistically, there are only a few legit reasons to use this (e.g. seat reservation system) - and if you need to manipulate the match state outside of the scope of the match handler, then that should be “smell” to rethink your match flow.

Can you talk me through why you need to access/manipulate the match state outside of a match?

I create matches via an RPC call, as that gives more flexibility in tracking how the match fills up etc. So I also wanted to be able to cancel matches via an RPC call. For instance if the player sees that the match is not filling up he/she cancels it in the game UI, then the match should also be terminated on the server side so that it doesn’t have to run there until it times out after a while. The player’s socket is still connected so the match loop doesn’t get a “player left” event.

For now I added a message into the realtime protocol that basically does the same, but it seems like signaling a match would be exactly what should be used for cases like this.

@chakie If the match does not fill up and you want to allow the match creator to cancel it, this should be done via a match data message with a specific opcode you designate.

Have the client send a data message to the match over its socket, the match handler should observe this message and close itself. This is the best way to achieve what you need - you should never manipulate the match state or even really read it from outside the match handler, aside from some very exceptional circumstances.

Another use case is being able to list active matches. This of course works to some degree using MatchList(), but I can’t access any of our custom state that I would need in order to present data to the player. If I can get access to api.Match objects, why can’t I get access to my own match state?

In that case you should use the match label. Expose and periodically update data you want accessible via the match listing API. You can then query specific match label fields to narrow down match listings.

The match label is intended for exactly your requirement - a way for matches to advertise a certain set of fields or information about themselves to the outside world (anything outside the individual match handler).

2 Likes