Lua Runtime Match_Loop messages never populated

Using the Lua runtime to implement authoritative match logic.

I am using the defold client sdk.

Sending data like this:

local result = socket.match_data_send(matchid, opcodes.send_input, data)

On the serverside:

function M.match_loop(context, dispatcher, tick, state, messages)
  for _, p in pairs(state.presences) do
   -- nk.logger_info(string.format("Presence %s named %s", p.user_id, p.username))
  end

  print(dump(messages))

  for _, m in ipairs(messages) do
    nk.logger_info(string.format("Received %s from %s", m.data, m.sender.username))
    local decoded = nk.json_decode(m.data)
    for k, v in pairs(decoded) do
      nk.logger_info(string.format("Key %s contains value %s", k, v))
   end
 end
  local sendState={}
  sendState.players=state.players
  dispatcher.broadcast_message(opcodes.position, nk.json_encode(sendState.players), nil)
  return state
end

However, messages table is never populated no matter what messages are being sent.
The debug logger shows messages being received

{"level":"debug","ts":"2024-07-10T17:18:31.705-0400","caller":"server/pipeline.go:65","msg":"Received *rtapi.Envelope_MatchDataSend message","uid":"2f1ac17d-b374-4aa2-85b1-34a73ee10af2","sid":"ea506ba6-3f01-11ef-b43b-e0e9830395f8","message":{"MatchDataSend":{"match_id":"89786132-d824-41e1-ad7d-820960662c7d.doomscroller","op_code":3,"data":"eyJ4IjoxLjA3MDY4ODAzMDE2MDIsInkiOjB9"}}}

The matchid is correct, however the matchloop messages table just never gets populated with anything. What am I missing?

Hello @Gwenny,

Are you creating an authoritative match by calling match_create (example: nakama/data/modules/match_init.lua at master · heroiclabs/nakama · GitHub) either via the matchmaker_matched hook or a custom RPC?

Best.

Yes… I have done that. I can see it in the console and everything. I have an rpc that creates a match, the user then joins the match with its match id, everything else is working as documentation show. The problem is simply that the messages are never populated. I have tried both the lua runtime and typescript. I raised an issue on github but it was closed and I was told to ask here but there’s like no activity here and I’m waiting all day for any assistance because I am doing everything as I’m supposed to. It’s a little bit frustrating. Ofc I’ve created an authoritative match…
Is there a discord or anything beyond the forums? There’s very little activity here. It’s difficult to wait an entire day for a single response when trying to get something done.

@Gwenny can you please share the relevant client side code you’re using to create the match and send the data?

EDIT: I suspect the issue is that you’re trying to send data to a match you haven’t yet joined and you’re not seeing any error because you aren’t listening to the error events on the socket.

I have already stated I have joined a match… How can I have not joined a match if I have a presence??? Isn’t it trivially obvious whether or not a match is joined?

Well… Here it is I guess…

local function create_match(context, payload)
  local modulename = "normal_run"
  local setupstate = { initialstate = payload }
  local matchid = nk.match_create(modulename, setupstate)

  return nk.json_encode({ matchid = matchid })
end

nk.register_rpc(create_match, "create_match_rpc")

and

            nakama.rpc_func(client, "create_match_rpc", json.encode(payload), nil, function(result)
              
                local response = json.decode(result.payload)
                print("response here:")
                print(response["matchid"])
                if response["matchid"] ~= nil then
                    nakama.sync(function()
                        matchid=response["matchid"]
                        match = socket.match_join(response["matchid"])

                        if match.error then
                            print(match.error.message)
                        end
                        in_match()
                        monarch.show(hash("game"))

                    end)
                end
            
            end)
        end
    
        if err then
          print(err.message)
        end
    end)

Why are you assuming that I haven’t joined a match… Isn’t it trivially obvious whether or not a match is joined? I can see that the match is created, I can see that the match has been joined. I can see that it has its spitting out the presence. Am I misunderstanding something? Can you have a presence without joining the match somehow?

 local result = socket.match_data_send(match.match["match_id"] , opcodes.send_input, data)

if result.error then
                print(result.error.message)
                pprint(result)
            end

And ofc I’m printing out all the errors…Sorry I’ve just been trying to figure this out for days now and there’s no place to get any help except here… once a day… And I’m really trying to figure out what the problem is. Isn’t it extremely obvious whether or not you’ve joined a match? There’s a console that shows you matches and their presences. So I’m not sure why this is supposedly the problem. It’s quite frustrating to have progress grind to a halt, all the documentation to be outdated, and the only place to find help is a pretty inactive forum in another timezone. There really should be a discord.

@Gwenny I’m sympathetic with your frustration but we do try to be as helpful as possible, if you’ve spotted any errors in the documentation please point them out so that we can improve and fix them.

We try our best to give timely responses in the forums but it is not realistic to expect them to be immediate.

Please make sure your code is listening and handling/logging on_error events as in the example here: GitHub - heroiclabs/nakama-defold: Defold client for Nakama server..

You’re calling match_join via the socket, but this operation is asynchronous, it’s possible that you may be calling match_data_send before the user has actually joined the match, which will cause the server to receive the message but not send it to the match.

The function should take an optional callback fn that you can use to ensure that you only send data after you get the response back from the match_join call. Using this callback may also be useful to add debug logs.

Also please make sure that you’re correctly handling joining the match in the runtime match handlers, it’s possible you’re not allowing the user to join via the match_join_attempt and match_join functions.

Double check the server logs, if this still won’t fix the issue please provide more information: all the code required to reproduce, both client and server, both client and server version, etc.

Best.

I have done all those things…The user is most definitely. In the match. Please take my word for it. I have already stated that. It completely just doesn’t work.I keep telling you the user has joined. Ofc I have checked the logs. You are just assuming I’ve down something really basic wrong. But I keep saying that I can confirm via multiple ways that the user should be in the match. I’ve been trying to figure this out for days. The user is in the match. The user is in the match. I have presences. The message just never progresses to the match_loop…Are you saying I can have a valid presence but not actually be in the match?

@Gwenny Either there’s a bug in your code or a regression was introduced in the server. We have projects using Nakama in production at scale with the Lua runtime without issue which usually rules out a regression, but we’re open to the possibility.

We’re asking if you can provide a full example of your client and server code (or a minimal reproducible test case) so we can test and fix as needed, no matter where the issue root cause is. It’s not a question of you doing anything wrong or any belief on our part, a test case is simply more useful for diagnostic purposes.

I said I’ve tried both typescript runtime and lua runtime so I just assume maybe its a problem with the defold sdk.

But here’s my client code
I create and join a match like this,

function start_a_run()
    nakama.sync(function()

        local ok, err = socket.connect()

        if ok then
            local payload ={
            }
            nakama.rpc_func(client, "create_match_rpc", json.encode(payload), nil, function(result)
              
                local response = json.decode(result.payload)
                print("response here:")
                print(response["matchid"])
                if response["matchid"] ~= nil then
                    nakama.sync(function()
                        matchid=response["matchid"]
                        match = socket.match_join(response["matchid"])

                        if match.error then
                            print(match.error.message)
                        end
                        in_match()
                        monarch.show(hash("game"))

                    end)
                end
            
            end)
        end
    
        if err then
          print(err.message)
        end
    end)
end

trying to send match data like this,

function update_inputs() 
    if match==nil or matchid == nil or not match_loaded then
        return
    end

  
    local data = json.encode({
        x = input.x,
        y = input.y
      })

      nakama.sync(function()

            local mid= match.match["match_id"]
            print("send to", mid)
       
            local result = socket.match_data_send(mid , opcodes.send_input, data)

           
        
            if result.error then
                print(result.error.message)
                pprint(result)
            end

        end)
    end

my create match rpc


local function create_match(context, payload)
  local modulename = "normal_run"
  local setupstate = { initialstate = payload }
  local matchid = nk.match_create(modulename, setupstate)

  return nk.json_encode({ matchid = matchid })
end

nk.register_rpc(create_match, "create_match_rpc")

my entire match handler

const enum opcodes {
position=1,
two,
receive_input

};

type Relic = {

};

type Input = {
x: number,
y: number
};

type Player = {
 x: number,
 y: number,
 relics: Relic[],
 input: Input
};

type SendState = {

players:  {[userId: string]: Player}

}	

const matchInit = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, params: {[key: string]: string}): {state: nkruntime.MatchState, tickRate: number, label: string} {
  logger.debug('Lobby match created');

  const presences: {[userId: string]: nkruntime.Presence} = {};
  const players: {[userId: string]: Player}={};
  return {
    state: { presences,players },
    tickRate: 10,
    label: 'normalrun'
  };
};

const matchJoinAttempt = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presence: nkruntime.Presence, metadata: {[key: string]: any }) : {state: nkruntime.MatchState, accept: boolean, rejectMessage?: string | undefined } | null {
  logger.debug('%q attempted to join Lobby match', ctx.userId);

  return {
    state,
    accept: true
  };
}

const matchJoin = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence[]) : { state: nkruntime.MatchState } | null {
  presences.forEach(function (presence) {

    let player : Player = {
    x:0,
    y:0,
    relics: [],
    input: {x:0,y:0}
    }
    state.presences[presence.userId] = presence;
    state.players[presence.userId] = player;
    logger.debug('%q joined Lobby match', presence.userId);
  });

  return {
    state
  };
}

const matchLeave = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence[]) : { state: nkruntime.MatchState } | null {
  presences.forEach(function (presence) {
    delete (state.presences[presence.userId]);
    logger.debug('%q left Lobby match', presence.userId);
  });

  return {
    state
  };
}

const matchLoop = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, messages: nkruntime.MatchMessage[]) : { state: nkruntime.MatchState} | null {
  
 // logger.info('match loop');
  Object.keys(state.presences).forEach(function (key) {
    const presence = state.presences[key];
   // logger.info('Presence %v name $v', presence.userId, presence.username);
  });

  //logger.info('message count: %v', messages.length);
  messages.forEach(function (message) {
    logger.info('Received %v from %v', message.data, message.sender.userId);
    
    dispatcher.broadcastMessage(2, message.data, [message.sender], null);
  });


  let send : SendState = { players: state.players }

if (tick%100 == 0) 
{ 
	dispatcher.broadcastMessage(opcodes.position, JSON.stringify(send), null, null);
}
  return {
    state
  };
}

const matchTerminate = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, graceSeconds: number) : { state: nkruntime.MatchState} | null {
  logger.debug('Lobby match terminated');

  const message = `Server shutting down in ${graceSeconds} seconds.`;
  dispatcher.broadcastMessage(2, message, null, null);

  return {
    state
  };
}

const matchSignal = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, data: string) : { state: nkruntime.MatchState, data?: string } | null {
  logger.debug('Lobby match signal received: ' + data);

  return {
    state,
    data: "Lobby match signal received: " + data
  };
}

my init module (again I switched to typescript to try and figure out what was wrong.



let InitModule: nkruntime.InitModule =
        function(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
    logger.info("Hello World!");

  initializer.registerMatch('normal_run', {
    matchInit,
    matchJoinAttempt,
    matchJoin,
    matchLeave,
    matchLoop,
    matchSignal,
    matchTerminate
  });

}

@Gwenny I successfully managed to send, print and broadcast a message in the lua runtime, I used the following code:

match_loop:

local function match_loop(context, dispatcher, tick, state, messages)
  for _, p in pairs(state.presences) do
    nk.logger_info(string.format("Presence %s named %s", p.user_id, p.username))
  end

  for _, m in ipairs(messages) do
    nk.logger_info(string.format("Received: %s", m.data))
    dispatcher.broadcast_message(1, m.data, nil, m.user_id)
  end

  return state
end

defold:

local config = {
		host = "127.0.0.1",
		port = 7350,
		use_ssl = false,
		username = "defaultkey",
		password = "",
		engine = defold,
		timeout = 10, -- connection timeout in seconds
	}
	local client = nakama.create_client(config)

	nakama.sync(function()
		client.authenticate_custom("uniqueidentifier", nil, false, nil, function(session)
			-- Use the token to authenticate future API requests
			nakama.set_bearer_token(client, session.token)

			local socket = client.create_socket()

			socket.on_error(function(message)
				print("Error: " .. message)
			end)

			socket.on_disconnect(function(message)
				print("Disconnected!")
			end)

			socket.on_match_data(function(message)
				print("Message received: " .. message.match_data.data)
			end)

			nakama.sync(function()
				local ok, err = socket.connect()
				if err then 
					print("failed to connect socket" .. err)
				end

				client.rpc_func("create_match_rpc", json.encode({}), nil, function(result)
					local response = json.decode(result.payload)
					print("match_id: " .. response["matchid"])
					if response["matchid"] ~= nil then
						nakama.sync(function()
							local matchid = response["matchid"]
							match = socket.match_join(matchid)

							if match.error then
								print(match.error.message)
							end

							local data = json.encode({
								dest_x = 1.0,
								dest_y = 0.1,
							})
							socket.match_data_send(matchid, 1, data)
						end)
					end
				end)
			end)
		end)
	end)

I used the match create RPC code you’ve provided.

Hope this helps.

1 Like