GameNet: Simple RPC System for Games - Sample Game included (C++11)

Started by
13 comments, last by spacerat 8 years, 5 months ago

Summary

GameNet is a client / server RPC system using newest C++11 templates to make the life easier for you. When registering RPCs, function parameters are auto-detected from the function pointer. When calling RPCs, variardic templates allow extracting data from arbitrary parameter counts.
Features
* Supports GLM datatypes for use in 3D Games
* Supported data types : (u)char,(u)short,(u)int,float,double,vector,map, GLM vec,mat and quat
* Support for data type nesting like map [ string , vector ]
* RPC Data-type mismatch or wrong number of parameters are stated as error to ease debugging
* Numbers are automatically sent as the smallest possible datatype (byte, short , .. )
* Reliable and unreliable calls possible
* Function pointers of remote functions are not required
* Based on ENet
* Compression (Enet standard)
* Hack-safe - illegal packets are discarded
* Tested on Cygwin and Windows, should compile in Linux too
* Byte order preserved (hton/ntoh)
Limitations
* RPCs cannot be class member functions
* No encryption
* Only void functions supported. Non-void functions were tested but complicated everything.
* Client to Client connections are not supported
Example Game Features
* Lobby
* Multiple Games
* Handle spawning/removing of game objects
* Simple Shooting functionality
* Intentionally in text mode to make it as simple as possible for you to adapt the code
Example Usage
Server Side:

    NetServer server;
    
    void login(uint clientid, string name, string password)
    {
        // clientid is attached as first parameter for server functions
        server.call(clientid, "set_pos", vec3(1,2,3));    
    }
    
    int main()
    {
        rpc_register_local(server.get_rpc(), login);
        rpc_register_remote(server.get_rpc(), set_pos);    
        server.start();
        core_sleep(10000) ; // wait client to do stuff
        server.stop();
    }
Client Side:

    NetClient client;
    
    void set_pos(vec3 pos)
    {
        // do something
        exit(0);
    }
    
    int main()
    {
        rpc_register_remote(client.get_rpc(), login);
        rpc_register_local(client.get_rpc(), set_pos);
        client.connect("localhost", 12345);
        client.call("login", "myname", "pass");
        while(1) client.process();
        //client.disconnect();
    }
Screenshots of the included example game:
Advertisement
Thanks for sharing!

Do you have any benchmarks for how it performs in real sized games, or any comparisons to other libraries to add to the information above?
enum Bool { True, False, FileNotFound };

Good question! So far I only compiled it on my PC.

A first simple test on localhost (Core i7 Notebook) gave:

1 Call / Network Update:

62.000 unreliable RPC calls/sec [client.call_ex(0,"hello_server", "Greetings")]

58.000 reliable RPC calls/sec [client.call_ex(1,"hello_server", "Greetings")]

10 Calls / Network Update

138.000 unreliable RPC calls/sec [client.call_ex(0,"hello_server", "Greetings")]

270.000 reliable RPC calls/sec [client.call_ex(1,"hello_server", "Greetings")]

No idea if thats good or not.. in any case strange that unreliable is slower for grouped calls. Enet drops packets that arrive with a lower number then the current - that could be a reason.

Not to pick at the code, but seeing this in header file:

using namespace std;

using namespace glm;

Makes me a little hesitant about the rest of the code.

Your encoding is broken. You are sending byte representations of complex types through a Union.

Among other things, different machines can have different byte orders, and that is before we get to the nightmare that is floating point(*).

You need to explicitly encode and decode your data.

(*) To quote the C++ Specification, "The value representation of floating-point types is implementation-defined."

Not to pick at the code, but seeing this in header file:

using namespace std;

using namespace glm;

Makes me a little hesitant about the rest of the code.

Actually I used std:: for many years, and this is the first project where i tried "using namespace". I must say it made the code really simpler and I dont have issues so far.

Update: I have enclosed the "using namespace" in namespace net now, so ppl using the lib are now independent from that issue

Your encoding is broken. You are sending byte representations of complex types through a Union.

Among other things, different machines can have different byte orders, and that is before we get to the nightmare that is floating point(*).

You need to explicitly encode and decode your data.

(*) To quote the C++ Specification, "The value representation of floating-point types is implementation-defined."

This is already on my todo list (See "* Byte order will be supported in the future (htons..)" in limitations)

Since I plan a PC / X86 only game at the moment, this issue didnt have such a high priority

Edit: Just found some time to update. Now, network byte order is supported.

hm, you said no compression, have you tried just using enet's builtin compression?
also google's snappy does a nice job of fast compression/decompression with decent ratios, and it's dead-simple to use.
for even nicer compression check out some binary delta compression, like bsdiff (https://github.com/mendsley/bsdiff).

hm, you said no compression, have you tried just using enet's builtin compression?
also google's snappy does a nice job of fast compression/decompression with decent ratios, and it's dead-simple to use.
for even nicer compression check out some binary delta compression, like bsdiff (https://github.com/mendsley/bsdiff).

Good point. I have added the enet compression now - in the benchmark the performance lowered a bit, but thats due to localhost benchmarking. For distant peers it might be a benefit

Your code is far too trusting.

For example, looking at the code, it looks like if I send something that purports to be a string, but is not null terminated, you are going to simply cast and go, resulting in either a read of random garbage, or, more likely, an access violation.

Similarly, and again, just on a read through, I can cause the server to allocate large blocks of memory and then (most likely) crash by sending a malformed vector.

This topic is closed to new replies.

Advertisement