I have a simple technical question regarding the serialization of the dynamic ghost optimization mode. The modes are describes in the documentation:
Because it is not explicitely stated: Is it correct that on dynamic optimization mode only changed ghost fields are transferred from server to client and non changed ghost fields are omitted from the data?
For example having the following component:
public struct GameUnit : IComponentData
{
[GhostField]
public ushort CurrentHealthPoints;
[GhostField]
public ushort FullHealthPoints;
}
You may imagine that the FullHealthPoints is only initially set and never changed thereafter while the CurrentHealthPoints may change frequently. Both fields are in the same component though. I only want to ensure that this kind of component design will not require unnecessary network bandwidth.
Generally speaking, yes. In the common case, we have a change bit for each GhostField, meaning each field will only be serialized if it’s actually changed. GhostFields are also delta-compressed, leading to fewer bits when changes occur.
We then also collect all of those change bits for the entire ghost, and delta-compress that change mask, leading to fewer than 1 bit being used for each field (on average).
Caveat: For non-primitive structs (e.g. float3), the GhostFieldAttribute.Composite flag can be set to true (default: false), which results in a single change bit being used for the entire struct (rather than per-field). E.g. By default, a float3 will have 3 change bits. With Composite set to true, a float3 will have 1 change bit.
You can validate this by looking at the number of bits used to serialize the ghost containing the GameUnit in the NetDbg tool (Multiplayer > Window: NetDbg (Browser)).
Thanks for the explanation. That’s interessting. This would mean when having multiple of these initially set ghost fields regarding the network bandwidth it is best to put them into a struct using the Composite flag. Then only one change bit would be required at most to show that nothing changes anymore on the FullStats fields. Then I would use this kind of component design:
public struct GameUnit : IComponentData
{
[GhostField(Composite = false)]
public GameUnitStats CurrentStats;
[GhostField(Composite = true)]
public GameUnitStats FullStats;
}
public struct GameUnitStats
{
[GhostField]
public ushort HealthPoints;
[GhostField]
public ushort ArmorPoints;
}
Also didn’t noticed the NetDbg-Tool yet. Thanks for sharing.
Currently, the float3, float2, quaternion, float4 are all always using 1 bit not matter what you value you set for the Composite field.
Using Composite flags may have good benefits but depend on the data.
For component that rarely change or for which many of the fields keep constant values, using Composite help reducing bandwidth a bit.
But then, when changed, even if delta compressed the bits cost for each individual fields that are identical is 2 bit when Huffman compressed (IIRC but should double check). So there are trade off benefits.