Categories
Game Development

Autoserialized, hook free, RPC

Traditional RPC requires several steps to setup and receive the call. First, you have to serialize the input parameters. Second, you send the serialized data, along with an identifer for what function to call. Third, you deserialize the data, and put it in some known format. Lastly, you call the function, with that format as […]

Traditional RPC requires several steps to setup and receive the call. First, you have to serialize the input parameters. Second, you send the serialized data, along with an identifer for what function to call. Third, you deserialize the data, and put it in some known format. Lastly, you call the function, with that format as the parameter.

For example, to call this function on a remote system:

void PrintNum(int i)
{
printf(“%i\n”, i);
}

I would need 3 functions:

Old way: Serialize data

void SerializeNum(int i, BitStream *bs)
{
bs->Write(i);
}

Old way: Call with serialized data

void SendRPC(BitStream *serializedData)
{
CALL(“DeserializeAndCallPrintNum”, serializedData, …);
}

Old way: Deserialize and call real function

void DeserializeAndCallPrintNum(BitStream *bs)
{
int i;
bs->Read(i);
PrintNum(i);
}

I came up with a way to get rid of Serialize and DeserializeAndCallPrintNum(). To the sender, the RPC is essentially varidic, in that the receiver can have an unknown and arbitrary number of parameters.

New way: Remote function call, passing parameter list directly

CALL(“PrintNum”, 5);

It’s less efficient, but much easier to use. The key gain from this is that no hooks are necessary. When a function is called, it can trigger itself on remote systems. Furthermore, I can inject extra parameters into the parameter list so that I know if the function was called from the network or not.

New way: Self-triggering networked function

PrintNum(10, false);

void PrintNum(int i, bool calledFromNetwork)
{
if (calledFromNetwork==false)
CALL(“PrintNum”, i); // Over the network, last parameter is injected by network code with true
printf(“%i\n”, i);
}

The process to do this is:

1. Use templates to serialize the input parameters, such that I can tell the number and size of each parameter.
2. Send the parameters across the network, along with the function name
3. Lookup the previously stored function pointer. Deserialize the parameters, expanding parameters under the size of a register to the size of a register (4 bytes).
4. Push parameters on the stack
5. Call the function

Here’s proof-of-concept code:
Autoserialized, hook free, RPC proof of concept

Drawbacks:

  1. Only works with parameters that are actually copied correctly into the stack (shallow copy, no arrays)
  2. No pointers (old system didn’t have this either)
  3. Lose flexibility to do algorithmic encoding, such as if (hasPosition) Write(position)

I might be able to fix the first point. If I can somehow detect what type of template parameter was passed maybe I can query if the object fulfills some kind of interface. The biggest drawback to the first point is that you can’t pass strings or arrays of non-fixed length. So you either have to be very wasteful, or fallback to the old system.

Leave a Reply

Your email address will not be published. Required fields are marked *