Client Server Ghost C++ Basics
by Spider · in Torque Game Engine · 02/05/2005 (6:24 pm) · 9 replies
I am trying to get my head around the basics of how things work with the client and the server. I don't want to change anything in that system, but only understand it enough to be able to connect it to my game, which I'm porting into Torque. Thing is, my game is written in C++ so some of this stuff I need to understand on a code level and not just a script level.
As I understand it:
-There is always a server, even if only simulated on the PC that someone is playing alone on.
-Objects are ghosted on client machines and kept track of on the server
-The server changes things in even ticks, while the client gets an advancetime in order to make movement and animations smooth
My questions specifically:
-When I do a sim::findobject am I getting the address of a ghost or a server object? can i choose?
-When I write a function that i only want to happen on the client, like a special animation that the server shouldn't have to deal with, what is the normal way of doing that?
-what parts of objects besides the load and unload generally contain code that is specific to client or server?
Any links or tips appreciated. I HAVE dug through what documentation I can find on this stuff, but I'm hoping I missed something because I don't totally get it yet...
As I understand it:
-There is always a server, even if only simulated on the PC that someone is playing alone on.
-Objects are ghosted on client machines and kept track of on the server
-The server changes things in even ticks, while the client gets an advancetime in order to make movement and animations smooth
My questions specifically:
-When I do a sim::findobject am I getting the address of a ghost or a server object? can i choose?
-When I write a function that i only want to happen on the client, like a special animation that the server shouldn't have to deal with, what is the normal way of doing that?
-what parts of objects besides the load and unload generally contain code that is specific to client or server?
Any links or tips appreciated. I HAVE dug through what documentation I can find on this stuff, but I'm hoping I missed something because I don't totally get it yet...
#2
I have one more question about this that I've been having trouble with...
My main game object is a global instance that is created in the code... That is, I have:
"CreatrixGame CG;" in my creatrixgame.cc file and "extern CreatrixGame CG;" in my other files. Does that mean that there is an instance of CreatrixGame on both the server and the client? If I understand properly, it does mean that. For some reason, though, my CG object gets processTick messages, but NEVER gets advanceTime messages. Any ideas why that would happen?
Off subject question: how do you make those boxed code snippets and separated quotes in these forums?
02/07/2005 (1:22 pm)
Wow Robert! Thanks for bottom lining that for me. That helps a bunch! I have one more question about this that I've been having trouble with...
My main game object is a global instance that is created in the code... That is, I have:
"CreatrixGame CG;" in my creatrixgame.cc file and "extern CreatrixGame CG;" in my other files. Does that mean that there is an instance of CreatrixGame on both the server and the client? If I understand properly, it does mean that. For some reason, though, my CG object gets processTick messages, but NEVER gets advanceTime messages. Any ideas why that would happen?
Off subject question: how do you make those boxed code snippets and separated quotes in these forums?
#3
02/07/2005 (2:42 pm)
In response to your off topic question, you can turn a chunk of text into a code block by surrounding it with [code][/code]. To see other tags click on the link underneath the "post a reply" box where it says "Want to use bold, italics and add links to your text? Click here to learn how"
#4
Actually, it doesn't, really: Keep in mind that there are really 3 different types of configurations when you are talking about a TGE game instance:
--single computer, single executable. This is your "standard" mode, single player only, and both the "server" and the "client" exist in the same process.
--multiple computer, one is "host+one client", others are all "one client connected to one host". I call this simply multi-player, and it's when someone "hosts" a game on their computer, and others connect to it.
--dedicated client/dedicated server: in this case, you run a true dedicated client at one computer, and all clients connect remotely to this computer. No one client is in the same execution space as the dedicated server.
The reason I'm breaking it down this way is because it really does depend, not only on which of the above modes you are talking about, but how you build and distribute your client and server executables as well. In general, when you declare a "global" c++ variable/class/data set, it is available to both the 'client' and the 'server' that are in the same execution space. In the first case above, both would have access. In the second case, the "host" would have access to both, and while the clients that aren't hosting may very well have the same variables accessable, they won't contain the same data (unless you code it so they do). In the third case above, only the dedicated server is going to have the "server" variables with valid data, although again, the client executables may have the variables accessable, just not the 'real deal' data. If you build your project well, they may not even have the variables present (pre-processing control is what I am getting at here).
My suggestion to you now would be to search through the code base using the three commands Robert provided (search in files with those as your search string), and see how they are used...it should give you a much better feel for how things work.
02/07/2005 (3:06 pm)
To answer your "on topic" question, the answer is "it depends!".Actually, it doesn't, really: Keep in mind that there are really 3 different types of configurations when you are talking about a TGE game instance:
--single computer, single executable. This is your "standard" mode, single player only, and both the "server" and the "client" exist in the same process.
--multiple computer, one is "host+one client", others are all "one client connected to one host". I call this simply multi-player, and it's when someone "hosts" a game on their computer, and others connect to it.
--dedicated client/dedicated server: in this case, you run a true dedicated client at one computer, and all clients connect remotely to this computer. No one client is in the same execution space as the dedicated server.
The reason I'm breaking it down this way is because it really does depend, not only on which of the above modes you are talking about, but how you build and distribute your client and server executables as well. In general, when you declare a "global" c++ variable/class/data set, it is available to both the 'client' and the 'server' that are in the same execution space. In the first case above, both would have access. In the second case, the "host" would have access to both, and while the clients that aren't hosting may very well have the same variables accessable, they won't contain the same data (unless you code it so they do). In the third case above, only the dedicated server is going to have the "server" variables with valid data, although again, the client executables may have the variables accessable, just not the 'real deal' data. If you build your project well, they may not even have the variables present (pre-processing control is what I am getting at here).
My suggestion to you now would be to search through the code base using the three commands Robert provided (search in files with those as your search string), and see how they are used...it should give you a much better feel for how things work.
#5
Yes, I'd found that you can't use Sim::findObject(
02/07/2005 (4:12 pm)
Hi - I'm also a Torque newbie trying to find my way around. I'm an experienced programmer, and while C is more my cup of T than C++, I know enough to get along. I have some questions relating to ghost objects on the client and server too...Quote:is the surrounding code client only, server only, or both? If both AND your running the exe stand alone then Sim::findObject will be called for the client side of things.
Yes, I'd found that you can't use Sim::findObject(
#6
This is a tricky topic, because it's based so closely on the networking model in TGE, but you're actually doing more work than you need to.
You didn't show the code in your packUpdate(), but you don't need to send the ghostIndex to the client, since the ghost index for an object (that is scoped, etc.) is the only reference the client actually has--the ghost index IS (abstractly) the object on the client's side.
Based on quick searching, all you need to do on the client side is:
conn->getGhostIndex(mObject); (note, mObject is any object you have a pointer to on the client side).
(Where conn is your client's connection to the server).
The server will use the reverse process, using the client's connection object:
clientConn->resolveGhost(ghostIndex);
Personally, I've found this type of resolving more important in scripting (a particular client sends a command to the server via serverCommand regarding a particular object). Since the client only knows about the ghostIndex of an object (it's the object itself, client side), the server has to resolve the number sent to the server's "real" version of the object:
(server side script)
(client side script)
NOTE: There isn't really a reverse functionality (for the client to be informed about the real server object ID), because you simply don't ever want the client to "know about" the real server's object...it doesn't have to. It should always act as if each client's ghostID is the "real" object, and let the server handle the mapping.
Most of the time (your mileage may vary based on implementation), you won't need to handle the conversions between server objectID and client objectGhostID when working with engine source code, since pack/unpack does all that for you.
02/07/2005 (4:35 pm)
@Rob:This is a tricky topic, because it's based so closely on the networking model in TGE, but you're actually doing more work than you need to.
You didn't show the code in your packUpdate(), but you don't need to send the ghostIndex to the client, since the ghost index for an object (that is scoped, etc.) is the only reference the client actually has--the ghost index IS (abstractly) the object on the client's side.
Based on quick searching, all you need to do on the client side is:
conn->getGhostIndex(mObject); (note, mObject is any object you have a pointer to on the client side).
(Where conn is your client's connection to the server).
The server will use the reverse process, using the client's connection object:
clientConn->resolveGhost(ghostIndex);
Personally, I've found this type of resolving more important in scripting (a particular client sends a command to the server via serverCommand regarding a particular object). Since the client only knows about the ghostIndex of an object (it's the object itself, client side), the server has to resolve the number sent to the server's "real" version of the object:
(server side script)
function serverCommandDoSomethingToMyObject(%client, %objID)
{
%obj = %client.resolveObjectFromGhostIndex(%objID);
%client.SomethingToMyObject(%obj);
}(client side script)
function ActOnObject(%object)
{
commandToServer('DoSomethingToMyObject', %object);
}NOTE: There isn't really a reverse functionality (for the client to be informed about the real server object ID), because you simply don't ever want the client to "know about" the real server's object...it doesn't have to. It should always act as if each client's ghostID is the "real" object, and let the server handle the mapping.
Most of the time (your mileage may vary based on implementation), you won't need to handle the conversions between server objectID and client objectGhostID when working with engine source code, since pack/unpack does all that for you.
#7
(BTW, what's with the @ symbols before people's names that everyone uses? Is it interpreted by the forum software or something?)
Hmm. I'm not quite doing what you describe, though... I'm not trying to get the ghost ID of the "this" object, but of a different, "third party" object.
Here's what I'm doing, specifically. The mission file currently has this:
So, I want the Celestials object to be able to take the sunname "Sun", and look up the fxSunLight object, which it (the Celestials object) then moves around the sky. The original version of the Celestials object (in Sam Iredale's daynight resource) required that fxSunLight be patched to pick up the Celestials' opinion of where it should be. I'm changing my copy, removing the Celestials-specific change from fxSunLight, and making the Celestials object call public methods on the fxSunLight object to move it.
Both the server and the client versions of the Celestials object need the pointer to Sky. On the server, it's easy - I can just use Sim::findObject(mSunName). On the client, I can't, even though the client has a copy of mSunName which equals "Sun", because Sim::findObject() on a dedicated client seems to just return NULL in all cases.
So, I solved it by sending the ghost ID of the Sun object from the Celestials server object (where I could use findObject) to the Celestials client object (where I couldn't).
So, in client-side torquescript, you have to deal exclusively with numeric object IDs, not object names? That is, you can't access objects by using %obj.function() if %obj contains a string which is an object name, only if it contains an object ID? I presume you can't use ghost IDs and object IDs interchangably, either?
02/07/2005 (5:54 pm)
@Stephen, thanks. Interesting.(BTW, what's with the @ symbols before people's names that everyone uses? Is it interpreted by the forum software or something?)
Hmm. I'm not quite doing what you describe, though... I'm not trying to get the ghost ID of the "this" object, but of a different, "third party" object.
Here's what I'm doing, specifically. The mission file currently has this:
...
new Celestials(SunCycles) {
....
sunname = "Sun";
};
new fxSunLight(Sun) {
...
};So, I want the Celestials object to be able to take the sunname "Sun", and look up the fxSunLight object, which it (the Celestials object) then moves around the sky. The original version of the Celestials object (in Sam Iredale's daynight resource) required that fxSunLight be patched to pick up the Celestials' opinion of where it should be. I'm changing my copy, removing the Celestials-specific change from fxSunLight, and making the Celestials object call public methods on the fxSunLight object to move it.
Both the server and the client versions of the Celestials object need the pointer to Sky. On the server, it's easy - I can just use Sim::findObject(mSunName). On the client, I can't, even though the client has a copy of mSunName which equals "Sun", because Sim::findObject() on a dedicated client seems to just return NULL in all cases.
So, I solved it by sending the ghost ID of the Sun object from the Celestials server object (where I could use findObject) to the Celestials client object (where I couldn't).
Quote:the ghostIndex of an object (it's the object itself, client side)
So, in client-side torquescript, you have to deal exclusively with numeric object IDs, not object names? That is, you can't access objects by using %obj.function() if %obj contains a string which is an object name, only if it contains an object ID? I presume you can't use ghost IDs and object IDs interchangably, either?
#8
Interesting approach to handling coordinated sun actions across a server-client relationship. While we haven't worked on it yet, what we planned on doing is simply to more tightly couple them via a "time server" on the dedicated server architecture, to keep the dedicated game servers and all clients synched that way, but then let the clients render on their own. A different approach would be to re-write the underlying code so that the "sun" object is actually a networked object, and updated via pack/unpack. There really isn't anything wrong with your approach that I can see, although it's probably pushing the code a bit against the grain of what it was designed for.
Regarding your follow-up question, you are correct, client side objects are referenced pretty much exclusively by their index numbers, not names. In fact, IIRC, you cannot (stock) even use the name of an object at all, because it's not in fact propagated via pack/unpack to the clients. The main reason for this (guess, but an educated one) is to further separate the client ghosted objects from the server "real" objects, to avoid security flaws in client side coding practices. You absolutely cannot do something like:
And finally, you are also correct: you cannot interchange client side object ID's and server side object ID's. You cannot even expect two different clients to have the same ghostID for the same server object--and that's intentional, again, for security purposes.
02/07/2005 (7:34 pm)
'@' means how it sounds: 'at'. As in: (I am speaking) 'at' Rob. Simply a community communications technique in long and dynamic threads.Interesting approach to handling coordinated sun actions across a server-client relationship. While we haven't worked on it yet, what we planned on doing is simply to more tightly couple them via a "time server" on the dedicated server architecture, to keep the dedicated game servers and all clients synched that way, but then let the clients render on their own. A different approach would be to re-write the underlying code so that the "sun" object is actually a networked object, and updated via pack/unpack. There really isn't anything wrong with your approach that I can see, although it's probably pushing the code a bit against the grain of what it was designed for.
Regarding your follow-up question, you are correct, client side objects are referenced pretty much exclusively by their index numbers, not names. In fact, IIRC, you cannot (stock) even use the name of an object at all, because it's not in fact propagated via pack/unpack to the clients. The main reason for this (guess, but an educated one) is to further separate the client ghosted objects from the server "real" objects, to avoid security flaws in client side coding practices. You absolutely cannot do something like:
%object = "MySun"; %object.function();%object is always an integer in script when used in this way.
And finally, you are also correct: you cannot interchange client side object ID's and server side object ID's. You cannot even expect two different clients to have the same ghostID for the same server object--and that's intentional, again, for security purposes.
#9
My approach is to have the Celestials object essentially function in the role of the time server. As it stands, it tracks time-of-day and day-of-year. My implementation has both the server and client objects tracking the time of day. The server object occasionally sends out an update which tells the client ghost copies of the Celestials object what it thinks the current time of day should be, and the clients adjust their starting times so that they agree with it to keep in synch. That happens every 60 seconds by default, but that duration is a torquescript-accessable property of the Celestials object, so it could be anything.
Then, every second (another torquescript-accessable property), the client-side Celestials object adjusts the position of the nominated fxSunLight based on its idea of the current time of day.
02/07/2005 (10:49 pm)
@Stephen, ok, thanks, that clears up a number of things.My approach is to have the Celestials object essentially function in the role of the time server. As it stands, it tracks time-of-day and day-of-year. My implementation has both the server and client objects tracking the time of day. The server object occasionally sends out an update which tells the client ghost copies of the Celestials object what it thinks the current time of day should be, and the clients adjust their starting times so that they agree with it to keep in synch. That happens every 60 seconds by default, but that duration is a torquescript-accessable property of the Celestials object, so it could be anything.
Then, every second (another torquescript-accessable property), the client-side Celestials object adjusts the position of the nominated fxSunLight based on its idea of the current time of day.
Torque 3D Owner Robert Blanchet Jr.
Dealing with animations specifically, it is best to deal with those within the advanceTime method that GameBase/ShapeBase derived objects have. This function is there specifically for advancing animation and only happens on the client.
There are essentially three functions you can call to ensure that a codeblock is on the server or the client. These functions are:
These functions are all essentially test the same condition, are we on the server or not? In fact, they all test the same flag, mNetFlags. isGhost is just another way of stating isClientObject so don't get hanged up at the name. You can ensure that a block of code is run on the client/server only by surrounding that block of code in an if statement w/ one of the above functions.
Generally speaking all code is executed on both the client and the server and there are only specific cases where this isn't so. Special functions like interpolateTick, advanceTime, unpackData, and unpackUpdate are meant for the client only. Animation and sound should only be executed on the client. These are just a few examples.
If you want a little better understanding look for the function processTimeEvent and make note of the different ways it branches for the server process and the client process.