heroiclabs / nakama

Distributed server for social and realtime games and apps.
https://heroiclabs.com
Apache License 2.0
8.82k stars 1.08k forks source link

[Feature] Allow rpc registration with indirect function references #549

Open MWFIAE opened 3 years ago

MWFIAE commented 3 years ago

Description

With nakama 3.1 it is no longer allowed to create rpc calls as anonymous functions. which means the following code will no longer work:

let InitModule: nkruntime.InitModule = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
    // Register RpcFunctions
    for (const name in rpcFunctions) {
        if (Object.prototype.hasOwnProperty.call(rpcFunctions, name)) {
            const func = rpcFunctions[name];
            initializer.registerRpc(name, func);
        }
    }
};

const rpcFunctions: RpcDictionary = {
    CheckVersion: checkVersion,
};

//I picked one as an example
function checkVersion(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string) {
    printDebug("Saving Formation!", logger);

    if (!ctx.userId) return rpcError(ErrorCode.NoUser, "No user!");

    let decoded: CheckVersionRequest;
    try {
        decoded = parseRpc(ctx, payload);
    } catch (e) {
        return rpcError(ErrorCode.Generic, "Invalid Payload");
    }

    if (decoded.VersionMajor < version.Major) return rpcError(ErrorCode.OldVersionMajor, "Old Version update your App");
    if (decoded.VersionMinor < version.Minor) return rpcError(ErrorCode.OldVersionMinor, "Old Version update your App");
    if (decoded.VersionMinor == version.Minor && decoded.VersionPatch < version.Patch)
        return rpcError(ErrorCode.OldVersionMinor, "Old Version update your App");

    return rpcResponse(ResponseCode.Ok, "");
}

Steps to Reproduce

  1. Save the rpc functions in an array or dictionary and try registering them in the way above.
  2. Start the server with nakama 3.1 Note: It does work with nakama 3.0
  3. Look into the console

Expected Result

No error message is returned and the rpc function(s) are successfully registered

Actual Result

Following error is logged in the console: nakama | {"level":"error","ts":"2021-02-08T15:50:28.427Z","caller":"server/runtime_javascript.go:1382","msg":"Failed to eval JavaScript modules.","error":"GoError: js rpc function key could not be extracted: key not found\n\tat github.com/heroiclabs/nakama/v3/server.(*RuntimeJavascriptInitModule).registerRpc.func1 (native)\n\tat index.js:280:45(48)\n"}

Context

Client: n/a

Your Environment

novabyte commented 3 years ago

@MWFIAE We discussed this on the community channel can you share the adjustments to your code which do work?

MWFIAE commented 3 years ago

Basically I had to scrap the dictionary and make the calls static like this:

let InitModule: nkruntime.InitModule = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
    initializer.registerRpc("CheckVersion", checkVersion);
    initializer.registerRpc("CreateItem", createItem);
    initializer.registerRpc("Debug", debugRpc);
    initializer.registerRpc("DeleteFormation", deleteFormation);
    initializer.registerRpc("GetGameHistory", getGameHistory);
    [... a whole bunch more...]
    };
novabyte commented 3 years ago

@MWFIAE Thanks. We'll look into it to make this easier and support more complex registration types. At the moment we extract the function pointers from the JavaScript VM to optimize the access path when the initializer is used to register the functions and match handlers in the server.

We'll see what we can do to achieve the same performance profile but with the extra convenience as well.

jquentin-lion commented 2 years ago

Any news on this? I would also like to initialize all rpc functions with one simple for loop.

Aoredon commented 2 years ago

I think this is related, I have a separate TypeScript file for the match itself and export the Match methods like so:

export let matchHandler: nkruntime.MatchHandler<nkruntime.MatchState> = {
    matchInit,
    matchJoinAttempt,
    matchJoin,
    matchLoop,
    matchLeave,
    matchTerminate,
    matchSignal
};

Then when I try to register like this:

import { matchHandler } from './match/adventure';
initializer.registerMatch('adventure', matchHandler);

I get this error:

{"level":"fatal","ts":"2022-03-08T13:56:21.411Z","caller":"main.go:146","msg":"Failed initializing runtime modules","error":"GoError: js match handler \"matchInit\" function for module \"adventure\" global id could not be extracted: not found\n\tat github.com/heroiclabs/nakama/v3/server.(*RuntimeJavascriptInitModule).registerMatch.func1 (native)\n\tat registerAdventureMatch (index.js:212:42(5))\n\tat InitModule (index.js:223:25(9))\n"}

So I tried it like @MWFIAE said and referenced each method by itself like so:

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

This works so I'm happy as I've spent a really long time trying to solve the above issue, but I do wish there was a better way at the moment.

dragonlobster commented 1 year ago

@novabyte I want to conditionally load rpc functions based on a value from ctx.env but i also get the error