Does Match Handler functions are called when a match is created from client side?

I have set up Typescript Runtime Server using this video. As per this video everything is okay. My question is, Does the Match Handler functions are called when a match is created from client side.

main.ts

let InitModule: nkruntime.InitModule = function (
  ctx: nkruntime.Context,
  logger: nkruntime.Logger,
  nk: nkruntime.Nakama,
  initializer: nkruntime.Initializer
) {
  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");
    return {
      state: { presences: {}, emptyTicks: 0 },
      tickRate: 1, // 1 tick per second = 1 MatchLoop func invocations per second
      label: "",
    };
  };

  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 (p) {
      state.presences[p.sessionId] = p;
    });
    logger.debug("Lobby match joined");
    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 (p) {
      delete state.presences[p.sessionId];
    });

    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 {
    // If we have no presences in the match according to the match state, increment the empty ticks count
    if (state.presences.length === 0) {
      state.emptyTicks++;
    }

    // If the match has been empty for more than 100 ticks, end the match by returning null
    if (state.emptyTicks > 100) {
      return null;
    }

    return {
      state,
    };
  };
  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 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,
    };
  };
  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");

    return {
      state,
    };
  };
  initializer.registerMatch("lobby", {
    matchInit,
    matchJoinAttempt,
    matchJoin,
    matchLeave,
    matchLoop,
    matchSignal,
    matchTerminate,
  });
};

// Reference InitModule to avoid it getting removed on build
// !InitModule && InitModule.bind(null);

Hi @Shahariar_Ashik,

In order to make use of your server authoritative match handler you should create the match on the server side.

You could initiate this from the client by having it call an RPC (and then having the server create the match and pass back the match ID to the client) or have the client use the matchmaker and configure a hook on the server to create the match that way.

You can see examples of both of these methods in our Lobby Guide.

1 Like

Recently I have registered two RPC’s to check if they works. But only healthcheck RPC is showing in Nakama Console, not the other one. Can you please help?

main.ts

initializer.registerRpc("matchcreate", rpcMatchcreate);
initializer.registerRpc("healthcheck", rpcHealthcheck);

matchcreate.ts

function rpcMatchcreate(
  ctx: nkruntime.Context,
  logger: nkruntime.Logger,
  nk: nkruntime.Nakama,
  payload: string
) {
  logger.info("match create rpc called");
  return JSON.stringify({ succeed: true });
}

healthcheck.ts

function rpcHealthcheck(
  ctx: nkruntime.Context,
  logger: nkruntime.Logger,
  nk: nkruntime.Nakama,
  payload: string
) {
  logger.info("healthcheck rpc called");
  return JSON.stringify({ succeed: true });
}

tsconfig.json

{
  "files": ["./main.ts", "./matchcreate.ts", "./healthcheck.ts"],
  "compilerOptions": {
    "target": "es5",
    "typeRoots": ["./node_modules"],
    "outFile": "./build/index.js",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

This RPC functions are for just checking. If this works, then I will add the code of creating match by the server in creatematch.ts file.

Are you running Nakama via docker or locally as a binary executable?

Please check that the generated JavaScript file contains the code for your rpcMatchcreate function as expected. If you’re running via Docker it can sometimes be useful to force it to rebuild your image by running:

docker compose up --build nakama

via Docker exactly as the video I attached.

Ok thanks for clarifying. Then please check that the bundled JavaScript inside the docker container is as you expect (i.e. registers the match create rpc and contains the function).

1 Like

Now after forcefully building the image it is now showing. Can you please do one more help?

let module = 'some.folder.module';
let params = {
  some: 'data',
}

let matchId: string;

try {
  matchId = nk.matchCreate(module, params);
} catch(error) {
  // Handle error
}

I wrote this code in matchcreate.ts but not working. Actually I am confused about the module=“” this option. As I am running Typescript Runtime server how should be the actual code look like?

The module parameter should be the same string that you used when registering the match handler with registerMatch.

Using the lobby guide as an example you can see where we first Register the match handler and then subsequently Create a match via an RPC using the same module name.

1 Like

Yes, I got it now. module should be equals “lobby” in my case. But now I am getting different error.
From Nakam Console:

Error whilst making RPC call: Unknown Error

From logs:

typescript-project-nakama-1  | {"level":"info","ts":"2022-11-01T11:55:32.153Z","caller":"server/runtime_javascript_logger.go:74","msg":"match create rpc called","rpc_id":"matchcreate"}
typescript-project-nakama-1  | {"level":"fatal","ts":"2022-11-01T11:55:32.163Z","caller":"server/runtime_javascript_match_core.go:103","msg":"Failed to get JavaScript match loop function reference.","mid":"7ba2caf7-356c-4180-b925-7a2e789299e4","fn":"matchInit","key":"matchInit"}

main.ts file

let InitModule: nkruntime.InitModule = function (
  ctx: nkruntime.Context,
  logger: nkruntime.Logger,
  nk: nkruntime.Nakama,
  initializer: nkruntime.Initializer
) {
  initializer.registerRpc("matchcreate", rpcMatchcreate);
  initializer.registerRpc("healthcheck", rpcHealthcheck);

  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");
    return {
      state: { presences: {}, emptyTicks: 0 },
      tickRate: 1, // 1 tick per second = 1 MatchLoop func invocations per second
      label: "",
    };
  };

  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 (p) {
      state.presences[p.sessionId] = p;
    });
    logger.debug("Lobby match joined");
    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 (p) {
      delete state.presences[p.sessionId];
    });

    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 {
    // If we have no presences in the match according to the match state, increment the empty ticks count
    if (state.presences.length === 0) {
      state.emptyTicks++;
    }

    // If the match has been empty for more than 100 ticks, end the match by returning null
    if (state.emptyTicks > 100) {
      return null;
    }

    return {
      state,
    };
  };
  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 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,
    };
  };
  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");

    return {
      state,
    };
  };
  initializer.registerMatch("lobby", {
    matchInit,
    matchJoinAttempt,
    matchJoin,
    matchLeave,
    matchLoop,
    matchSignal,
    matchTerminate,
  });
};

// Reference InitModule to avoid it getting removed on build
// !InitModule && InitModule.bind(null);

matchcreate.ts

function rpcMatchcreate(
  ctx: nkruntime.Context,
  logger: nkruntime.Logger,
  nk: nkruntime.Nakama,
  payload: string
) {
  logger.info("match create rpc called");
  let module = "lobby";
  let params = {
    some: "data",
  };

  let matchId: string;

  try {
    matchId = nk.matchCreate(module, params);
  } catch (error) {
    // Handle error
    matchId = "";
    logger.info("having error during creating match from server");
  }
  return JSON.stringify({ match_id: matchId });
}

Update: Worked. I added all the handler functions inside the InitModule in main.ts file that’s why it was not working. Now I threw all of them outside the InitModule. Now it works. Thanks a lot for the help though.

1 Like

Glad to hear you got it working.

1 Like