NetworkIdentity: SerializeServer split into Spawn and Broadcast to prepare for Unreliable sync method

This commit is contained in:
mischa 2024-09-05 12:21:21 +02:00
parent 43365aee5a
commit 32939d129e
4 changed files with 86 additions and 29 deletions

View File

@ -924,9 +924,9 @@ internal static bool IsDirty(ulong mask, int index)
return (mask & nthBit) != 0;
}
// serialize components into writer on the server.
// serialize server components, with full state for spawn message.
// check ownerWritten/observersWritten to know if anything was written
internal void SerializeServer(bool initialState, NetworkWriter ownerWriter, NetworkWriter observersWriter)
internal void SerializeServer_Spawn(NetworkWriter ownerWriter, NetworkWriter observersWriter)
{
// ensure NetworkBehaviours are valid before usage
ValidateComponents();
@ -939,7 +939,7 @@ internal void SerializeServer(bool initialState, NetworkWriter ownerWriter, Netw
// instead of writing a 1 byte index per component,
// we limit components to 64 bits and write one ulong instead.
// the ulong is also varint compressed for minimum bandwidth.
(ulong ownerMask, ulong observerMask) = ServerDirtyMasks(initialState);
(ulong ownerMask, ulong observerMask) = ServerDirtyMasks(true);
// if nothing dirty, then don't even write the mask.
// otherwise, every unchanged object would send a 1 byte dirty mask!
@ -973,7 +973,7 @@ internal void SerializeServer(bool initialState, NetworkWriter ownerWriter, Netw
// serialize into helper writer
using (NetworkWriterPooled temp = NetworkWriterPool.Get())
{
comp.Serialize(temp, initialState);
comp.Serialize(temp, true);
ArraySegment<byte> segment = temp.ToArraySegment();
// copy to owner / observers as needed
@ -981,18 +981,76 @@ internal void SerializeServer(bool initialState, NetworkWriter ownerWriter, Netw
if (observersDirty) observersWriter.WriteBytes(segment.Array, segment.Offset, segment.Count);
}
// clear dirty bits for the components that we serialized.
// do not clear for _all_ components, only the ones that
// were dirty and had their syncInterval elapsed.
// dirty bits indicate 'changed since last delta sync'.
// don't clear then on full sync here, since full sync
// is called whenever a new player spawns and needs the
// full state!
//comp.ClearAllDirtyBits();
}
}
}
}
// serialize server components, with delta state for broadcast messages.
// check ownerWritten/observersWritten to know if anything was written
internal void SerializeServer_Broadcast(NetworkWriter ownerWriter, NetworkWriter observersWriter)
{
// ensure NetworkBehaviours are valid before usage
ValidateComponents();
NetworkBehaviour[] components = NetworkBehaviours;
// check which components are dirty for owner / observers.
// this is quite complicated with SyncMode + SyncDirection.
// see the function for explanation.
//
// we don't want to clear bits before the syncInterval
// was elapsed, as then they wouldn't be synced.
// instead of writing a 1 byte index per component,
// we limit components to 64 bits and write one ulong instead.
// the ulong is also varint compressed for minimum bandwidth.
(ulong ownerMask, ulong observerMask) = ServerDirtyMasks(false);
// if nothing dirty, then don't even write the mask.
// otherwise, every unchanged object would send a 1 byte dirty mask!
if (ownerMask != 0) Compression.CompressVarUInt(ownerWriter, ownerMask);
if (observerMask != 0) Compression.CompressVarUInt(observersWriter, observerMask);
// serialize all components
// perf: only iterate if either dirty mask has dirty bits.
if ((ownerMask | observerMask) != 0)
{
for (int i = 0; i < components.Length; ++i)
{
NetworkBehaviour comp = components[i];
// is the component dirty for anyone (owner or observers)?
// may be serialized to owner, observer, both, or neither.
//
// only clear for delta, not for full (spawn messages).
// otherwise if a player joins, we serialize monster,
// and shouldn't clear dirty bits not yet synced to
// other players.
if (!initialState) comp.ClearAllDirtyBits();
// OnSerialize should only be called once.
// this is faster, and it cleaner because it may set
// internal state, counters, logs, etc.
//
// previously we always serialized to owner and then copied
// the serialization to observers. however, since
// SyncDirection it's not guaranteed to be in owner anymore.
// so we need to serialize to temporary writer first.
// and then copy as needed.
bool ownerDirty = IsDirty(ownerMask, i);
bool observersDirty = IsDirty(observerMask, i);
if (ownerDirty || observersDirty)
{
// serialize into helper writer
using (NetworkWriterPooled temp = NetworkWriterPool.Get())
{
comp.Serialize(temp, false);
ArraySegment<byte> segment = temp.ToArraySegment();
// copy to owner / observers as needed
if (ownerDirty) ownerWriter.WriteBytes(segment.Array, segment.Offset, segment.Count);
if (observersDirty) observersWriter.WriteBytes(segment.Array, segment.Offset, segment.Count);
}
// dirty bits indicate 'changed since last delta sync'.
// clear them after a delta sync here.
comp.ClearAllDirtyBits();
}
}
}
@ -1143,8 +1201,7 @@ internal NetworkIdentitySerialization GetServerSerializationAtTick(int tick)
lastSerialization.ResetWriters();
// serialize
SerializeServer(false,
lastSerialization.ownerWriter,
SerializeServer_Broadcast(lastSerialization.ownerWriter,
lastSerialization.observersWriter);
// set tick

View File

@ -1405,7 +1405,7 @@ static ArraySegment<byte> CreateSpawnMessagePayload(bool isOwner, NetworkIdentit
// serialize all components with initialState = true
// (can be null if has none)
identity.SerializeServer(true, ownerWriter, observersWriter);
identity.SerializeServer_Spawn(ownerWriter, observersWriter);
// convert to ArraySegment to avoid reader allocations
// if nothing was written, .ToArraySegment returns an empty segment.

View File

@ -50,7 +50,7 @@ public void SerializeAndDeserializeAll()
serverObserversComp.value = 42;
// serialize server object
serverIdentity.SerializeServer(true, ownerWriter, observersWriter);
serverIdentity.SerializeServer_Spawn(ownerWriter, observersWriter);
// deserialize client object with OWNER payload
NetworkReader reader = new NetworkReader(ownerWriter.ToArray());
@ -96,7 +96,7 @@ public void SerializationException()
// serialize server object
// should work even if compExc throws an exception.
// error log because of the exception is expected.
serverIdentity.SerializeServer(true, ownerWriter, observersWriter);
serverIdentity.SerializeServer_Spawn(ownerWriter, observersWriter);
// deserialize client object with OWNER payload
// should work even if compExc throws an exception
@ -187,7 +187,7 @@ public void SerializationMismatch()
serverComp.value = "42";
// serialize server object
serverIdentity.SerializeServer(true, ownerWriter, observersWriter);
serverIdentity.SerializeServer_Spawn(ownerWriter, observersWriter);
// deserialize on client
// ignore warning log because of serialization mismatch
@ -219,7 +219,7 @@ public void SerializeServer_NotInitial_NotDirty_WritesNothing()
// serialize server object.
// 'initial' would write everything.
// instead, try 'not initial' with 0 dirty bits
serverIdentity.SerializeServer(false, ownerWriter, observersWriter);
serverIdentity.SerializeServer_Broadcast(ownerWriter, observersWriter);
Assert.That(ownerWriter.Position, Is.EqualTo(0));
Assert.That(observersWriter.Position, Is.EqualTo(0));
}
@ -292,7 +292,7 @@ public void SerializeServer_OwnerMode_ClientToServer()
comp.SetValue(11); // modify with helper function to avoid #3525
// initial: should still write for owner
identity.SerializeServer(true, ownerWriter, observersWriter);
identity.SerializeServer_Spawn(ownerWriter, observersWriter);
Debug.Log("initial ownerWriter: " + ownerWriter);
Debug.Log("initial observerWriter: " + observersWriter);
Assert.That(ownerWriter.Position, Is.GreaterThan(0));
@ -302,7 +302,7 @@ public void SerializeServer_OwnerMode_ClientToServer()
comp.SetValue(22); // modify with helper function to avoid #3525
ownerWriter.Position = 0;
observersWriter.Position = 0;
identity.SerializeServer(false, ownerWriter, observersWriter);
identity.SerializeServer_Broadcast(ownerWriter, observersWriter);
Debug.Log("delta ownerWriter: " + ownerWriter);
Debug.Log("delta observersWriter: " + observersWriter);
Assert.That(ownerWriter.Position, Is.EqualTo(0));
@ -330,7 +330,7 @@ public void SerializeServer_ObserversMode_ClientToServer()
comp.SetValue(11); // modify with helper function to avoid #3525
// initial: should write something for owner and observers
identity.SerializeServer(true, ownerWriter, observersWriter);
identity.SerializeServer_Spawn(ownerWriter, observersWriter);
Debug.Log("initial ownerWriter: " + ownerWriter);
Debug.Log("initial observerWriter: " + observersWriter);
Assert.That(ownerWriter.Position, Is.GreaterThan(0));
@ -340,7 +340,7 @@ public void SerializeServer_ObserversMode_ClientToServer()
comp.SetValue(22); // modify with helper function to avoid #3525
ownerWriter.Position = 0;
observersWriter.Position = 0;
identity.SerializeServer(false, ownerWriter, observersWriter);
identity.SerializeServer_Broadcast(ownerWriter, observersWriter);
Debug.Log("delta ownerWriter: " + ownerWriter);
Debug.Log("delta observersWriter: " + observersWriter);
Assert.That(ownerWriter.Position, Is.EqualTo(0));

View File

@ -404,7 +404,7 @@ public void TestSyncingAbstractNetworkBehaviour()
NetworkWriter ownerWriter = new NetworkWriter();
// not really used in this Test
NetworkWriter observersWriter = new NetworkWriter();
serverIdentity.SerializeServer(true, ownerWriter, observersWriter);
serverIdentity.SerializeServer_Spawn(ownerWriter, observersWriter);
// set up a "client" object
CreateNetworked(out _, out NetworkIdentity clientIdentity, out SyncVarAbstractNetworkBehaviour clientBehaviour);