Component Object Models as they relate to Multiplayer Networking

Started by
15 comments, last by LoneDwarf 16 years, 8 months ago
I thought I would chime in here since I am also using a component style system for my MUD. It's based off of the Scott Bilas presentation. I was wondering how far into the project's you two are? My project is at the point were I am into game play and this really starts to put theories to the test.

1) I am using a variant style system and it sounds like your guys aren't. My components implement a set/get property that passes a variant on the stack. Note that my components don't actually have to store the property as a variant. I did this so that C++ code could run faster. I have stuff like this:
void Sim_ComFrame::setValue( const STL_String& name, const Prop_Value& value ){	if ( Sim_IsName( "pos" ) == true  )	{		setMatrix( value.getMatrix4x3f() );	}}Prop_Value Sim_ComFrame::getValue( const STL_String& name ){	if ( Sim_IsName( "pos" ) == true )	{		return Prop_Value( getMatrix() );	}	else	return Prop_Value();}

I am thinking of changing this because it's becoming a hassle to manage the code. The only real use I have left for the variant (Prop_Value), is to allow for my data layout to use XML without knowing anthying about the object. Also it allows for an object inspection editor to written pretty easy. It all works but it nags of YAGNI. I keep getting the feel like I am reinventing lua tables, which is even funnier since my scripting languga is lua and I have written bindings to setValue/getValue :)
So I am mulling over writting all the data layout and editor code by hand for each component. My engine is starting to stink of middleware instead of writing my specific game. Have you guys already dealt with this sort of stuff?

2) I did the networking part of my components using a scope flag per component. So a component is flagged as client, server or both. The problem with this is that I am finding that I need to conditionally compile some componets and this makes me feel dirty. Maybe I should spit components based on their scope?
Advertisement
I've changed a few things to adapt everything to actor based model.

Each entity that is a part of game state has the following base elements:
1) VTable - a per-instance configurable multiple dispatch function table, that maps incoming messages to functions
2) Subscription Table - per-instance subscription table updated by world managers that sends internal state updates
3) Property Table - strongly typed property storage
4) Member variables

All except 4 will be inherited from base class if undefined. This leads to extremely low memory foot-print of live objects.

After introducing the Actor model, in combination with 1), 4) became a viable option for frequently accessed fields.

I have no distinction between server and client objects. All logic outside of process scheduler operates on Actor references, and can only pass messages to other actors - it can never obtain reference to actual object. As such, there's no difference between making an in-process call, shared memory inter-process call or remote call to either other cluster nodes or client.

The inheritance part comes very handy in certain situations, especially vtable. Due to large number of messages that get passed around, base object can have well over hundred handlers. Keeping all those around on per-instance basis would be very redundant, through inheritance I only take a log n penalty, where n is a single digit number. And, in addition, I can spawn instances with custom functionality.

This approach has downsides. Everything is loosely coupled. Nothing stops me from sending commands to objects that can't handle them, for example. So this needs to be logged during run-time. Other down-side is that functionality is scattered over hundreds of handlers, each oblivious to everything else. This isn't downside per-se, but it does cause an explosion of files implementing handlers.
Quote:The problem with this is that I am finding that I need to conditionally compile some componets and this makes me feel dirty.


I have some cases where a component will do:

  if (IsAuthoritative()) {    ... server side code ...  }


I prefer this to separate compilation, because it allows me to debug client and server within the same executable. The server will typically be authoritative for the object that the component lives in, whereas the client is not.

One example of such a case is the "spawn" component, which only actually spawns new objects on the server side (and the new objects are discovered using the regular object network scoping).
enum Bool { True, False, FileNotFound };
Antheus:

I am going to assume that since you're using this style of system, that your game is fairly large. So that would lead me to wonder how you are creating object definitions. I use an offline XML file that loads component and object definitions using variants. Since my properties are generic it's pretty easy to do. Then objects get created using these definitions. So how do you do it? Do you hand write something for each component type since your properties are strong typed?

Also I guess what you call views, are how you distinguish ownership. I was going to ask if the view flag was per property or at the component level, but now I am starting to wonder if you have components. Is you system basiclly an object with a bag of properties? I ask because my system is an object with a bag of components and each component is a bag of properties.

hplus0603:

Since I am a huge fan of doing things to make the code easier to debug, I can see why you might do that. The problem I have is that I am going to assume that my client will be hacked at somepoint (hopefully it will be worth the time). So I am making the client as dumb as I can get away with.

Antheus & hplus0603:

So have you guys got something playable? I ask because I am wondering if you're past the graphics demo phase and into the game logic part. That's were I have notice things start to fall apart. I am not being negative, I just want to know how tested these ideas are.




Quote:
Also I guess what you call views, are how you distinguish ownership. I was going to ask if the view flag was per property or at the component level, but now I am starting to wonder if you have components. Is you system basiclly an object with a bag of properties? I ask because my system is an object with a bag of components and each component is a bag of properties.


Yes, objects are bags of properties, and subscribers listen to a subset of those. I don't have ownership as such, objects are atomic entities.
Views determine which updates get sent to which clients - I no longer need intra-cluster shadowing, so that part is now irrelevant.

Quote:So have you guys got something playable? I ask because I am wondering if you're past the graphics demo phase and into the game logic part. That's were I have notice things start to fall apart. I am not being negative, I just want to know how tested these ideas are.


I've recently deployed the system across several machines, spawning several million objects with basic logic running. That part works as expected and predicted with regard to server and network loads, but I'm also using a fairly reliable and proven model that's currently functionally identical to stackless python.

The problems I need to solve now will be based on observations made from there, and various samples regarding data load, data locality and other aspects.

I don't deal with client or data, so that part is not my concern. Each object inherits all properties from a parent. Different object types define additional properties on top of that. Each property identifier is a unique tuple (namespace, name) value. Instances may access only the properties identified by their respective object type, and it's not possible for descendants to remove properties, they can only revert to defaults.

The ideas I'm building on are long tested in all large-scale MMOs, and all of it is document across various publications. This isn't as much about size itself, as providing scalability from ground up.

In recent testing I observed that due to transparent distributivity of objects the processing is best performed by spawning a small number of processes (2 or 3) for each physical processor, and physically distribute objects among them. Since the cost of communication through shared memory is zero (unlike network), the overall response rate improves due to shorter incoming queue sizes (which results in better cache coherency due to higher locality), despite additional context switching.

Here's where the scalability pays off. With no change to code, and without any knowledge of distribution, objects are scattered around available resources.

An added benefit of purely message-driven approach is the ability to profile exact behavior on actual per-message basis. This allows for extremely fine-grained statistics acquisition on where and how bottlenecks occur.
Quote:The problem I have is that I am going to assume that my client will be hacked at somepoint


But what does that matter? Yes, someone with a disassembler can figure out the specific rules of the game. Likely, good players do that anyway by just playing. If someone toggles the comparison and runs the authoritative code on their own machine, they will have things happen that don't match the server, which means they're giving themselves a bad experience. Seeing as their clients aren't actually servers, that won't affect anyone else.
enum Bool { True, False, FileNotFound };
I would agree that the rules being exposed isn't much of an issue, since pen and paper games have been working forever with both GM and players knowing the rules. I am more concerned about the data the rules are working on. The less they know the better off your going to be.

This topic is closed to new replies.

Advertisement