mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
perf: NetworkIdentity DirtyComponentsMask code removed entirely. OnSerialize now includes the component index as byte before serializing each component. Faster because we avoid GetDirtyComponentsMask() and GetSyncModeObserversMask() calculations. Increases allowed NetworkBehaviour components from 64 to 255. Bandwidth is now smaller if <8 components, and larger if >8 components. Code is significantly more simple.
This commit is contained in:
parent
4c6791852e
commit
47dd2ef663
@ -242,11 +242,11 @@ public NetworkBehaviour[] NetworkBehaviours
|
||||
void CreateNetworkBehavioursCache()
|
||||
{
|
||||
networkBehavioursCache = GetComponents<NetworkBehaviour>();
|
||||
if (networkBehavioursCache.Length > 64)
|
||||
if (networkBehavioursCache.Length > byte.MaxValue)
|
||||
{
|
||||
Debug.LogError($"Only 64 NetworkBehaviour components are allowed for NetworkIdentity: {name} because of the dirtyComponentMask", this);
|
||||
Debug.LogError($"Only {byte.MaxValue} NetworkBehaviour components are allowed for NetworkIdentity: {name} because we send the index as byte in order to save bandwidth.", this);
|
||||
// Log error once then resize array so that NetworkIdentity does not throw exceptions later
|
||||
Array.Resize(ref networkBehavioursCache, 64);
|
||||
Array.Resize(ref networkBehavioursCache, byte.MaxValue);
|
||||
}
|
||||
}
|
||||
|
||||
@ -869,48 +869,41 @@ bool OnSerializeSafely(NetworkBehaviour comp, NetworkWriter writer, bool initial
|
||||
/// <para>We pass dirtyComponentsMask into this function so that we can check if any Components are dirty before creating writers</para>
|
||||
/// </summary>
|
||||
/// <param name="initialState"></param>
|
||||
/// <param name="dirtyComponentsMask"></param>
|
||||
/// <param name="ownerWriter"></param>
|
||||
/// <param name="ownerWritten"></param>
|
||||
/// <param name="observersWriter"></param>
|
||||
/// <param name="observersWritten"></param>
|
||||
internal void OnSerializeAllSafely(bool initialState, ulong dirtyComponentsMask, NetworkWriter ownerWriter, out int ownerWritten, NetworkWriter observersWriter, out int observersWritten)
|
||||
internal void OnSerializeAllSafely(bool initialState, NetworkWriter ownerWriter, out int ownerWritten, NetworkWriter observersWriter, out int observersWritten)
|
||||
{
|
||||
// clear 'written' variables
|
||||
ownerWritten = observersWritten = 0;
|
||||
|
||||
// dirtyComponentsMask should be changed before tyhis function is called
|
||||
Debug.Assert(dirtyComponentsMask != 0UL, "OnSerializeAllSafely Should not be given a zero dirtyComponentsMask", this);
|
||||
// check if components are in byte.MaxRange just to be 100% sure
|
||||
// that we avoid overflows
|
||||
NetworkBehaviour[] components = NetworkBehaviours;
|
||||
if (components.Length > byte.MaxValue)
|
||||
throw new IndexOutOfRangeException($"{name} has more than {byte.MaxValue} components. This is not supported.");
|
||||
|
||||
// calculate syncMode mask at runtime. this allows users to change
|
||||
// component.syncMode while the game is running, which can be a huge
|
||||
// advantage over syncvar-based sync modes. e.g. if a player decides
|
||||
// to share or not share his inventory, or to go invisible, etc.
|
||||
//
|
||||
// (this also lets the TestSynchronizingObjects test pass because
|
||||
// otherwise if we were to cache it in Awake, then we would call
|
||||
// GetComponents<NetworkBehaviour> before all the test behaviours
|
||||
// were added)
|
||||
ulong syncModeObserversMask = GetSyncModeObserversMask();
|
||||
|
||||
// write regular dirty mask for owner,
|
||||
// writer 'dirty mask & syncMode==Everyone' for everyone else
|
||||
// (WritePacked64 so we don't write full 8 bytes if we don't have to)
|
||||
ownerWriter.WriteUInt64(dirtyComponentsMask);
|
||||
observersWriter.WriteUInt64(dirtyComponentsMask & syncModeObserversMask);
|
||||
|
||||
foreach (NetworkBehaviour comp in NetworkBehaviours)
|
||||
// serialize all components
|
||||
for (int i = 0; i < components.Length; ++i)
|
||||
{
|
||||
// is this component dirty?
|
||||
// -> always serialize if initialState so all components are included in spawn packet
|
||||
// -> note: IsDirty() is false if the component isn't dirty or sendInterval isn't elapsed yet
|
||||
NetworkBehaviour comp = components[i];
|
||||
if (initialState || comp.IsDirty())
|
||||
{
|
||||
// Debug.Log("OnSerializeAllSafely: " + name + " -> " + comp.GetType() + " initial=" + initialState);
|
||||
|
||||
// remember start position in case we need to copy it into
|
||||
// observers writer too
|
||||
int startPosition = ownerWriter.Position;
|
||||
|
||||
// write index as byte [0..255]
|
||||
ownerWriter.WriteByte((byte)i);
|
||||
|
||||
// serialize into ownerWriter first
|
||||
// (owner always gets everything!)
|
||||
int startPosition = ownerWriter.Position;
|
||||
OnSerializeSafely(comp, ownerWriter, initialState);
|
||||
++ownerWritten;
|
||||
|
||||
@ -934,56 +927,6 @@ internal void OnSerializeAllSafely(bool initialState, ulong dirtyComponentsMask,
|
||||
}
|
||||
}
|
||||
|
||||
internal ulong GetDirtyComponentsMask()
|
||||
{
|
||||
// loop through all components only once and then write dirty+payload into the writer afterwards
|
||||
ulong dirtyComponentsMask = 0L;
|
||||
NetworkBehaviour[] components = NetworkBehaviours;
|
||||
for (int i = 0; i < components.Length; ++i)
|
||||
{
|
||||
NetworkBehaviour comp = components[i];
|
||||
if (comp.IsDirty())
|
||||
{
|
||||
dirtyComponentsMask |= 1UL << i;
|
||||
}
|
||||
}
|
||||
|
||||
return dirtyComponentsMask;
|
||||
}
|
||||
|
||||
internal ulong GetInitialComponentsMask()
|
||||
{
|
||||
// loop through all components only once and then write dirty+payload into the writer afterwards
|
||||
ulong dirtyComponentsMask = 0UL;
|
||||
for (int i = 0; i < NetworkBehaviours.Length; ++i)
|
||||
{
|
||||
dirtyComponentsMask |= 1UL << i;
|
||||
}
|
||||
|
||||
return dirtyComponentsMask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// a mask that contains all the components with SyncMode.Observers
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal ulong GetSyncModeObserversMask()
|
||||
{
|
||||
// loop through all components
|
||||
ulong mask = 0UL;
|
||||
NetworkBehaviour[] components = NetworkBehaviours;
|
||||
for (int i = 0; i < NetworkBehaviours.Length; ++i)
|
||||
{
|
||||
NetworkBehaviour comp = components[i];
|
||||
if (comp.syncMode == SyncMode.Observers)
|
||||
{
|
||||
mask |= 1UL << i;
|
||||
}
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
void OnDeserializeSafely(NetworkBehaviour comp, NetworkReader reader, bool initialState)
|
||||
{
|
||||
// read header as 4 bytes and calculate this chunk's start+end
|
||||
@ -1024,18 +967,16 @@ void OnDeserializeSafely(NetworkBehaviour comp, NetworkReader reader, bool initi
|
||||
|
||||
internal void OnDeserializeAllSafely(NetworkReader reader, bool initialState)
|
||||
{
|
||||
// read component dirty mask
|
||||
ulong dirtyComponentsMask = reader.ReadUInt64();
|
||||
|
||||
// deserialize all components that were received
|
||||
NetworkBehaviour[] components = NetworkBehaviours;
|
||||
// loop through all components and deserialize the dirty ones
|
||||
for (int i = 0; i < components.Length; ++i)
|
||||
while (reader.Remaining > 0)
|
||||
{
|
||||
// is the dirty bit at position 'i' set to 1?
|
||||
ulong dirtyBit = 1UL << i;
|
||||
if ((dirtyComponentsMask & dirtyBit) != 0L)
|
||||
// read & check index [0..255]
|
||||
byte index = reader.ReadByte();
|
||||
if (index < components.Length)
|
||||
{
|
||||
OnDeserializeSafely(components[i], reader, initialState);
|
||||
// deserialize this component
|
||||
OnDeserializeSafely(components[index], reader, initialState);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1135,13 +1076,7 @@ internal void ServerUpdate()
|
||||
{
|
||||
if (observers.Count > 0)
|
||||
{
|
||||
ulong dirtyComponentsMask = GetDirtyComponentsMask();
|
||||
|
||||
// AnyComponentsDirty
|
||||
if (dirtyComponentsMask != 0UL)
|
||||
{
|
||||
SendUpdateVarsMessage(dirtyComponentsMask);
|
||||
}
|
||||
SendUpdateVarsMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1151,13 +1086,13 @@ internal void ServerUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
void SendUpdateVarsMessage(ulong dirtyComponentsMask)
|
||||
void SendUpdateVarsMessage()
|
||||
{
|
||||
// one writer for owner, one for observers
|
||||
using (PooledNetworkWriter ownerWriter = NetworkWriterPool.GetWriter(), observersWriter = NetworkWriterPool.GetWriter())
|
||||
{
|
||||
// serialize all the dirty components and send
|
||||
OnSerializeAllSafely(false, dirtyComponentsMask, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
|
||||
OnSerializeAllSafely(false, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
|
||||
if (ownerWritten > 0 || observersWritten > 0)
|
||||
{
|
||||
UpdateVarsMessage varsMessage = new UpdateVarsMessage
|
||||
|
@ -857,8 +857,7 @@ static ArraySegment<byte> CreateSpawnMessagePayload(bool isOwner, NetworkIdentit
|
||||
|
||||
// serialize all components with initialState = true
|
||||
// (can be null if has none)
|
||||
ulong dirtyComponentsMask = identity.GetInitialComponentsMask();
|
||||
identity.OnSerializeAllSafely(true, dirtyComponentsMask, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
|
||||
identity.OnSerializeAllSafely(true, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
|
||||
|
||||
// convert to ArraySegment to avoid reader allocations
|
||||
// (need to handle null case too)
|
||||
|
@ -472,10 +472,9 @@ public void ApplyPayload_SendsDataToNetworkBehaviourDeserialize()
|
||||
serverPayloadBehaviour.direction = direction;
|
||||
|
||||
|
||||
ulong dirtyMask = 1UL;
|
||||
NetworkWriter ownerWriter = new NetworkWriter(1024);
|
||||
NetworkWriter observersWriter = new NetworkWriter(1024);
|
||||
serverIdentity.OnSerializeAllSafely(true, dirtyMask, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
|
||||
serverIdentity.OnSerializeAllSafely(true, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
|
||||
|
||||
// check that Serialize was called
|
||||
Assert.That(onSerializeCalled, Is.EqualTo(1));
|
||||
|
@ -584,10 +584,9 @@ public void OnSerializeAndDeserializeAllSafely()
|
||||
// serialize all - should work even if compExc throws an exception
|
||||
NetworkWriter ownerWriter = new NetworkWriter(1024);
|
||||
NetworkWriter observersWriter = new NetworkWriter(1024);
|
||||
ulong mask = identity.GetInitialComponentsMask();
|
||||
// error log because of the exception is expected
|
||||
LogAssert.ignoreFailingMessages = true;
|
||||
identity.OnSerializeAllSafely(true, mask, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
|
||||
identity.OnSerializeAllSafely(true, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
|
||||
LogAssert.ignoreFailingMessages = false;
|
||||
|
||||
// owner should have written all components
|
||||
@ -645,8 +644,7 @@ public void OnSerializeAllSafelyShouldNotLogErrorsForTooManyComponents()
|
||||
NetworkWriter ownerWriter = new NetworkWriter(1024);
|
||||
NetworkWriter observersWriter = new NetworkWriter(1024);
|
||||
|
||||
ulong mask = identity.GetInitialComponentsMask();
|
||||
identity.OnSerializeAllSafely(true, mask, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
|
||||
identity.OnSerializeAllSafely(true, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
|
||||
|
||||
// Should still write with too mnay Components because NetworkBehavioursCache should handle the error
|
||||
Assert.That(ownerWriter.Position, Is.GreaterThan(0));
|
||||
@ -658,14 +656,14 @@ public void OnSerializeAllSafelyShouldNotLogErrorsForTooManyComponents()
|
||||
[Test]
|
||||
public void CreatingNetworkBehavioursCacheShouldLogErrorForTooComponents()
|
||||
{
|
||||
// add 65 components
|
||||
for (int i = 0; i < 65; ++i)
|
||||
// add byte.MaxValue+1 components
|
||||
for (int i = 0; i < byte.MaxValue+1; ++i)
|
||||
{
|
||||
gameObject.AddComponent<SerializeTest1NetworkBehaviour>();
|
||||
}
|
||||
|
||||
// call NetworkBehaviours property to create the cache
|
||||
LogAssert.Expect(LogType.Error, new Regex("Only 64 NetworkBehaviour components are allowed for NetworkIdentity.+"));
|
||||
LogAssert.Expect(LogType.Error, new Regex($"Only {byte.MaxValue} NetworkBehaviour components are allowed for NetworkIdentity.+"));
|
||||
_ = identity.NetworkBehaviours;
|
||||
}
|
||||
|
||||
@ -689,8 +687,7 @@ public void OnDeserializeSafelyShouldDetectAndHandleDeSerializationMismatch()
|
||||
// serialize
|
||||
NetworkWriter ownerWriter = new NetworkWriter(1024);
|
||||
NetworkWriter observersWriter = new NetworkWriter(1024);
|
||||
ulong mask = identity.GetInitialComponentsMask();
|
||||
identity.OnSerializeAllSafely(true, mask, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
|
||||
identity.OnSerializeAllSafely(true, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
|
||||
|
||||
// reset component values
|
||||
comp1.value = 0;
|
||||
@ -965,66 +962,5 @@ public void HandleRpc()
|
||||
NetworkIdentity.spawned.Clear();
|
||||
RemoteCallHelper.RemoveDelegate(registeredHash);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetInitialComponentsMaskShouldReturn1BitPerNetworkBehaviour()
|
||||
{
|
||||
gameObject.AddComponent<MyTestComponent>();
|
||||
gameObject.AddComponent<SerializeTest1NetworkBehaviour>();
|
||||
gameObject.AddComponent<SerializeTest2NetworkBehaviour>();
|
||||
|
||||
ulong mask = identity.GetInitialComponentsMask();
|
||||
|
||||
// 1 + 2 + 4 = 7
|
||||
Assert.That(mask, Is.EqualTo(7UL));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetInitialComponentsMaskShouldReturnZeroWhenNoNetworkBehaviours()
|
||||
{
|
||||
ulong mask = identity.GetInitialComponentsMask();
|
||||
|
||||
Assert.That(mask, Is.EqualTo(0UL));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDirtyComponentsMaskShouldReturn1BitOnlyForDirtyComponents()
|
||||
{
|
||||
MyTestComponent comp1 = gameObject.AddComponent<MyTestComponent>();
|
||||
SerializeTest1NetworkBehaviour comp2 = gameObject.AddComponent<SerializeTest1NetworkBehaviour>();
|
||||
SerializeTest2NetworkBehaviour comp3 = gameObject.AddComponent<SerializeTest2NetworkBehaviour>();
|
||||
|
||||
|
||||
// mark comps 1 and 3 as dirty
|
||||
|
||||
comp1.syncInterval = 0;
|
||||
comp3.syncInterval = 0;
|
||||
|
||||
comp1.SetDirtyBit(1UL);
|
||||
comp2.ClearAllDirtyBits();
|
||||
comp3.SetDirtyBit(1UL);
|
||||
|
||||
|
||||
ulong mask = identity.GetDirtyComponentsMask();
|
||||
|
||||
// 1 + 4 = 5
|
||||
Assert.That(mask, Is.EqualTo(5UL));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDirtyComponentsMaskShouldReturnZeroWhenNoDirtyComponents()
|
||||
{
|
||||
MyTestComponent comp1 = gameObject.AddComponent<MyTestComponent>();
|
||||
SerializeTest1NetworkBehaviour comp2 = gameObject.AddComponent<SerializeTest1NetworkBehaviour>();
|
||||
SerializeTest2NetworkBehaviour comp3 = gameObject.AddComponent<SerializeTest2NetworkBehaviour>();
|
||||
|
||||
comp1.ClearAllDirtyBits();
|
||||
comp2.ClearAllDirtyBits();
|
||||
comp3.ClearAllDirtyBits();
|
||||
|
||||
ulong mask = identity.GetDirtyComponentsMask();
|
||||
|
||||
Assert.That(mask, Is.EqualTo(0UL));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -123,8 +123,7 @@ public void TestSynchronizingObjects()
|
||||
NetworkWriter ownerWriter = new NetworkWriter(1024);
|
||||
// not really used in this Test
|
||||
NetworkWriter observersWriter = new NetworkWriter(1024);
|
||||
ulong mask = identity1.GetInitialComponentsMask();
|
||||
identity1.OnSerializeAllSafely(true, mask, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
|
||||
identity1.OnSerializeAllSafely(true, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
|
||||
|
||||
// set up a "client" object
|
||||
GameObject gameObject2 = new GameObject();
|
||||
@ -138,25 +137,5 @@ public void TestSynchronizingObjects()
|
||||
// check that the syncvars got updated
|
||||
Assert.That(player2.guild.name, Is.EqualTo("Back street boys"), "Data should be synchronized");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSyncModeObserversMask()
|
||||
{
|
||||
GameObject gameObject1 = new GameObject();
|
||||
NetworkIdentity identity = gameObject1.AddComponent<NetworkIdentity>();
|
||||
MockPlayer player1 = gameObject1.AddComponent<MockPlayer>();
|
||||
player1.syncInterval = 0;
|
||||
MockPlayer player2 = gameObject1.AddComponent<MockPlayer>();
|
||||
player2.syncInterval = 0;
|
||||
MockPlayer player3 = gameObject1.AddComponent<MockPlayer>();
|
||||
player3.syncInterval = 0;
|
||||
|
||||
// sync mode
|
||||
player1.syncMode = SyncMode.Observers;
|
||||
player2.syncMode = SyncMode.Owner;
|
||||
player3.syncMode = SyncMode.Observers;
|
||||
|
||||
Assert.That(identity.GetSyncModeObserversMask(), Is.EqualTo(0b101));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user