Synching dynamic fields through script
by Maxime Jouin · in Torque Game Engine · 11/26/2008 (2:15 am) · 9 replies
Is there a way to sync dynamic fields created on the server, to the client, exclusively through script? What I would like to do is add a dynamic property to an object instance, in a mission file, and be able to read that new property on the client, without having to go through the explicit pack/unpack process. Is it even doable?
Thanks.
Thanks.
#2
If there's an easier more elegant way to do it, i'm all open for suggestions :)
11/27/2008 (5:28 am)
Shame this isn't implemented. I already started playing around with this and what I am trying to do is synchronize the FieldDictionary every time it changes, through the pack/unpack methods in the NetObject class. I also added a dirty flag to the dictionary that is set whenever mVersion is modified. It's not very elegant (and can have a lot of overhead because the dirty flag is set for the entire dictionary) but it's a start.If there's an easier more elegant way to do it, i'm all open for suggestions :)
#3
The problem I am facing is twofold:
- The packupdate method is correctly called on the server and wirtes all the dynamice fields to the stream. But on the client side, I never go through the unpack update loop. readFlag always returns false and I never get a stream read error!
- When a field is modified on the server side, the packupdate method is never called.
For the life of me, I cannot understand why this won't work.
I'd noticed that GameBase class does not call it's parent's packUpdate/unpackUpdate method. Fixed it, but it seems there's still something else amiss.
11/27/2008 (7:57 am)
I seem to have hit a wall. I managed to raise a dirty flag everytime a dynamic field is added or modified in the FieldDictionary, and for having debugged the mechanism, it works as expected. Then I modified the pack/unpack methods in the NetObject thus:U64 NetObject::packUpdate(NetConnection* conn, U64 mask, BitStream* stream)
{
SimFieldDictionary* FieldDict = getFieldDictionary();
if(FieldDict != NULL && FieldDict->hasChanged())
{
SimFieldDictionaryIterator itr(FieldDict);
SimFieldDictionary::Entry *entry;
while((entry = *itr) != NULL)
{
stream->writeFlag(true);
stream->writeString(entry->slotName);
stream->writeString(entry->value);
++itr;
}
FieldDict->clearChanged();
}
stream->writeFlag(false);
return 0;
}void NetObject::unpackUpdate(NetConnection* conn, BitStream* stream)
{
char slotName[256];
char value[256];
while(stream->readFlag())
{
stream->readString(slotName);
stream->readString(value);
setDataField(slotName, "", value);
}
}The problem I am facing is twofold:
- The packupdate method is correctly called on the server and wirtes all the dynamice fields to the stream. But on the client side, I never go through the unpack update loop. readFlag always returns false and I never get a stream read error!
- When a field is modified on the server side, the packupdate method is never called.
For the life of me, I cannot understand why this won't work.
I'd noticed that GameBase class does not call it's parent's packUpdate/unpackUpdate method. Fixed it, but it seems there's still something else amiss.
#4
One problem is that your not calling setMaskBits()... by default the packUpdate() is only called if the mask bits flag is non-zero.
Now instead of hijacking a mask bit, which seem to always be in demand, i would instead track down where Torque checks for them before packUpdate() is called and fix it to also check FieldDict->hasChanged().
11/27/2008 (10:01 am)
Its cool to see someone experiment with this... i've been meaning to try it for a long time, but haven't had a project that really needed it.One problem is that your not calling setMaskBits()... by default the packUpdate() is only called if the mask bits flag is non-zero.
Now instead of hijacking a mask bit, which seem to always be in demand, i would instead track down where Torque checks for them before packUpdate() is called and fix it to also check FieldDict->hasChanged().
#5
Unfortunately, I DO call setMaskBits (and yes, these suckers are so highly in demand we switched them all to U64s a long time ago!). I added as SimObject* mParent member to the FieldDictionary class and, each time the FieldDictionary is modified, it calls setMaskBits on a dynamic cast of mParent into a netObject (yes, yes, it is ugly, I know, but I'm experimenting).
Tracing the calls, the behavior of the dictionary is as it should be but the packUpdate is never ever called. Tracing into setMaskBits, the NetObject is added to the dirty list, but something is preventing its packUpdate from being called (could you at least point me to where the test for calling packUpdate is done? What exactly should I be looking at to debug this?).
Also, this does not address the issue of unpackupdate() not being called on the initial game update.
I'm completely at a loss... :(
11/28/2008 (12:38 am)
Hi Tom, thanks for seeing this through with me :) (shame hte time difference can't make this go a bit faster!)Unfortunately, I DO call setMaskBits (and yes, these suckers are so highly in demand we switched them all to U64s a long time ago!). I added as SimObject* mParent member to the FieldDictionary class and, each time the FieldDictionary is modified, it calls setMaskBits on a dynamic cast of mParent into a netObject (yes, yes, it is ugly, I know, but I'm experimenting).
Tracing the calls, the behavior of the dictionary is as it should be but the packUpdate is never ever called. Tracing into setMaskBits, the NetObject is added to the dirty list, but something is preventing its packUpdate from being called (could you at least point me to where the test for calling packUpdate is done? What exactly should I be looking at to debug this?).
Also, this does not address the issue of unpackupdate() not being called on the initial game update.
I'm completely at a loss... :(
#6
The weird thing, is that NetParent->isServerObject() always returns 'false'! I even tried issuing a 'commandToServer' to be sure to modify the object on the server. The setDirty() method is correctly called, but the isServerObject() always fails!!
Why???? :'(
11/28/2008 (2:44 am)
I modified my setDirty() method on the FieldDictionary to look like this:void SimFieldDictionary::setDirty()
{
mIsDirty = true;
NetObject* NetParent = dynamic_cast<NetObject*>(mParent);
if( NetParent && NetParent->isServerObject())
{
NetParent->setMaskBits(NetObject::DictionaryDirtyMask);
}
}The weird thing, is that NetParent->isServerObject() always returns 'false'! I even tried issuing a 'commandToServer' to be sure to modify the object on the server. The setDirty() method is correctly called, but the isServerObject() always fails!!
Why???? :'(
#7
If I connect a distant client to a dedicated server, then all goes well.
Weird...
12/01/2008 (8:08 am)
Figured out the problem, but I still don't understand why it occurs. This behavior only occurs if I start a single user game (i.e. a client and server on the same machine).If I connect a distant client to a dedicated server, then all goes well.
Weird...
#8
12/01/2008 (6:11 pm)
Do you happen to be doing this testing on a SimDatablock? In single player mode the server datablock is shared by the client and it has some special code for shortcutting packData/unpackData.
#9
12/02/2008 (12:32 am)
The flags are set by the FieldDictionary class whenever the FieldDictionary is dirtied (and I didn't trace to see where the callstack starts). As far as I can tell, the dictionary is only modified through the SimObject possessing it. I don't see where a SimDatablock might intervene in a case like this...
Associate Tom Spilman
Sickhead Games
If you wanted to do this purely in script you could send select values to the client via a commandToClient call.
If you want performance and optimized bandwidth i would write a C++ function exposed to script that takes the SimObject id and a comma separated list of fields to build a custom NetEvent to send to the client and/or clients.
In the future it would be nice to add the concept of "replication" as a first class feature of TorqueScript. For instance maybe something like:
new Player( TestPlayer ) { datablock = TestPlayerData; // replicates a full 32bit float replicate float testVar = "20"; // replicates a float 9 bits of precision replicate float9 scale = "2.4"; // replicates a single bit flag replicate bool flag = true; replicate string description = "This is a string!"; };... or something like that. When a field is changed from script its marked dirty and its packed in the next packUpdate done in C++.