RPC3 is the third iteration of support for remote procedure calls, superceeding RakPeer::RPC and the AutoRPC plugin. With the help of the 3rd party library Boost, it uses template metaprogramming to automatically write custom C++ code based on function parameter types. Like the prior systems, it allows you to call procedures on remote systems using the same syntax as if they were local, and without the need to serialize or deserialize parameters. If you do not want to use Boost, use the older AutoRPC system instead.
Because this plugin has a third party dependency, the code is located at \DependentExtensions\RPC3 rather than \Source as the other plugins. Feel free to copy the code to \Source or with the other code as you wish.
A simple example demonstrating that you do not need to serialize packets or use Message Identifiers with this plugin.
// Remote system
RPC3_REGISTER_FUNCTION(&rpc3Inst, PrintNumber);
void PrintNumber(int i) { printf("%i", i); };
...
// Local system
RakPeerInterface *rakPeer;
RakNet::RPC3 rpc3Inst;
rakPeer->AttachPlugin(&rpc3Inst);
rpc3Inst.Call("PrintNumber", 10);
Boost 1.35 or later. Download from http://www.boost.org/users/download/. The sample project settings assume it is installed at C:\boost_1_35_0.
- Standard variables:
int, char, short, long, float, etc
- C style strings:
"Hello world", char *myString;, etc.
- Native raknet types:
Bitstream, RakString, RakNetTime, NetworkID, SystemAddress, etc.
- Enumerations:
int_32, MessageID, ushort, etc.
- Arrays.
int intArray[10]; Call("MyFunc", intArray);
- Pointers to arrays:
int intArray[10]; int *intPtr = &intArray; Call("MyFunc", RakNet::_RPC3::PtrToArray(10,intPtr));
- Pointers to objects or variables:
int i; int *intPtr = &i;
- Standard classes and structures references and pointers:
struct Vector{float x; float y; float z;}; Call("MyFunc", &vector);
- Objects that derive from NetworkIDObject:
class Player : public NetworkIDObject {}; Call("MyFunc", &player);
1. Class pointers and references:
Pointers and references are automatically dereferenced up to one time to serialize the contents of the pointed to class or structure. These three calls all do the same thing:
struct Vector {float x; float y; float z};
void RemotelyPrintVector(Vector *v) {rpc3.Call("PrintVector", v);}
void RemotelyPrintVector(Vector &v) {rpc3.Call("PrintVector", v);}
void RemotelyPrintVector(Vector v) {rpc3.Call("PrintVector", v);}
However, only a single level of indirection is currently supported. This code would send the pointer address, which is usually not what you want.
struct Vector {float x; float y; float z};
void RemotelyPrintVector(Vector **v) {rpc3.Call("PrintVector", v);}
Use this instead:
struct Vector {float x; float y; float z};
void RemotelyPrintVector(Vector **v) {rpc3.Call("PrintVector", *v);}
...
void PrintVector(Vector *v) {printf("%f %f %f", v->x, v->y, v->z);
2. Pointers to arrays
Sometimes you need to specify RakNet::_RPC3::PtrToArray so the system knows a pointer is actually an array.
void DoItFirst()
{
int intArray[10];
rpc3.Call("DoThat", intArray); // OK
}
void DoItSecond()
{
int intArray[10];
DoItThird(intArray); // passes array to following function
}
void DoItThird(int intArray[10])
{
rpc3.Call("DoThat", intArray); // Wouldn't work, intArray is actually a pointer. Data would arrive as garbage
rpc3.Call("DoThat", RakNet::_RPC3::PtrToArray(10,intArray)); // Using PtrToArray fixes this
}
void DoThat(int intArray[10])
{
for (int i=0; i < 10; i++) printf("%i ", intArray[i]);
}
3. Structures and classes with overloaded operator << and operator >>
Sometimes you want to individually serialize every member of a structure, rather than essentially memcpy it out. One reason is to do endian swapping on each parameter. Another reason is to do conditional serialization, such as only sending part of a structure.
To do so, provide an implementation of operator << and operator >> specialized for RakNet::BitStream. Following our prior example with the Vector class:
struct Vector {float x; float y; float z};
RakNet::BitStream& operator<<(RakNet::BitStream& out, Vector& in)
{
// The BitStream class will automatically endian swap x,y, and z if you comment out __BITSTREAM_NATIVE_END in RakNetDefines. So this code could run between Macs and PCs.
out.Write(in.x);
out.Write(in.y);
out.Write(in.z);
return out;
}
RakNet::BitStream& operator>>(RakNet::BitStream& in, Vector& out)
{
out.Read(in.x);
out.Read(in.y);
out.Read(in.z);
return in;
}
4. Pointers to structures and classes that derive from NetworkIDObject
The NetworkIDObject class is used to lookup object pointers by NetworkID. RPC3 can do this for you automatically when passed a pointer to a class that derives from NetworkIDObject.
In the following example, the RPC3 class will automatically call player->GetNetworkID(), serialize and send the NetworkID, look it up on the remote system, and call the function with the correct pointer
class Player : public NetworkIDObject
{
int health;
}
void KillPlayer(Player *player)
{
if (player==0)
{
printf("GET_OBJECT_FROM_ID lookup failed.\n");
}
}
rpc3.Call("KillPlayer", &primaryPlayer);
That this only works for pointers to classes that are convertible to NetworkIDObject. "void KillPlayer(Player *player)" in the example above would not work - it would essentially just memcpy the Player class instance.
By default, this does not automatically serialize the contents of the Player class. This is keeping inline with how pointers work, which just refer to objects, rather than actually sending the object. You can also serialize the player by using
RakNet::_RPC3::Deref()
For example:
// This would serialize Player in addition to just sending Player::GetNetworkID(). The prior example would only send the latter.
rpc3.Call("KillPlayer", RakNet::_RPC3::Deref(&primaryPlayer));
5. char *, unsigned char*, const char *, const unsigned char *
These 4 types are treated as standard C strings, and are serialized using RakString::Serialize(); If you want to actually copy an array of bytes, use:
RakNet::_RPC3::PtrToArray(numBytes,charArrayPtr);
6. Pointer to RakNet::RPC3.
void DoSomething(int i, RakNet::RPC3 *rpc3)
{
if (rpcFromNetwork==0)
printf("\nDoSomething called locally\n");
else
printf("\nDoSomething called from %s\n", rpcFromNetwork->GetLastSenderAddress().ToString());
printf("i=%i\n", i); // Should be 5
}
rpc3.Call("DoSomething", 5, 0);
On the sender, parameters of type RakNet::RPC3* are ignored. On the reciever, it is changed to match the instance of the RPC3 plugin that is handling the packet. This way you can determine if a function was called from the network, or locally, and can read parameters such as the sender system address.
void PrintNumber(int i, char c)
{
printf("%i %i", i, c);
}
struct Bytes
{
char bytes[20];
}
void PrintString(Bytes b)
{
printf("%s", b.bytes);
}
class MyClass
{
public:
int a;
MyClass() {a=10;}
virtual PrintClass(void) {
printf("%i", a);
}
}
Create an instance of the RPC3 plugin:
RPC3 rpc3;
Attach the plugin to an instance of RakPeerInterface:
rakPeer->AttachPlugin(&rpc3);
If calling C++ class member functions, create (or use an existing) instance of NetworkIDManager
NetworkIDManager networkIDManager;
If calling C++ class member functions, tell the plugin about your instance of NetworkIDManager
rpc3.SetNetworkIDManager(&networkIDManager);
void MyFunc() {}
class MyClass
{
void ClassFunc() {}
}
rpc3.RegisterFunction("MyFunc", MyFunc);
rpc3.RegisterFunction("&MyClass::ClassFunc", &MyClass::ClassFunc);
A macro can do both at once if you want
RPC3_REGISTER_FUNCTION(&rpc3Inst, CFunc);
RPC3_REGISTER_FUNCTION(&rpc3Inst, &MyClass::ClassFunc);
You don't need the ampersand in the string function identifier, but the macro does this automatically.
rpc3.Call("myFunction", param1, param2, param3);
To designate the packet recipient, if this is a C call or C++ member function, etc. use the functions in the RPC3 class. See RPC3.h for parameter documentation:
void SetTimestamp(RakNetTime timeStamp);
void SetSendParams(PacketPriority priority, PacketReliability reliability, char orderingChannel);
void SetRecipientAddress(SystemAddress systemAddress, bool broadcast);
void SetRecipientObject(NetworkID networkID);
On error, the remote system will send ID_RPC_REMOTE_ERROR. The error code is held in the 2nd byte, e.g. packet->data[1]. The name of the function starts at the 3rd byte, e.g. packet->data[2]; See the enumeration RPCErrorCodes for a full list of error codes.
There are two ways to get access to the sender, timestamp, and other parameters relevant to recieving a packet.
First, you can query the plugin if you know contextually that the called function is triggered from the network:
RakNetTime GetLastSenderTimestamp(void) const;
SystemAddress GetLastSenderAddress(void) const;
RakPeerInterface *GetRakPeer(void) const;
const char *GetCurrentExecution(void) const;
Second, you can add an RPC3 pointer to your function parameter list. This will be filled in automatically by the plugin when the function is called.
See the sample project RPC3 for a detailed implementation of this system. |