Best way to make RPCs with return values?

Typically, RPC(remote procedure call) may have return values. Especially when caller needs to get result or just whether calling is successful. RPCs with return values can also grantee that calculation is always made on authoritatives and prevent having to share data with NetworkVariable and calculate locally.

Currently NGO does not seem to support RPC with return type. Declaring a non-void method with [RPC] would get err

error  - RPC method must return `void`!

So currently my solution is always wrap a [RPC] void CommandxxxRpc() [RPC] void CallbackRpc() and TaskCompletionSource for every where I need a return value to achieve this like

private TaskCompletionSource<bool> _callWithReturnValueTaskCompletionSource;
        public async Task<bool> CallWithReturnValue()
        {
            _callWithReturnValueTaskCompletionSource = new TaskCompletionSource<bool>();
            CommandCallRpc();
            return await _callWithReturnValueTaskCompletionSource.Task;
        }

        [Rpc(SendTo.Server)]
        private void CommandCallRpc(RpcParams rpcParams = default)
        {
            // do something
            var success = true;
            ReportCommandResultRpc(success, RpcTarget.Single(rpcParams.Receive.SenderClientId, RpcTargetUse.Temp));
        }
        
        [Rpc(SendTo.SpecifiedInParams)]
        private void ReportCommandResultRpc(bool success, RpcParams rpcParams = default)
        {
            _callWithReturnValueTaskCompletionSource.TrySetResult(success);
        }

Is this a good idea? How do you solve this? What’s NGO’s recommended solution?

I use this Task completion as well. It has the added benefit that you can await the call:

var success = await CallWithReturnValue();

I wouldn’t prefix it “Call” though because that’s misleading. It’s a “Send” operation technically. :wink:

The alternative without a TaskCompletionSource is to simply have an “ACK” Rpc, the same as you wrapped. Basically this just without the TCS:

[Rpc(SendTo.Server)]
void SendToServerRpc(..)
{
    AckToClientRpc(result);
}

[Rpc(SendTo.ClientsAndHost)]
void AckToClientRpc(bool result)
{
}

You may want to prefer this in case the TCS causes (too many) allocations / garbage.

I have been thinking of wrapping this in its own class since I already wrap custom named messages in their own classes. I just haven’t done it because the awaiting is rarely needed, I only use it for spawning players currently.

1 Like