Loading CHANGELOG.md +2 −1 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr ### Added - Config option to adjust authoritative match data input queue size. - Config option to adjust authoritative match call queue size. - Authoritative match modules now allow a `match_join` callback that triggers when users have completed their join process. ### Changed - Presence list in match join responses no longer contains the user's own presence. Loading data/modules/clientrpc.lua +1 −1 Original line number Diff line number Diff line Loading @@ -66,7 +66,7 @@ end nk.register_rpc(send_stream_data, "clientrpc.send_stream_data") local function create_authoritative_match(_context, _payload) local match_id = nk.match_create("match", {}) local match_id = nk.match_create("match", { debug = true }) return nk.json_encode({ match_id = match_id }) end nk.register_rpc(create_authoritative_match, "clientrpc.create_authoritative_match") Loading data/modules/match.lua +56 −3 Original line number Diff line number Diff line Loading @@ -72,7 +72,7 @@ Dispatcher exposes useful functions to the match. Format: } Tick is the current match tick number, starts at 0 and increments after every match_loop call. Does not increment with calls to match_join_attempt or match_leave. calls to match_join_attempt, match_join, or match_leave. State is the current in-memory match state, may be any Lua term except nil. Loading @@ -95,6 +95,58 @@ local function match_join_attempt(context, dispatcher, tick, state, presence) return state, true end --[[ Called when one or more users have successfully completed the match join process after their match_join_attempt returns `true`. When their presences are sent to this function the users are ready to receive match data messages and can be targets for the dispatcher's `broadcast_message` function. Context represents information about the match and server, for information purposes. Format: { env = {}, -- key-value data set in the runtime.env server configuration. execution_mode = "Match", match_id = "client-friendly match ID, can be shared with clients and used in match join operations", match_node = "name of the Nakama node hosting this match", match_label = "the label string returned from match_init", match_tick_rate = 1 -- the tick rate returned by match_init } Dispatcher exposes useful functions to the match. Format: { broadcast_message = function(op_code, data, presences, sender), -- numeric message op code -- a data payload string, or nil -- list of presences (a subset of match participants) to use as message targets, or nil to send to the whole match -- a presence to tag on the message as the 'sender', or nil match_kick = function(presences) -- a list of presences to remove from the match } Tick is the current match tick number, starts at 0 and increments after every match_loop call. Does not increment with calls to match_join_attempt, match_join, or match_leave. State is the current in-memory match state, may be any Lua term except nil. Presences is a list of users that have joined the match. Format: { { user_id: "user unique ID", session_id: "session ID of the user's current connection", username: "user's unique username", node: "name of the Nakama node the user is connected to" }, ... } Expected return these values (all required) in order: 1. An (optionally) updated state. May be any non-nil Lua term, or nil to end the match. --]] local function match_join(context, dispatcher, tick, state, presences) if state.debug then print("match join:\n" .. du.print_r(presences)) end return state end --[[ Called when one or more users have left the match for any reason, including connection loss. Loading @@ -120,7 +172,7 @@ Dispatcher exposes useful functions to the match. Format: } Tick is the current match tick number, starts at 0 and increments after every match_loop call. Does not increment with calls to match_join_attempt or match_leave. calls to match_join_attempt, match_join, or match_leave. State is the current in-memory match state, may be any Lua term except nil. Loading Loading @@ -170,7 +222,7 @@ Dispatcher exposes useful functions to the match. Format: } Tick is the current match tick number, starts at 0 and increments after every match_loop call. Does not increment with calls to match_join_attempt or match_leave. calls to match_join_attempt, match_join, or match_leave. State is the current in-memory match state, may be any Lua term except nil. Loading Loading @@ -206,6 +258,7 @@ end return { match_init = match_init, match_join_attempt = match_join_attempt, match_join = match_join, match_leave = match_leave, match_loop = match_loop } main.go +1 −0 Original line number Diff line number Diff line Loading @@ -109,6 +109,7 @@ func main() { } leaderboardCache := server.NewLocalLeaderboardCache(logger, startupLogger, db) matchRegistry := server.NewLocalMatchRegistry(logger, db, config, socialClient, leaderboardCache, sessionRegistry, tracker, router, stdLibs, once, config.GetName()) tracker.SetMatchJoinListener(matchRegistry.Join) tracker.SetMatchLeaveListener(matchRegistry.Leave) // Separate module evaluation/validation from module loading. // We need the match registry to be available to wire all functions exposed to the runtime, which in turn needs the modules at least cached first. Loading server/match_handler.go +66 −0 Original line number Diff line number Diff line Loading @@ -56,6 +56,7 @@ type MatchHandler struct { vm *lua.LState initFn lua.LValue joinAttemptFn lua.LValue joinFn lua.LValue leaveFn lua.LValue loopFn lua.LValue ctx *lua.LTable Loading Loading @@ -121,6 +122,10 @@ func NewMatchHandler(logger *zap.Logger, db *sql.DB, config Config, socialClient if joinAttemptFn.Type() != lua.LTFunction { return nil, errors.New("match_join_attempt not found or not a function") } joinFn := tab.RawGet(lua.LString("match_join")) if joinFn == nil || joinFn.Type() != lua.LTFunction { joinFn = nil } leaveFn := tab.RawGet(lua.LString("match_leave")) if leaveFn.Type() != lua.LTFunction { return nil, errors.New("match_leave not found or not a function") Loading Loading @@ -210,6 +215,7 @@ func NewMatchHandler(logger *zap.Logger, db *sql.DB, config Config, socialClient vm: vm, initFn: initFn, joinAttemptFn: joinAttemptFn, joinFn: joinFn, leaveFn: leaveFn, loopFn: loopFn, ctx: ctx, Loading Loading @@ -471,6 +477,66 @@ func JoinAttempt(resultCh chan *MatchJoinResult, userID, sessionID uuid.UUID, us } } func Join(joins []*MatchPresence) func(mh *MatchHandler) { return func(mh *MatchHandler) { if mh.joinFn == nil { return } mh.Lock() if mh.stopped { mh.Unlock() return } mh.Unlock() presences := mh.vm.CreateTable(len(joins), 0) for i, p := range joins { presence := mh.vm.CreateTable(0, 4) presence.RawSetString("user_id", lua.LString(p.UserID.String())) presence.RawSetString("session_id", lua.LString(p.SessionID.String())) presence.RawSetString("username", lua.LString(p.Username)) presence.RawSetString("node", lua.LString(p.Node)) presences.RawSetInt(i+1, presence) } // Execute the match_leave call. mh.vm.Push(LSentinel) mh.vm.Push(mh.joinFn) mh.vm.Push(mh.ctx) mh.vm.Push(mh.dispatcher) mh.vm.Push(mh.tick) mh.vm.Push(mh.state) mh.vm.Push(presences) err := mh.vm.PCall(5, lua.MultRet, nil) if err != nil { mh.Stop() mh.logger.Warn("Stopping match after error from match_join execution", zap.Int("tick", int(mh.tick)), zap.Error(err)) return } // Extract the resulting state. state := mh.vm.Get(-1) if state.Type() == lua.LTNil || state.Type() == LTSentinel { mh.logger.Info("Match join returned nil or no state, stopping match") mh.Stop() return } mh.vm.Pop(1) // Check for and remove the sentinel value, will fail if there are any extra return values. if sentinel := mh.vm.Get(-1); sentinel.Type() != LTSentinel { mh.logger.Warn("Match join returned too many values, stopping match") mh.Stop() return } mh.vm.Pop(1) mh.state = state } } func Leave(leaves []*MatchPresence) func(mh *MatchHandler) { return func(mh *MatchHandler) { mh.Lock() Loading Loading
CHANGELOG.md +2 −1 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr ### Added - Config option to adjust authoritative match data input queue size. - Config option to adjust authoritative match call queue size. - Authoritative match modules now allow a `match_join` callback that triggers when users have completed their join process. ### Changed - Presence list in match join responses no longer contains the user's own presence. Loading
data/modules/clientrpc.lua +1 −1 Original line number Diff line number Diff line Loading @@ -66,7 +66,7 @@ end nk.register_rpc(send_stream_data, "clientrpc.send_stream_data") local function create_authoritative_match(_context, _payload) local match_id = nk.match_create("match", {}) local match_id = nk.match_create("match", { debug = true }) return nk.json_encode({ match_id = match_id }) end nk.register_rpc(create_authoritative_match, "clientrpc.create_authoritative_match") Loading
data/modules/match.lua +56 −3 Original line number Diff line number Diff line Loading @@ -72,7 +72,7 @@ Dispatcher exposes useful functions to the match. Format: } Tick is the current match tick number, starts at 0 and increments after every match_loop call. Does not increment with calls to match_join_attempt or match_leave. calls to match_join_attempt, match_join, or match_leave. State is the current in-memory match state, may be any Lua term except nil. Loading @@ -95,6 +95,58 @@ local function match_join_attempt(context, dispatcher, tick, state, presence) return state, true end --[[ Called when one or more users have successfully completed the match join process after their match_join_attempt returns `true`. When their presences are sent to this function the users are ready to receive match data messages and can be targets for the dispatcher's `broadcast_message` function. Context represents information about the match and server, for information purposes. Format: { env = {}, -- key-value data set in the runtime.env server configuration. execution_mode = "Match", match_id = "client-friendly match ID, can be shared with clients and used in match join operations", match_node = "name of the Nakama node hosting this match", match_label = "the label string returned from match_init", match_tick_rate = 1 -- the tick rate returned by match_init } Dispatcher exposes useful functions to the match. Format: { broadcast_message = function(op_code, data, presences, sender), -- numeric message op code -- a data payload string, or nil -- list of presences (a subset of match participants) to use as message targets, or nil to send to the whole match -- a presence to tag on the message as the 'sender', or nil match_kick = function(presences) -- a list of presences to remove from the match } Tick is the current match tick number, starts at 0 and increments after every match_loop call. Does not increment with calls to match_join_attempt, match_join, or match_leave. State is the current in-memory match state, may be any Lua term except nil. Presences is a list of users that have joined the match. Format: { { user_id: "user unique ID", session_id: "session ID of the user's current connection", username: "user's unique username", node: "name of the Nakama node the user is connected to" }, ... } Expected return these values (all required) in order: 1. An (optionally) updated state. May be any non-nil Lua term, or nil to end the match. --]] local function match_join(context, dispatcher, tick, state, presences) if state.debug then print("match join:\n" .. du.print_r(presences)) end return state end --[[ Called when one or more users have left the match for any reason, including connection loss. Loading @@ -120,7 +172,7 @@ Dispatcher exposes useful functions to the match. Format: } Tick is the current match tick number, starts at 0 and increments after every match_loop call. Does not increment with calls to match_join_attempt or match_leave. calls to match_join_attempt, match_join, or match_leave. State is the current in-memory match state, may be any Lua term except nil. Loading Loading @@ -170,7 +222,7 @@ Dispatcher exposes useful functions to the match. Format: } Tick is the current match tick number, starts at 0 and increments after every match_loop call. Does not increment with calls to match_join_attempt or match_leave. calls to match_join_attempt, match_join, or match_leave. State is the current in-memory match state, may be any Lua term except nil. Loading Loading @@ -206,6 +258,7 @@ end return { match_init = match_init, match_join_attempt = match_join_attempt, match_join = match_join, match_leave = match_leave, match_loop = match_loop }
main.go +1 −0 Original line number Diff line number Diff line Loading @@ -109,6 +109,7 @@ func main() { } leaderboardCache := server.NewLocalLeaderboardCache(logger, startupLogger, db) matchRegistry := server.NewLocalMatchRegistry(logger, db, config, socialClient, leaderboardCache, sessionRegistry, tracker, router, stdLibs, once, config.GetName()) tracker.SetMatchJoinListener(matchRegistry.Join) tracker.SetMatchLeaveListener(matchRegistry.Leave) // Separate module evaluation/validation from module loading. // We need the match registry to be available to wire all functions exposed to the runtime, which in turn needs the modules at least cached first. Loading
server/match_handler.go +66 −0 Original line number Diff line number Diff line Loading @@ -56,6 +56,7 @@ type MatchHandler struct { vm *lua.LState initFn lua.LValue joinAttemptFn lua.LValue joinFn lua.LValue leaveFn lua.LValue loopFn lua.LValue ctx *lua.LTable Loading Loading @@ -121,6 +122,10 @@ func NewMatchHandler(logger *zap.Logger, db *sql.DB, config Config, socialClient if joinAttemptFn.Type() != lua.LTFunction { return nil, errors.New("match_join_attempt not found or not a function") } joinFn := tab.RawGet(lua.LString("match_join")) if joinFn == nil || joinFn.Type() != lua.LTFunction { joinFn = nil } leaveFn := tab.RawGet(lua.LString("match_leave")) if leaveFn.Type() != lua.LTFunction { return nil, errors.New("match_leave not found or not a function") Loading Loading @@ -210,6 +215,7 @@ func NewMatchHandler(logger *zap.Logger, db *sql.DB, config Config, socialClient vm: vm, initFn: initFn, joinAttemptFn: joinAttemptFn, joinFn: joinFn, leaveFn: leaveFn, loopFn: loopFn, ctx: ctx, Loading Loading @@ -471,6 +477,66 @@ func JoinAttempt(resultCh chan *MatchJoinResult, userID, sessionID uuid.UUID, us } } func Join(joins []*MatchPresence) func(mh *MatchHandler) { return func(mh *MatchHandler) { if mh.joinFn == nil { return } mh.Lock() if mh.stopped { mh.Unlock() return } mh.Unlock() presences := mh.vm.CreateTable(len(joins), 0) for i, p := range joins { presence := mh.vm.CreateTable(0, 4) presence.RawSetString("user_id", lua.LString(p.UserID.String())) presence.RawSetString("session_id", lua.LString(p.SessionID.String())) presence.RawSetString("username", lua.LString(p.Username)) presence.RawSetString("node", lua.LString(p.Node)) presences.RawSetInt(i+1, presence) } // Execute the match_leave call. mh.vm.Push(LSentinel) mh.vm.Push(mh.joinFn) mh.vm.Push(mh.ctx) mh.vm.Push(mh.dispatcher) mh.vm.Push(mh.tick) mh.vm.Push(mh.state) mh.vm.Push(presences) err := mh.vm.PCall(5, lua.MultRet, nil) if err != nil { mh.Stop() mh.logger.Warn("Stopping match after error from match_join execution", zap.Int("tick", int(mh.tick)), zap.Error(err)) return } // Extract the resulting state. state := mh.vm.Get(-1) if state.Type() == lua.LTNil || state.Type() == LTSentinel { mh.logger.Info("Match join returned nil or no state, stopping match") mh.Stop() return } mh.vm.Pop(1) // Check for and remove the sentinel value, will fail if there are any extra return values. if sentinel := mh.vm.Get(-1); sentinel.Type() != LTSentinel { mh.logger.Warn("Match join returned too many values, stopping match") mh.Stop() return } mh.vm.Pop(1) mh.state = state } } func Leave(leaves []*MatchPresence) func(mh *MatchHandler) { return func(mh *MatchHandler) { mh.Lock() Loading