From 32939d129ebaed5f85e2314e41cc830ae0e72b78 Mon Sep 17 00:00:00 2001 From: mischa Date: Thu, 5 Sep 2024 12:21:21 +0200 Subject: [PATCH] NetworkIdentity: SerializeServer split into Spawn and Broadcast to prepare for Unreliable sync method --- Assets/Mirror/Core/NetworkIdentity.cs | 95 +++++++++++++++---- Assets/Mirror/Core/NetworkServer.cs | 2 +- .../NetworkIdentitySerializationTests.cs | 16 ++-- .../Editor/SyncVars/SyncVarAttributeTest.cs | 2 +- 4 files changed, 86 insertions(+), 29 deletions(-) diff --git a/Assets/Mirror/Core/NetworkIdentity.cs b/Assets/Mirror/Core/NetworkIdentity.cs index 746dd483d..50c55535b 100644 --- a/Assets/Mirror/Core/NetworkIdentity.cs +++ b/Assets/Mirror/Core/NetworkIdentity.cs @@ -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 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. - // - // we don't want to clear bits before the syncInterval - // 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. - if (!initialState) comp.ClearAllDirtyBits(); + // 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. + // + // 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 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(); // serialize - SerializeServer(false, - lastSerialization.ownerWriter, - lastSerialization.observersWriter); + SerializeServer_Broadcast(lastSerialization.ownerWriter, + lastSerialization.observersWriter); // set tick lastSerialization.tick = tick; diff --git a/Assets/Mirror/Core/NetworkServer.cs b/Assets/Mirror/Core/NetworkServer.cs index ccb30289c..25d59548e 100644 --- a/Assets/Mirror/Core/NetworkServer.cs +++ b/Assets/Mirror/Core/NetworkServer.cs @@ -1405,7 +1405,7 @@ static ArraySegment 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. diff --git a/Assets/Mirror/Tests/Editor/NetworkIdentity/NetworkIdentitySerializationTests.cs b/Assets/Mirror/Tests/Editor/NetworkIdentity/NetworkIdentitySerializationTests.cs index 9b8eab08e..df5fdea67 100644 --- a/Assets/Mirror/Tests/Editor/NetworkIdentity/NetworkIdentitySerializationTests.cs +++ b/Assets/Mirror/Tests/Editor/NetworkIdentity/NetworkIdentitySerializationTests.cs @@ -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)); diff --git a/Assets/Mirror/Tests/Editor/SyncVars/SyncVarAttributeTest.cs b/Assets/Mirror/Tests/Editor/SyncVars/SyncVarAttributeTest.cs index b40d7360f..1e3ba90b4 100644 --- a/Assets/Mirror/Tests/Editor/SyncVars/SyncVarAttributeTest.cs +++ b/Assets/Mirror/Tests/Editor/SyncVars/SyncVarAttributeTest.cs @@ -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);