Net variables.. why not?
by Phil Carlisle · in Torque Game Engine · 01/31/2004 (3:59 am) · 15 replies
I'm writing some waypoint update code for my ships, so I'm sitting there writing stuff like
if mask & maskbit then write variable
over and over for different updated bits of the ship.
Then i think.. why isnt all this stuff wrapped in a class?
For instance.
NetF32 var;
var = blah;
internally to the class, this sets a "dirty" mask.
Then in packupdate
var.serialize(bitstream *stream);
which checks the dirty flag, if its set, serializes the data, if not, it
just sends the flag
var.deserialize(stream); does the inverse
Isnt this just a nicer method than having all sorts of if's in the packupdate?
At least it should be cleaner code.
What say you? its the same code, just reorganised to look a touch clearer.
if mask & maskbit then write variable
over and over for different updated bits of the ship.
Then i think.. why isnt all this stuff wrapped in a class?
For instance.
NetF32 var;
var = blah;
internally to the class, this sets a "dirty" mask.
Then in packupdate
var.serialize(bitstream *stream);
which checks the dirty flag, if its set, serializes the data, if not, it
just sends the flag
var.deserialize(stream); does the inverse
Isnt this just a nicer method than having all sorts of if's in the packupdate?
At least it should be cleaner code.
What say you? its the same code, just reorganised to look a touch clearer.
About the author
Recent Threads
#2
It would be interesting to have maybe globals of that nature, though.
01/31/2004 (9:24 am)
Not quite the same code - a lot more bits, and masking wouldn't work! Better would be to have definitions of the packet structure in script or something and have it be all code driven.It would be interesting to have maybe globals of that nature, though.
#3
There's got to be a better way of structuring that.
I think having "network" variables that are somehow automatically ghosted when changed would be the best solution.
01/31/2004 (11:42 am)
Well, the bits are they to basically tell the packet that "something" needs sending..There's got to be a better way of structuring that.
I think having "network" variables that are somehow automatically ghosted when changed would be the best solution.
#4
01/31/2004 (4:10 pm)
Of course. I think the solution there is going to be script driven. Anything else and the syntax would be more of a hassle than just writing the code directly.
#5
How is script a good way of doing that? Basically, we need some sort of "published" variables that can be created in any class which automatically get registered somehow.
This would mean that as they change, they get sent across the network.
In fact, instead of datablocks, we should have "static published" which would essentially be class static networked variables.
The point is, make it easy to define data inside a class that is automatically distributed to the client side objects.
So for instance:
class blah
{
NETPUBLISHF32 FloatValue;
STATICNETPUBLISHF32 StaticFloatValue;
};
Any changes to those values (using a operator = for example) would cause them to be marked dirty and some kind of change notification to happen.
To be honest, the mask values seem a bit redundant. Their purpose appears to be to mark a bit in the mask to say that a packet needs sending. Then the packetupdate goes and sees if anything with a mask needs sending. Plus it always has to send the flag anyway.
Why not just serialise the floatvalue and let IT determine if it just needs to flag "no change" or if it needs to flag change + new value.
I know its just for cleaner code and wont actually change anything, but it seems cleaner to me.
01/31/2004 (11:52 pm)
Naah. Why script? I'm talking about class information.How is script a good way of doing that? Basically, we need some sort of "published" variables that can be created in any class which automatically get registered somehow.
This would mean that as they change, they get sent across the network.
In fact, instead of datablocks, we should have "static published" which would essentially be class static networked variables.
The point is, make it easy to define data inside a class that is automatically distributed to the client side objects.
So for instance:
class blah
{
NETPUBLISHF32 FloatValue;
STATICNETPUBLISHF32 StaticFloatValue;
};
Any changes to those values (using a operator = for example) would cause them to be marked dirty and some kind of change notification to happen.
To be honest, the mask values seem a bit redundant. Their purpose appears to be to mark a bit in the mask to say that a packet needs sending. Then the packetupdate goes and sees if anything with a mask needs sending. Plus it always has to send the flag anyway.
Why not just serialise the floatvalue and let IT determine if it just needs to flag "no change" or if it needs to flag change + new value.
I know its just for cleaner code and wont actually change anything, but it seems cleaner to me.
#6
What I think is needed is a definition tied to the object, something like
So that the network flow is totally data driven.
02/01/2004 (1:49 pm)
The mask is so that the engine can track what's dirty for each connection/ghosted instance of the object - that's why the mask is set, saved, passed, and written.What I think is needed is a definition tied to the object, something like
class Ball {
net {
group appearance {
String name;
ColorF<6> color;
}
group position {
Point3F<7> pos;
Point3F<6> velocity;
}
}
}So that the network flow is totally data driven.
#7
"Mind thier dirty bits". Okay, so I'm never going to grow up. Works for me.
They would also need to register with their owning objects if you wanted to avoid writing any more pack/unpack functions. They'd just step through each NETPUBLISH object in their list, and send out the data as needed. They'd also need to build a mask to send so the receiving object would know what it was getting.
Ben's 'net groups' could be custom containers, emulating/duplicating NETPUBLISH's dirty/mask code. "Duplicate" is a dirty word... base class? Sure.
It would be a major rewrite of the netcode though... I suppose you wouldn't have to do everything all at once though. So long as the read and write of a given object is all done at once, it doesn't matter if the surrounding objects are speaking a different language... though ideally the data being sent wouldn't actually change, just the code sending it.
Set up properly, things would boil down to adding a NetPublish var and passing in the parent object in the parent's constructor[s]. NetPublish's constructor could require that parent, so you'd catch them all at compile time.
You might want two layers of templates. One that just reads and writes, one that manages the mask/dirty stuff. NetGroups could be composed of collections of read/write layer objects, so they'd share the same mask bit.
All this assumes a way of handing out mask bits during construction. It would need some support at the "Ball" level.
But this is all C++. Exsposing all that through script is a whole 'nother problem... requiring new keywords at the very least.
02/02/2004 (10:18 am)
NETPUBLISH sounds like a template with an overloaded "operator="... and maybe >> and << (stream operators) to mind their dirty bits."Mind thier dirty bits". Okay, so I'm never going to grow up. Works for me.
They would also need to register with their owning objects if you wanted to avoid writing any more pack/unpack functions. They'd just step through each NETPUBLISH object in their list, and send out the data as needed. They'd also need to build a mask to send so the receiving object would know what it was getting.
Ben's 'net groups' could be custom containers, emulating/duplicating NETPUBLISH's dirty/mask code. "Duplicate" is a dirty word... base class? Sure.
It would be a major rewrite of the netcode though... I suppose you wouldn't have to do everything all at once though. So long as the read and write of a given object is all done at once, it doesn't matter if the surrounding objects are speaking a different language... though ideally the data being sent wouldn't actually change, just the code sending it.
Set up properly, things would boil down to adding a NetPublish var and passing in the parent object in the parent's constructor[s]. NetPublish's constructor could require that parent, so you'd catch them all at compile time.
You might want two layers of templates. One that just reads and writes, one that manages the mask/dirty stuff. NetGroups could be composed of collections of read/write layer objects, so they'd share the same mask bit.
All this assumes a way of handing out mask bits during construction. It would need some support at the "Ball" level.
But this is all C++. Exsposing all that through script is a whole 'nother problem... requiring new keywords at the very least.
#8
The script solution is a bit slicker because it means 99% of your pack/unpack stuff is going to be a call to some data driven networking module.
02/02/2004 (5:40 pm)
The problem with your suggestion is that unless they assign themselves mask bits efficiently, you're looking at having no more than 32 such variables before the ghost system is overwhelmed and you lose net state.The script solution is a bit slicker because it means 99% of your pack/unpack stuff is going to be a call to some data driven networking module.
#9
What youre suggesting is that we have a group, which contains a list of network variables and then have the mask set those.. but the whole idea of masks is a bit hit and miss.. I'd perhaps combine the notion of groups with the NETPUBLISH model, so that the variables determine when they are "dirty" because they have been updated, the group would query each variable for dirty.
The thing is, each NETPUBLISH variable would essentially be cache'd, so you set the flag for "dirty" only if it has changed in value.
It would be useful for this to be data driven though. I agree completely.
02/06/2004 (3:13 am)
But we're already looking at 32 different variables, or rather groups of variables.What youre suggesting is that we have a group, which contains a list of network variables and then have the mask set those.. but the whole idea of masks is a bit hit and miss.. I'd perhaps combine the notion of groups with the NETPUBLISH model, so that the variables determine when they are "dirty" because they have been updated, the group would query each variable for dirty.
The thing is, each NETPUBLISH variable would essentially be cache'd, so you set the flag for "dirty" only if it has changed in value.
It would be useful for this to be data driven though. I agree completely.
#10
Masks are very key to how they work... I guess we'll have to have this out next IGC or something where I can draw you diagrams and swear at you. :P
But data driven does rock. It is the future!
02/06/2004 (2:36 pm)
I'm not sure you're understanding how the ghost system works quite right, Phil... :-/Masks are very key to how they work... I guess we'll have to have this out next IGC or something where I can draw you diagrams and swear at you. :P
But data driven does rock. It is the future!
#11
Just look at the typical usage tho..
If (mask & maskbit)
{
senddatatodowithmask();
}
Now, to me, that looks UGLY :)
The masking/sending should be automated and as you say, data driven. The act of defining the data should tie that data in with the mask.
02/06/2004 (4:08 pm)
Naah, Ive got it.. what I'm saying is that the masks are limited anyway.Just look at the typical usage tho..
If (mask & maskbit)
{
senddatatodowithmask();
}
Now, to me, that looks UGLY :)
The masking/sending should be automated and as you say, data driven. The act of defining the data should tie that data in with the mask.
#12
A policy template that is used to "register" the variable to "track" changes, much like a Ref Counting Smart pointer keeps up with who "has" it.
It would assign it a context sensitve maskbit automatically based on the class it was a member of and flip that bit everytime the variable was "changed".
The have a function or overloaded operator that packs all the changed variables up and sends them as a stream of bytes ( perfect place to put some compression and encryption in optionally )
Also this could be done very elegantly with Aspects. But done wrong ( JBOSS ) they can be a peformance killer. And AspectC++ is just not quite there yet.
02/06/2004 (8:21 pm)
This is a job for C++ TEMPLATES! A policy template that is used to "register" the variable to "track" changes, much like a Ref Counting Smart pointer keeps up with who "has" it.
It would assign it a context sensitve maskbit automatically based on the class it was a member of and flip that bit everytime the variable was "changed".
The have a function or overloaded operator that packs all the changed variables up and sends them as a stream of bytes ( perfect place to put some compression and encryption in optionally )
Also this could be done very elegantly with Aspects. But done wrong ( JBOSS ) they can be a peformance killer. And AspectC++ is just not quite there yet.
#13
Any of this is fairly easy to implement; the problem lies in what exactly is transmitted from the object. By this I mean the method of delineation of the variables themselves. At the moment you've got block-conditionals (bool flags) injected into the data-stream for the clients to read out and know if the relevant blocks are to be read.
How exactly you'd do this with adhoc variables writing to the stream on the server, I don't know. You'd have to identify them individually, possibly by using a Huffman-encoded string table to register variable IDs or something. The downside to this would be additional chuff within the stream. Statistically this may prove more efficient as you could send invidual variables rather than blocks but bulk tranfers of variables would definately be less efficient.
There'd be no reason why two couldn't be designed to work together. It would mean changing the protocol of the bit-stream though.
- Melv.
02/07/2004 (8:33 am)
I see where Phil is going with this and I'd have to agree that it looks like it could be done by using an intelligent variable class and serialisation.Any of this is fairly easy to implement; the problem lies in what exactly is transmitted from the object. By this I mean the method of delineation of the variables themselves. At the moment you've got block-conditionals (bool flags) injected into the data-stream for the clients to read out and know if the relevant blocks are to be read.
How exactly you'd do this with adhoc variables writing to the stream on the server, I don't know. You'd have to identify them individually, possibly by using a Huffman-encoded string table to register variable IDs or something. The downside to this would be additional chuff within the stream. Statistically this may prove more efficient as you could send invidual variables rather than blocks but bulk tranfers of variables would definately be less efficient.
There'd be no reason why two couldn't be designed to work together. It would mean changing the protocol of the bit-stream though.
- Melv.
#14
The point being, that its up to the variables to check if they are "dirty" and to set when they are.
Perhaps there could be some method like bens example of the group above, to define a group, define a variable for that group and define a mask for the group automatically (or using a macro).
Something that looks similar to bens description, but by creating the group, as have the mask assigned and each variable defined in the group is defined as a network published variable.
02/07/2004 (8:39 am)
Well, as Ben suggests, we can also define the grouping of the variables themselves in some manner.The point being, that its up to the variables to check if they are "dirty" and to set when they are.
Perhaps there could be some method like bens example of the group above, to define a group, define a variable for that group and define a mask for the group automatically (or using a macro).
Something that looks similar to bens description, but by creating the group, as have the mask assigned and each variable defined in the group is defined as a network published variable.
#15
I think that 32bits is probably enough - there might be cases where it's not totally efficient, but 32 is definitely enough to cover the most frequently changed data groups. And if you make it data driven, you could write a profiler to track how frequently each field is dirtied and how big it is, to help you tune it...
02/07/2004 (12:10 pm)
One issue here is that the bit status, for ghosting, has to be tracked for every client that is ghosting the current object. So right now that's pretty efficient because we use a word - the memory usage is O(#objects * #players). But I'd be loathe to increase the number of bits because you would have to do more complicate bit arithmetic (no 64 bit or instructions, for instance) and you'd up the storage requirements. If you made it dynamic, then that would make it essentially worse, since you'd have the overhead of dealing with variable size buffers.I think that 32bits is probably enough - there might be cases where it's not totally efficient, but 32 is definitely enough to cover the most frequently changed data groups. And if you make it data driven, you could write a profiler to track how frequently each field is dirtied and how big it is, to help you tune it...
Torque 3D Owner Danner Jones
-Nerseus