nakama icon indicating copy to clipboard operation
nakama copied to clipboard

[Feature] Allow rpc registration with indirect function references

Open MWFIAE opened this issue 4 years ago • 6 comments

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

  • Nakama: 3.1.0
  • Database: cockroachdb CCL v19.2.5 @ 2020/03/16 18:27:12 (go1.12.12)
  • Environment name and version: Docker
  • Operating System and version: Windows 10 pro

MWFIAE avatar Feb 08 '21 16:02 MWFIAE

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

novabyte avatar Feb 08 '21 16:02 novabyte

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...]
    };

MWFIAE avatar Feb 08 '21 16:02 MWFIAE

@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.

novabyte avatar Feb 08 '21 16:02 novabyte

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

jquentin-lion avatar Feb 11 '22 20:02 jquentin-lion

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.

Aoredon avatar Mar 08 '22 14:03 Aoredon

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

dragonlobster avatar May 06 '23 03:05 dragonlobster