From 159d1abe1d8725b9ba8cf47cb45fc19ead39d625 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Thu, 27 Nov 2025 18:56:26 +0100 Subject: [PATCH 01/10] Start optimizing destroy object message --- .../Messages/DestroyObjectMessage.cs | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index 1316defab8..e52e6dcf23 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -7,12 +7,12 @@ internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcp { private const int k_OptimizeDestroyObjectMessage = 1; private const int k_AllowDestroyGameInPlaced = 2; + public int Version => k_AllowDestroyGameInPlaced; private const string k_Name = "DestroyObjectMessage"; public ulong NetworkObjectId; - private byte m_DestroyFlags; internal int DeferredDespawnTick; @@ -21,55 +21,64 @@ internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcp internal bool IsDistributedAuthority; - internal bool IsTargetedDestroy - { - get => ByteUtility.GetBit(m_DestroyFlags, 0); - - set => ByteUtility.SetBit(ref m_DestroyFlags, 0, value); - } - - private bool IsDeferredDespawn - { - get => ByteUtility.GetBit(m_DestroyFlags, 1); + private const byte k_IsTargetedDestroy = 0x01; + private const byte k_IsDeferredDespawn = 0x02; + private const byte k_DestroyGameObject = 0x04; - set => ByteUtility.SetBit(ref m_DestroyFlags, 1, value); - } + internal bool IsTargetedDestroy; // Get bit [(bitField & (1 << bitPosition)) != 0;] with bitPosition= 0 + // set (byte)((bitField & ~(1 << bitPosition)) | (ToByte(value) << bitPosition)) + private bool m_IsDeferredDespawn; // 1 /// /// Used to communicate whether to destroy the associated game object. /// Should be false if the object is InScenePlaced and true otherwise /// - public bool DestroyGameObject + public bool DestroyGameObject; // 2 + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal uint GetBitsetRepresentation() { - get => ByteUtility.GetBit(m_DestroyFlags, 2); + uint bitset = 0; + if (IsTargetedDestroy) { bitset |= k_IsTargetedDestroy; } + if (m_IsDeferredDespawn) { bitset |= k_IsDeferredDespawn; } + if (DestroyGameObject) { bitset |= k_DestroyGameObject; } + return bitset; + } - set => ByteUtility.SetBit(ref m_DestroyFlags, 2, value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void SetStateFromBitset(uint bitset) + { + IsTargetedDestroy = (bitset & k_IsTargetedDestroy) != 0; + m_IsDeferredDespawn = (bitset & k_IsDeferredDespawn) != 0; + DestroyGameObject = (bitset & k_DestroyGameObject) != 0; } public void Serialize(FastBufferWriter writer, int targetVersion) { // Set deferred despawn flag - IsDeferredDespawn = DeferredDespawnTick > 0; + m_IsDeferredDespawn = DeferredDespawnTick > 0; + + uint getBitsetRepresentation = GetBitsetRepresentation(); BytePacker.WriteValueBitPacked(writer, NetworkObjectId); if (IsDistributedAuthority) { - writer.WriteByteSafe(m_DestroyFlags); + writer.WriteValueSafe(getBitsetRepresentation); if (IsTargetedDestroy) { BytePacker.WriteValueBitPacked(writer, TargetClientId); } - if (targetVersion < k_OptimizeDestroyObjectMessage || IsDeferredDespawn) + if (targetVersion < k_OptimizeDestroyObjectMessage || m_IsDeferredDespawn) { BytePacker.WriteValueBitPacked(writer, DeferredDespawnTick); } } else if (targetVersion >= k_AllowDestroyGameInPlaced) { - writer.WriteByteSafe(m_DestroyFlags); + writer.WriteValueSafe(getBitsetRepresentation); } if (targetVersion < k_OptimizeDestroyObjectMessage) @@ -90,12 +99,14 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int if (networkManager.DistributedAuthorityMode) { reader.ReadByteSafe(out m_DestroyFlags); + SetStateFromBitset(m_DestroyFlags); + if (IsTargetedDestroy) { ByteUnpacker.ReadValueBitPacked(reader, out TargetClientId); } - if (receivedMessageVersion < k_OptimizeDestroyObjectMessage || IsDeferredDespawn) + if (receivedMessageVersion < k_OptimizeDestroyObjectMessage || m_IsDeferredDespawn) { ByteUnpacker.ReadValueBitPacked(reader, out DeferredDespawnTick); } @@ -103,6 +114,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int else if (receivedMessageVersion >= k_AllowDestroyGameInPlaced) { reader.ReadByteSafe(out m_DestroyFlags); + SetStateFromBitset(m_DestroyFlags); } if (receivedMessageVersion < k_OptimizeDestroyObjectMessage) From 28a0ef58c782d9993a967722ec4a0544176e20e7 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Thu, 27 Nov 2025 19:10:20 +0100 Subject: [PATCH 02/10] remove comments + added todo comment --- .../Messaging/Messages/DestroyObjectMessage.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index e52e6dcf23..d13ff35513 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -13,7 +13,7 @@ internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcp private const string k_Name = "DestroyObjectMessage"; public ulong NetworkObjectId; - private byte m_DestroyFlags; + private byte m_DestroyFlags; // TO DO check naming internal int DeferredDespawnTick; // Temporary until we make this a list @@ -25,15 +25,14 @@ internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcp private const byte k_IsDeferredDespawn = 0x02; private const byte k_DestroyGameObject = 0x04; - internal bool IsTargetedDestroy; // Get bit [(bitField & (1 << bitPosition)) != 0;] with bitPosition= 0 - // set (byte)((bitField & ~(1 << bitPosition)) | (ToByte(value) << bitPosition)) - private bool m_IsDeferredDespawn; // 1 + internal bool IsTargetedDestroy; + private bool m_IsDeferredDespawn; /// /// Used to communicate whether to destroy the associated game object. /// Should be false if the object is InScenePlaced and true otherwise /// - public bool DestroyGameObject; // 2 + public bool DestroyGameObject; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal uint GetBitsetRepresentation() @@ -58,13 +57,13 @@ public void Serialize(FastBufferWriter writer, int targetVersion) // Set deferred despawn flag m_IsDeferredDespawn = DeferredDespawnTick > 0; - uint getBitsetRepresentation = GetBitsetRepresentation(); + uint bitsetRepresentation = GetBitsetRepresentation(); BytePacker.WriteValueBitPacked(writer, NetworkObjectId); if (IsDistributedAuthority) { - writer.WriteValueSafe(getBitsetRepresentation); + writer.WriteValueSafe(bitsetRepresentation); if (IsTargetedDestroy) { @@ -78,7 +77,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) } else if (targetVersion >= k_AllowDestroyGameInPlaced) { - writer.WriteValueSafe(getBitsetRepresentation); + writer.WriteValueSafe(bitsetRepresentation); } if (targetVersion < k_OptimizeDestroyObjectMessage) From 65ff1d70232f0ed866025e9630f3842839a00172 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Thu, 27 Nov 2025 19:52:33 +0100 Subject: [PATCH 03/10] Address PR feedback, inline get and set bitset --- .../Messages/DestroyObjectMessage.cs | 40 +++++++------------ 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index d13ff35513..94f18f1b00 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -13,7 +13,6 @@ internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcp private const string k_Name = "DestroyObjectMessage"; public ulong NetworkObjectId; - private byte m_DestroyFlags; // TO DO check naming internal int DeferredDespawnTick; // Temporary until we make this a list @@ -34,36 +33,21 @@ internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcp /// public bool DestroyGameObject; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal uint GetBitsetRepresentation() - { - uint bitset = 0; - if (IsTargetedDestroy) { bitset |= k_IsTargetedDestroy; } - if (m_IsDeferredDespawn) { bitset |= k_IsDeferredDespawn; } - if (DestroyGameObject) { bitset |= k_DestroyGameObject; } - return bitset; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SetStateFromBitset(uint bitset) - { - IsTargetedDestroy = (bitset & k_IsTargetedDestroy) != 0; - m_IsDeferredDespawn = (bitset & k_IsDeferredDespawn) != 0; - DestroyGameObject = (bitset & k_DestroyGameObject) != 0; - } - public void Serialize(FastBufferWriter writer, int targetVersion) { // Set deferred despawn flag m_IsDeferredDespawn = DeferredDespawnTick > 0; - uint bitsetRepresentation = GetBitsetRepresentation(); + uint bitset = 0; + if (IsTargetedDestroy) { bitset |= k_IsTargetedDestroy; } + if (m_IsDeferredDespawn) { bitset |= k_IsDeferredDespawn; } + if (DestroyGameObject) { bitset |= k_DestroyGameObject; } BytePacker.WriteValueBitPacked(writer, NetworkObjectId); if (IsDistributedAuthority) { - writer.WriteValueSafe(bitsetRepresentation); + writer.WriteValueSafe(bitset); if (IsTargetedDestroy) { @@ -77,7 +61,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) } else if (targetVersion >= k_AllowDestroyGameInPlaced) { - writer.WriteValueSafe(bitsetRepresentation); + writer.WriteValueSafe(bitset); } if (targetVersion < k_OptimizeDestroyObjectMessage) @@ -97,8 +81,10 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId); if (networkManager.DistributedAuthorityMode) { - reader.ReadByteSafe(out m_DestroyFlags); - SetStateFromBitset(m_DestroyFlags); + reader.ReadByteSafe(out byte bitset); + IsTargetedDestroy = (bitset & k_IsTargetedDestroy) != 0; + m_IsDeferredDespawn = (bitset & k_IsDeferredDespawn) != 0; + DestroyGameObject = (bitset & k_DestroyGameObject) != 0; if (IsTargetedDestroy) { @@ -112,8 +98,10 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int } else if (receivedMessageVersion >= k_AllowDestroyGameInPlaced) { - reader.ReadByteSafe(out m_DestroyFlags); - SetStateFromBitset(m_DestroyFlags); + reader.ReadByteSafe(out byte bitset); + IsTargetedDestroy = (bitset & k_IsTargetedDestroy) != 0; + m_IsDeferredDespawn = (bitset & k_IsDeferredDespawn) != 0; + DestroyGameObject = (bitset & k_DestroyGameObject) != 0; } if (receivedMessageVersion < k_OptimizeDestroyObjectMessage) From f3639d2686f0f1d236235f6068dbc09e60456647 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Thu, 27 Nov 2025 20:05:11 +0100 Subject: [PATCH 04/10] Update CHANGELOG --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 7105c92dc8..9f82087a90 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -17,6 +17,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Improve performance of `NetworkTransformState`. (#3801) - Improve performance of `NetworkTransformState`. (#3770) From f99c4d01d8ad87488d692863c027b79b8aae5a9f Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Thu, 27 Nov 2025 20:08:52 +0100 Subject: [PATCH 05/10] Fixing typo --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9f82087a90..c3bdd2418c 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -17,7 +17,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed -- Improve performance of `NetworkTransformState`. (#3801) +- Improve performance of `DestroyObjectMessage`. (#3801) - Improve performance of `NetworkTransformState`. (#3770) From 94bf7853772586ce60ab06084784c1027a1cd8f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noellie=20V=C3=A9lez?= Date: Thu, 27 Nov 2025 22:10:42 +0100 Subject: [PATCH 06/10] Apply suggestions from code review Co-authored-by: Emma --- .../Runtime/Messaging/Messages/DestroyObjectMessage.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index 94f18f1b00..4538d6ab8c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -7,7 +7,6 @@ internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcp { private const int k_OptimizeDestroyObjectMessage = 1; private const int k_AllowDestroyGameInPlaced = 2; - public int Version => k_AllowDestroyGameInPlaced; private const string k_Name = "DestroyObjectMessage"; @@ -99,8 +98,6 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int else if (receivedMessageVersion >= k_AllowDestroyGameInPlaced) { reader.ReadByteSafe(out byte bitset); - IsTargetedDestroy = (bitset & k_IsTargetedDestroy) != 0; - m_IsDeferredDespawn = (bitset & k_IsDeferredDespawn) != 0; DestroyGameObject = (bitset & k_DestroyGameObject) != 0; } From e7fe26518a66e8c80bc2c9587f83f5ef58062855 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Thu, 27 Nov 2025 22:35:46 +0100 Subject: [PATCH 07/10] Address PR feedback: convert field into local var --- .../Messaging/Messages/DestroyObjectMessage.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index 4538d6ab8c..c3a9c37a26 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -24,7 +24,6 @@ internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcp private const byte k_DestroyGameObject = 0x04; internal bool IsTargetedDestroy; - private bool m_IsDeferredDespawn; /// /// Used to communicate whether to destroy the associated game object. @@ -35,11 +34,11 @@ internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcp public void Serialize(FastBufferWriter writer, int targetVersion) { // Set deferred despawn flag - m_IsDeferredDespawn = DeferredDespawnTick > 0; + var isDeferredDespawn = DeferredDespawnTick > 0; uint bitset = 0; if (IsTargetedDestroy) { bitset |= k_IsTargetedDestroy; } - if (m_IsDeferredDespawn) { bitset |= k_IsDeferredDespawn; } + if (isDeferredDespawn) { bitset |= k_IsDeferredDespawn; } if (DestroyGameObject) { bitset |= k_DestroyGameObject; } BytePacker.WriteValueBitPacked(writer, NetworkObjectId); @@ -53,7 +52,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) BytePacker.WriteValueBitPacked(writer, TargetClientId); } - if (targetVersion < k_OptimizeDestroyObjectMessage || m_IsDeferredDespawn) + if (targetVersion < k_OptimizeDestroyObjectMessage || isDeferredDespawn) { BytePacker.WriteValueBitPacked(writer, DeferredDespawnTick); } @@ -82,7 +81,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int { reader.ReadByteSafe(out byte bitset); IsTargetedDestroy = (bitset & k_IsTargetedDestroy) != 0; - m_IsDeferredDespawn = (bitset & k_IsDeferredDespawn) != 0; + var isDeferredDespawn = (bitset & k_IsDeferredDespawn) != 0; DestroyGameObject = (bitset & k_DestroyGameObject) != 0; if (IsTargetedDestroy) @@ -90,7 +89,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int ByteUnpacker.ReadValueBitPacked(reader, out TargetClientId); } - if (receivedMessageVersion < k_OptimizeDestroyObjectMessage || m_IsDeferredDespawn) + if (receivedMessageVersion < k_OptimizeDestroyObjectMessage || isDeferredDespawn) { ByteUnpacker.ReadValueBitPacked(reader, out DeferredDespawnTick); } From 7c63e27df215d1325c215854741ce23220c3458e Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 1 Dec 2025 18:46:01 +0100 Subject: [PATCH 08/10] fix bitset type --- .../Runtime/Messaging/Messages/DestroyObjectMessage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index c3a9c37a26..3f04588e2d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -36,7 +36,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) // Set deferred despawn flag var isDeferredDespawn = DeferredDespawnTick > 0; - uint bitset = 0; + byte bitset = 0x00; if (IsTargetedDestroy) { bitset |= k_IsTargetedDestroy; } if (isDeferredDespawn) { bitset |= k_IsDeferredDespawn; } if (DestroyGameObject) { bitset |= k_DestroyGameObject; } From 5a8e3a29c3d00adb2e3536fb4a13cd61a2bae7d6 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 1 Dec 2025 18:50:18 +0100 Subject: [PATCH 09/10] Use ReadByteSafe instead of ReadValueSafe --- .../Runtime/Messaging/Messages/DestroyObjectMessage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index 3f04588e2d..44b230db13 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -45,7 +45,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) if (IsDistributedAuthority) { - writer.WriteValueSafe(bitset); + writer.WriteByteSafe(bitset); if (IsTargetedDestroy) { From f03efbabcab6e4081a73c53bacbbc230f57ff76b Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Fri, 5 Dec 2025 12:42:31 +0100 Subject: [PATCH 10/10] Use WriteByte intead of Value when writing a byte --- .../Runtime/Messaging/Messages/DestroyObjectMessage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs index 44b230db13..2550c3015f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -59,7 +59,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) } else if (targetVersion >= k_AllowDestroyGameInPlaced) { - writer.WriteValueSafe(bitset); + writer.WriteByteSafe(bitset); } if (targetVersion < k_OptimizeDestroyObjectMessage)