async RPC

Started by
6 comments, last by hplus0603 13 years, 9 months ago
MMORPG, seamless world split to zones, cpp


there's intensive inter-zone communication, so the task is to simplify it's implementation as much as possible.

I'm using "proxy" approach where a remote object is represented by its proxy in local zone. this proxy can communicate with its master.

something like async RPC would be helpful, that allows to simply define "remote" methods and to generate as much code as possible, ideally it could look like:

#define REMOVE_METHOD(return_type, method_name, args)


where args are a list of method parameters (type, name, special serialization flag)

this macro could generate:

- declaration of [method_name]. definition will implement game logic that should be provided by a programmer of course
- declaration and definition of forward_[method_name] that for a proxy will pack args to a specific type packet and send to its master
- declaration and definition of do_[method_name] that checks if an object is master or proxy and calls its [method_name] or forward_[method_name] respectively
- declaration and definition of receive_[method_name] that will be automatically called upon receiving specific packet, will unpack data and call [method_name]

results if any can be sent back just like another remote method call

having such system communicating with world objects would be uniform no matter where they are located and it would be extremely simple to add a new "remote" method, only specific serialization of some of its args can require additional effort (once per argument type)

first of all, what do you think about this in general? are there any similar systems that can be used as a reference?

there're two alternative ways to implement such "async RPC":
- by macros: can be tricky even using boost preprocessor library
- by code generation: e.g. by generating C++ code (pre-compile step) based on some simple syntax describing those "remote" methods

what is better (more flexible or less time consuming)? any problems in outlook with code generation?

tnx for your opinion
Advertisement
I think you're approaching this from a too low level.

First, you need to come up with a general theory for your gameplay. Is it physically simualted? Is it MUD-like? Is it using a specific rule set? What kind of interactions does the player have? Movement? Targeting? Communications? Attacks? Abilities? etc.

Once you have that, you can design a mechanism for distributing state among systems. Systems include the players' individual machines, and the servers. You need to make it so that each player machine will have all the state it needs for its particular role in the simulation (rule set, user interface), with minimum latency, without using too much bandwidth. Then you need to make it so that players issue commands to the servers, but any "important" decision has to be arbited by the server (to avoid cheating).

Once this is done, you can define a rules implementation and state persistency system. This allows you to hang data off of different entities, and express game rules in terms of the data of the entities that interact (as well as deciding how entities interact). It would also let you checkpoint state so that a player can log out and back in again without losing state.

Finally, time comes to distribute work between servers. Depending on where your coupling is, you may wish to put players that are physically proximate on the same server, for simulation consistency. Or you want to put players in the same guild on the same server. Or players of the same character class on the same server -- it entirely depends on where you can cut lines to make the most frequent interactions between players be as low latency as possible.

Now, you need to implement this design. A small part of that design involves sending messages from point A to point B. At that point, your main objective is to make it easy to address the right kind of object, easy to inspect and debug the data, but make the data as efficient as possible on the wire (so as to not waste bandwidth and cause lag). Whether you express this as an "RPC" or a "serialized object" or a "message PDU" or a "sequence of machine code" is less important, as long as it's easy for you to prove that the implementation will do the right thing, and will be able to reject rogue requests.

Speaking of rogue requests: If your RPC mechanism simply dispatches the incoming data to named functions, then you're wide open to users sending packets that they're not intended to send. For example, instead of the "cast spell X" packet, they may send the "grant player X gold" packet. Thus, it's usually quite important to delineate how different messages are authenticated within the system.

RPC samples? TorqueScript, and XDR, and DCOM, and CORBA, and a zillion other systems use R{C, although almost all of them are enterprise-ey systems which do not need low latency. TorqueScript uses unreliable UDP messaging for the low-latency messages, and only uses RPC for infrequent operations (chat, etc).
enum Bool { True, False, FileNotFound };
now I see that I had to provide more details

I'm trying to implement that "async RPC" for a game that already works on single zone server and has most of things you listed. also virtual world can be split to many zones and mobiles can move between zones. so now I'm on "interactions between zones" task. atm game objects interacts by stacking async calls. it was designed/implemented to simplify clustering support and to avoid many thread locks. and now I hope to transfer these async calls to that imaginary "async RPC". there're lots of such async calls and just implementing those pack/forward/receive/unpack methods doesn't look very pleasant. the volume of required effort allows experimenting.

so "async RPC" is required for interzone communication. it's just a messaging system on low-level + high-level that allows to define "remote" methods as simple as possible and to concentrate on game logic instead of writing tons of low-level things.

I'm familiar with DCOM and a bit with CORBA. Yeah, they are enterprise systems with all consequences. for example DCOM has many interesting approaches, but it was always resembling me a skeleton: strong enough but far away from complete body. it wasn't very convenient to use IDL to define COM interfaces but flexible. so I wonder what's better: to try to implement "async RPC" w/o code generation at all or to not bother about this and to write python script to generate required c++ code.
There is no "better." Almost all RPC systems, from Sun XDR, used with NFS, through DCOM and CORBA, end up using an IDL.
You can view your macros as an IDL that happens to be executed by the pre-processor.
At the level you're talking about, "async RPC" and "message sending/dispatching" are more or less equivalent -- you've just defined one particular way that the user interacts with the messaging format, and how the messaging format gets dispatched within the target system.

Also, when doing RPC, it's important to make clear how you deal with object addressing and object lifetime. If you have asynchronous messaging, the target of a given RPC may die after the RPC has already been sent, but before it has been dispatched. If the sending entity has some state where it's waiting for a response, that state may be kept forever otherwise, as there won't be a response.

Personally, I find these kinds of protocols/state machines/interactions easier to model as messaging, but there really is no logical difference between the two. Use whatever you feel is easier to implement and debug for yourself.
enum Bool { True, False, FileNotFound };
We use an in house remote method system that autogenerates the files needed to make the call. (proxy object that calls into a dispatcher object a class can inherit from) Its not a big deal to set up pre-build steps in makefiles or visual studio.

If you decide to make an in house solution - realize that the proxy (caller) and dispatcher (receiver) will have to know about the objects its serializing. So you dont want a instancing server call into a gamecode endpoint - for instance. Since now, your instancing servers neesd to compile the gamecode objects, which may require it to compile all your gamecode, depending on how clean your dependencies are(this might be obvious, but since we already screwed up here i'm listing this warning!)

found this on google
http://www.codeproject.com/KB/threads/Rcf_Ipc_For_Cpp.aspx
but i have no experience with it, but it might give you some ideas to toy with.

You might also get useful advice by looking at how signaling systems are made.

Personally i like autogenreated code rather than template wizardry
www.ageofconan.com
Quote:Original post by @
are there any similar systems that can be used as a reference?


Some C++ implementation documentation:
http://www.replicanet.com/assets/docs/Main/html/classRNReplicaNet_1_1DataBlock__Function.html

Basically it uses a description language to generate macros that generate a helper class which can call member functions or update member variables.

The description language:
http://www.replicanet.com/assets/docs/Main/html/classRNReplicaNet_1_1Function.html
http://www.replicanet.com/assets/docs/Main/html/classRNReplicaNet_1_1object.html#498944623f8bfeb153926c6ed7f75248



-- Martin PiperReplicaNet Multiplayer Middlewarehttp://www.replicanet.com/
Quote:Original post by _Kami_
found this on google
http://www.codeproject.com/KB/threads/Rcf_Ipc_For_Cpp.aspx
but i have no experience with it, but it might give you some ideas to toy with.


I tried that one a while back, and it crashed VS 2005 or 2008 really hard. There is simply too much template magic going on. Even when it worked, the compilation time really took a hit.
Note that the subtle difference between "async" RPC and "normal" RPC actually isn't very subtle at all when it comes to implementation and performance.

Regular RPC will generally end up marshaling entire objects. Something like, "marshal the state of this client object, then the request, then send to the server, which chews on it, and then sends back a new marshaled state of the object, which I will demarshal-in-place into this object."
That is heavy-handed, high overhead, and not a good match for client/server games in general. It is also not very thread friendly.

When your RPC is asynchronous, that kind of terror doesn't usually happen. The reason is that you don't know when or if the RPC call will have a result, so you don't know when the returned state of the object will be updated/reflected. Thus, you're not tempted to marshal full object states. Similarly, object updates tend to be done through async RPCs going the other way, through some kind of subscription, which tends to lend itself well to selective property updating.

Thus, when something just says "RPC," I tend to view it with a high degree of skepticism, and that skepticism is almost always warranted when I get to see the details. When you add "async," and the RPC reduces down to messaging based on ID-targeted object instances (method call == message send), you have at least a fighting chance of building something that can be used in multi-player games.
enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement