Skip to content

Commit fdb32f1

Browse files
perf: improved runtime performance, dirty objects (#2116)
* Performance fix for large number of NetworkObjects * docs: style: changelog and coding convention * fixes tests (but not NetworkList ones) * Fixes NetworkList functionality * disabling incompatible test * test adjustment, following improvement in NetworkVariable sending * Merging two loops to scan over objects one less time (performance). Re-enabling test, making it use SetDirty() properly * improved naming, fixed tests * addressing 'This seems like it should be handled by PostNetworkVariableWrite' PR review comment * refactoring common part to duplicated code paths into a single instance * limiting m_IsDirty visibility, to encourage using SetDirty() Co-authored-by: starchitectus <[email protected]>
1 parent 21fcaab commit fdb32f1

File tree

14 files changed

+130
-102
lines changed

14 files changed

+130
-102
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
1111

1212
- When using `UnityTransport`, _reliable_ payloads are now allowed to exceed the configured 'Max Payload Size'. Unreliable payloads remain bounded by this setting. (#2081)
1313

14+
- Preformance improvements for cases with large number of NetworkObjects, by not iterating over all unchanged NetworkObjects
1415

1516
### Fixed
1617

com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,8 @@ internal void PostNetworkVariableWrite()
583583
{
584584
NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty();
585585
}
586+
587+
MarkVariablesDirty(false);
586588
}
587589

588590
internal void VariableUpdate(ulong targetClientId)
@@ -664,11 +666,11 @@ private bool CouldHaveDirtyNetworkVariables()
664666
return false;
665667
}
666668

667-
internal void MarkVariablesDirty()
669+
internal void MarkVariablesDirty(bool dirty)
668670
{
669671
for (int j = 0; j < NetworkVariableFields.Count; j++)
670672
{
671-
NetworkVariableFields[j].SetDirty(true);
673+
NetworkVariableFields[j].SetDirty(dirty);
672674
}
673675
}
674676

com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@ namespace Unity.Netcode
88
/// </summary>
99
public class NetworkBehaviourUpdater
1010
{
11-
private HashSet<NetworkObject> m_Touched = new HashSet<NetworkObject>();
11+
private HashSet<NetworkObject> m_DirtyNetworkObjects = new HashSet<NetworkObject>();
1212

1313
#if DEVELOPMENT_BUILD || UNITY_EDITOR
1414
private ProfilerMarker m_NetworkBehaviourUpdate = new ProfilerMarker($"{nameof(NetworkBehaviour)}.{nameof(NetworkBehaviourUpdate)}");
1515
#endif
1616

17+
internal void AddForUpdate(NetworkObject networkObject)
18+
{
19+
m_DirtyNetworkObjects.Add(networkObject);
20+
}
21+
1722
internal void NetworkBehaviourUpdate(NetworkManager networkManager)
1823
{
1924
#if DEVELOPMENT_BUILD || UNITY_EDITOR
@@ -23,38 +28,31 @@ internal void NetworkBehaviourUpdate(NetworkManager networkManager)
2328
{
2429
if (networkManager.IsServer)
2530
{
26-
m_Touched.Clear();
27-
for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++)
31+
foreach (var dirtyObj in m_DirtyNetworkObjects)
2832
{
29-
var client = networkManager.ConnectedClientsList[i];
30-
var spawnedObjs = networkManager.SpawnManager.SpawnedObjectsList;
31-
m_Touched.UnionWith(spawnedObjs);
32-
foreach (var sobj in spawnedObjs)
33+
for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++)
3334
{
34-
if (sobj.IsNetworkVisibleTo(client.ClientId))
35+
var client = networkManager.ConnectedClientsList[i];
36+
if (networkManager.IsHost && client.ClientId == networkManager.LocalClientId)
37+
{
38+
continue;
39+
}
40+
41+
if (dirtyObj.IsNetworkVisibleTo(client.ClientId))
3542
{
3643
// Sync just the variables for just the objects this client sees
37-
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
44+
for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++)
3845
{
39-
sobj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId);
46+
dirtyObj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId);
4047
}
4148
}
4249
}
4350
}
44-
45-
// Now, reset all the no-longer-dirty variables
46-
foreach (var sobj in m_Touched)
47-
{
48-
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
49-
{
50-
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
51-
}
52-
}
5351
}
5452
else
5553
{
5654
// when client updates the server, it tells it about all its objects
57-
foreach (var sobj in networkManager.SpawnManager.SpawnedObjectsList)
55+
foreach (var sobj in m_DirtyNetworkObjects)
5856
{
5957
if (sobj.IsOwner)
6058
{
@@ -64,16 +62,16 @@ internal void NetworkBehaviourUpdate(NetworkManager networkManager)
6462
}
6563
}
6664
}
67-
68-
// Now, reset all the no-longer-dirty variables
69-
foreach (var sobj in networkManager.SpawnManager.SpawnedObjectsList)
65+
}
66+
// Now, reset all the no-longer-dirty variables
67+
foreach (var dirtyobj in m_DirtyNetworkObjects)
68+
{
69+
for (int k = 0; k < dirtyobj.ChildNetworkBehaviours.Count; k++)
7070
{
71-
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
72-
{
73-
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
74-
}
71+
dirtyobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
7572
}
7673
}
74+
m_DirtyNetworkObjects.Clear();
7775
}
7876
finally
7977
{

com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@ internal static string PrefabDebugHelper(NetworkPrefab networkPrefab)
5454
return $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.gameObject.name}\"";
5555
}
5656

57-
internal NetworkBehaviourUpdater BehaviourUpdater { get; private set; }
57+
internal NetworkBehaviourUpdater BehaviourUpdater { get; set; }
58+
59+
internal void MarkNetworkObjectDirty(NetworkObject networkObject)
60+
{
61+
BehaviourUpdater.AddForUpdate(networkObject);
62+
}
5863

5964
internal MessagingSystem MessagingSystem { get; private set; }
6065

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -868,12 +868,12 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie
868868
}
869869
}
870870

871-
internal void MarkVariablesDirty()
871+
internal void MarkVariablesDirty(bool dirty)
872872
{
873873
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
874874
{
875875
var behavior = ChildNetworkBehaviours[i];
876-
behavior.MarkVariablesDirty();
876+
behavior.MarkVariablesDirty(dirty);
877877
}
878878
}
879879

com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ public override bool IsDirty()
6363
return base.IsDirty() || m_DirtyEvents.Length > 0;
6464
}
6565

66+
internal void MarkNetworkObjectDirty()
67+
{
68+
m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject);
69+
}
70+
6671
/// <inheritdoc />
6772
public override void WriteDelta(FastBufferWriter writer)
6873
{
@@ -189,6 +194,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
189194
Index = m_List.Length - 1,
190195
Value = m_List[m_List.Length - 1]
191196
});
197+
MarkNetworkObjectDirty();
192198
}
193199
}
194200
break;
@@ -225,6 +231,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
225231
Index = index,
226232
Value = m_List[index]
227233
});
234+
MarkNetworkObjectDirty();
228235
}
229236
}
230237
break;
@@ -257,6 +264,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
257264
Index = index,
258265
Value = value
259266
});
267+
MarkNetworkObjectDirty();
260268
}
261269
}
262270
break;
@@ -284,6 +292,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
284292
Index = index,
285293
Value = value
286294
});
295+
MarkNetworkObjectDirty();
287296
}
288297
}
289298
break;
@@ -319,6 +328,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
319328
Value = value,
320329
PreviousValue = previousValue
321330
});
331+
MarkNetworkObjectDirty();
322332
}
323333
}
324334
break;
@@ -341,6 +351,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
341351
{
342352
Type = eventType
343353
});
354+
MarkNetworkObjectDirty();
344355
}
345356
}
346357
break;
@@ -485,6 +496,7 @@ public T this[int index]
485496
private void HandleAddListEvent(NetworkListEvent<T> listEvent)
486497
{
487498
m_DirtyEvents.Add(listEvent);
499+
MarkNetworkObjectDirty();
488500
OnListChanged?.Invoke(listEvent);
489501
}
490502

com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ private static unsafe bool ValueEquals(ref T a, ref T b)
8787
/// <param name="value">the new value of type `T` to be set/></param>
8888
private protected void Set(T value)
8989
{
90-
m_IsDirty = true;
90+
SetDirty(true);
9191
T previousValue = m_InternalValue;
9292
m_InternalValue = value;
9393
OnValueChanged?.Invoke(previousValue, m_InternalValue);
@@ -119,7 +119,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
119119

120120
if (keepDirtyDelta)
121121
{
122-
m_IsDirty = true;
122+
SetDirty(true);
123123
}
124124

125125
OnValueChanged?.Invoke(previousValue, m_InternalValue);

com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ protected NetworkVariableBase(
5454
/// The <see cref="m_IsDirty"/> property is used to determine if the
5555
/// value of the `NetworkVariable` has changed.
5656
/// </summary>
57-
private protected bool m_IsDirty;
57+
private bool m_IsDirty;
5858

5959
/// <summary>
6060
/// Gets or sets the name of the network variable's instance
@@ -79,6 +79,10 @@ protected NetworkVariableBase(
7979
public virtual void SetDirty(bool isDirty)
8080
{
8181
m_IsDirty = isDirty;
82+
if (m_IsDirty)
83+
{
84+
m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject);
85+
}
8286
}
8387

8488
/// <summary>

com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject
576576
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
577577
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
578578

579-
networkObject.MarkVariablesDirty();
579+
networkObject.MarkVariablesDirty(true);
580580
}
581581

582582
internal ulong? GetSpawnParentId(NetworkObject networkObject)
Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,55 @@
11
using NUnit.Framework;
2+
using UnityEngine;
23

34
namespace Unity.Netcode.EditorTests.NetworkVar
45
{
56
public class NetworkVarTests
67
{
8+
public class NetworkVarComponent : NetworkBehaviour
9+
{
10+
public NetworkVariable<int> NetworkVariable = new NetworkVariable<int>();
11+
}
712
[Test]
813
public void TestAssignmentUnchanged()
914
{
10-
var intVar = new NetworkVariable<int>();
11-
12-
intVar.Value = 314159265;
13-
14-
intVar.OnValueChanged += (value, newValue) =>
15+
var gameObjectMan = new GameObject();
16+
var networkManager = gameObjectMan.AddComponent<NetworkManager>();
17+
networkManager.BehaviourUpdater = new NetworkBehaviourUpdater();
18+
var gameObject = new GameObject();
19+
var networkObject = gameObject.AddComponent<NetworkObject>();
20+
networkObject.NetworkManagerOwner = networkManager;
21+
var networkVarComponent = gameObject.AddComponent<NetworkVarComponent>();
22+
networkVarComponent.NetworkVariable.Initialize(networkVarComponent);
23+
networkVarComponent.NetworkVariable.Value = 314159265;
24+
networkVarComponent.NetworkVariable.OnValueChanged += (value, newValue) =>
1525
{
1626
Assert.Fail("OnValueChanged was invoked when setting the same value");
1727
};
18-
19-
intVar.Value = 314159265;
28+
networkVarComponent.NetworkVariable.Value = 314159265;
29+
Object.DestroyImmediate(gameObject);
30+
Object.DestroyImmediate(gameObjectMan);
2031
}
21-
2232
[Test]
2333
public void TestAssignmentChanged()
2434
{
25-
var intVar = new NetworkVariable<int>();
26-
27-
intVar.Value = 314159265;
28-
35+
var gameObjectMan = new GameObject();
36+
var networkManager = gameObjectMan.AddComponent<NetworkManager>();
37+
networkManager.BehaviourUpdater = new NetworkBehaviourUpdater();
38+
var gameObject = new GameObject();
39+
var networkObject = gameObject.AddComponent<NetworkObject>();
40+
var networkVarComponent = gameObject.AddComponent<NetworkVarComponent>();
41+
networkObject.NetworkManagerOwner = networkManager;
42+
networkVarComponent.NetworkVariable.Initialize(networkVarComponent);
43+
networkVarComponent.NetworkVariable.Value = 314159265;
2944
var changed = false;
30-
31-
intVar.OnValueChanged += (value, newValue) =>
45+
networkVarComponent.NetworkVariable.OnValueChanged += (value, newValue) =>
3246
{
3347
changed = true;
3448
};
35-
36-
intVar.Value = 314159266;
37-
49+
networkVarComponent.NetworkVariable.Value = 314159266;
3850
Assert.True(changed);
51+
Object.DestroyImmediate(gameObject);
52+
Object.DestroyImmediate(gameObjectMan);
3953
}
4054
}
4155
}

0 commit comments

Comments
 (0)