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; 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 // 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 // ensure NetworkBehaviours are valid before usage
ValidateComponents(); ValidateComponents();
@ -939,7 +939,7 @@ internal void SerializeServer(bool initialState, NetworkWriter ownerWriter, Netw
// instead of writing a 1 byte index per component, // instead of writing a 1 byte index per component,
// we limit components to 64 bits and write one ulong instead. // we limit components to 64 bits and write one ulong instead.
// the ulong is also varint compressed for minimum bandwidth. // 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. // if nothing dirty, then don't even write the mask.
// otherwise, every unchanged object would send a 1 byte dirty 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 // serialize into helper writer
using (NetworkWriterPooled temp = NetworkWriterPool.Get()) using (NetworkWriterPooled temp = NetworkWriterPool.Get())
{ {
comp.Serialize(temp, initialState); comp.Serialize(temp, true);
ArraySegment<byte> segment = temp.ToArraySegment(); ArraySegment<byte> segment = temp.ToArraySegment();
// copy to owner / observers as needed // 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); if (observersDirty) observersWriter.WriteBytes(segment.Array, segment.Offset, segment.Count);
} }
// clear dirty bits for the components that we serialized. // dirty bits indicate 'changed since last delta sync'.
// do not clear for _all_ components, only the ones that // don't clear then on full sync here, since full sync
// were dirty and had their syncInterval elapsed. // is called whenever a new player spawns and needs the
// // full state!
// we don't want to clear bits before the syncInterval //comp.ClearAllDirtyBits();
// was elapsed, as then they wouldn't be synced. }
// }
// 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. // serialize server components, with delta state for broadcast messages.
if (!initialState) comp.ClearAllDirtyBits(); // 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.
//
// 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.
//
// 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,9 +1201,8 @@ internal NetworkIdentitySerialization GetServerSerializationAtTick(int tick)
lastSerialization.ResetWriters(); lastSerialization.ResetWriters();
// serialize // serialize
SerializeServer(false, SerializeServer_Broadcast(lastSerialization.ownerWriter,
lastSerialization.ownerWriter, lastSerialization.observersWriter);
lastSerialization.observersWriter);
// set tick // set tick
lastSerialization.tick = tick; lastSerialization.tick = tick;

View File

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

View File

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

View File

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