Skip to content

Commit 26577d6

Browse files
ShadauxCat0xFA11
andauthored
feat: Supporting managed types in NetworkVariable [MTT-4594] (#2219)
* feat: Supporting managed types in NetworkVariable. Also changes NetworkVariable to deserialize INetworkSerializable types in-place - this is done to avoid having a GC alloc every time the value changes. There will only be a GC alloc if it receives an updated value and the current value is null. Managed types are supported in two ways: 1. By making them INetworkSerializable, or 2. By assigning the delegates UserNetworkVariableSerialization<T>.WriteValue and UserNetworkVariableSerialization<T>.ReadValue If a managed type is passed in without meeting one of those criteria (or any unsupported type), then an exception will be thrown the first time the NetworkVariable is read or written. That timing is to ensure no weird behavior with exceptions being thrown before user code is able to assign the delegates. Co-authored-by: Fatih Mar <[email protected]>
1 parent 374cb49 commit 26577d6

File tree

18 files changed

+1178
-169
lines changed

18 files changed

+1178
-169
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Additional documentation and release notes are available at [Multiplayer Documen
2323
- The send queues of `UnityTransport` are now dynamically-sized. This means that there shouldn't be any need anymore to tweak the 'Max Send Queue Size' value. In fact, this field is now removed from the inspector and will not be serialized anymore. It is still possible to set it manually using the `MaxSendQueueSize` property, but it is not recommended to do so aside from some specific needs (e.g. limiting the amount of memory used by the send queues in very constrained environments). (#2212)
2424
- As a consequence of the above change, the `UnityTransport.InitialMaxSendQueueSize` field is now deprecated. There is no default value anymore since send queues are dynamically-sized. (#2212)
2525
- The debug simulator in `UnityTransport` is now non-deterministic. Its random number generator used to be seeded with a constant value, leading to the same pattern of packet drops, delays, and jitter in every run. (#2196)
26+
- `NetworkVariable<>` now supports managed `INetworkSerializable` types, as well as other managed types with serialization/deserialization delegates registered to `UserNetworkVariableSerialization<T>.WriteValue` and `UserNetworkVariableSerialization<T>.ReadValue` (#2219)
27+
- `NetworkVariable<>` and `BufferSerializer<BufferSerializerReader>` now deserialize `INetworkSerializable` types in-place, rather than constructing new ones. (#2219)
2628

2729
### Fixed
2830

com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,19 @@ public static bool HasInterface(this TypeReference typeReference, string interfa
123123
try
124124
{
125125
var typeDef = typeReference.Resolve();
126+
// Note: this won't catch generics correctly.
127+
//
128+
// class Foo<T>: IInterface<T> {}
129+
// class Bar: Foo<int> {}
130+
//
131+
// Bar.HasInterface(IInterface<int>) -> returns false even though it should be true.
132+
//
133+
// This can be fixed (see GetAllFieldsAndResolveGenerics() in NetworkBehaviourILPP to understand how)
134+
// but right now we don't need that to work so it's left alone to reduce complexity
135+
if (typeDef.BaseType.HasInterface(interfaceTypeFullName))
136+
{
137+
return true;
138+
}
126139
var typeFaces = typeDef.Interfaces;
127140
return typeFaces.Any(iface => iface.InterfaceType.FullName == interfaceTypeFullName);
128141
}

com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs

Lines changed: 264 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ public override void ReadField(FastBufferReader reader)
151151
reader.ReadValueSafe(out ushort count);
152152
for (int i = 0; i < count; i++)
153153
{
154-
NetworkVariableSerialization<T>.Read(reader, out T value);
154+
var value = new T();
155+
NetworkVariableSerialization<T>.Read(reader, ref value);
155156
m_List.Add(value);
156157
}
157158
}
@@ -167,7 +168,8 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
167168
{
168169
case NetworkListEvent<T>.EventType.Add:
169170
{
170-
NetworkVariableSerialization<T>.Read(reader, out T value);
171+
var value = new T();
172+
NetworkVariableSerialization<T>.Read(reader, ref value);
171173
m_List.Add(value);
172174

173175
if (OnListChanged != null)
@@ -195,7 +197,8 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
195197
case NetworkListEvent<T>.EventType.Insert:
196198
{
197199
reader.ReadValueSafe(out int index);
198-
NetworkVariableSerialization<T>.Read(reader, out T value);
200+
var value = new T();
201+
NetworkVariableSerialization<T>.Read(reader, ref value);
199202

200203
if (index < m_List.Length)
201204
{
@@ -231,7 +234,8 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
231234
break;
232235
case NetworkListEvent<T>.EventType.Remove:
233236
{
234-
NetworkVariableSerialization<T>.Read(reader, out T value);
237+
var value = new T();
238+
NetworkVariableSerialization<T>.Read(reader, ref value);
235239
int index = m_List.IndexOf(value);
236240
if (index == -1)
237241
{
@@ -293,7 +297,8 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
293297
case NetworkListEvent<T>.EventType.Value:
294298
{
295299
reader.ReadValueSafe(out int index);
296-
NetworkVariableSerialization<T>.Read(reader, out T value);
300+
var value = new T();
301+
NetworkVariableSerialization<T>.Read(reader, ref value);
297302
if (index >= m_List.Length)
298303
{
299304
throw new Exception("Shouldn't be here, index is higher than list length");

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

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using UnityEngine;
22
using System;
3-
using System.Runtime.CompilerServices;
4-
using Unity.Collections.LowLevel.Unsafe;
53

64
namespace Unity.Netcode
75
{
@@ -10,7 +8,7 @@ namespace Unity.Netcode
108
/// </summary>
119
/// <typeparam name="T">the unmanaged type for <see cref="NetworkVariable{T}"/> </typeparam>
1210
[Serializable]
13-
public class NetworkVariable<T> : NetworkVariableBase where T : unmanaged
11+
public class NetworkVariable<T> : NetworkVariableBase
1412
{
1513
/// <summary>
1614
/// Delegate type for value changed event
@@ -52,7 +50,7 @@ public virtual T Value
5250
set
5351
{
5452
// Compare bitwise
55-
if (ValueEquals(ref m_InternalValue, ref value))
53+
if (NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref value))
5654
{
5755
return;
5856
}
@@ -66,20 +64,6 @@ public virtual T Value
6664
}
6765
}
6866

69-
// Compares two values of the same unmanaged type by underlying memory
70-
// Ignoring any overridden value checks
71-
// Size is fixed
72-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
73-
private static unsafe bool ValueEquals(ref T a, ref T b)
74-
{
75-
// get unmanaged pointers
76-
var aptr = UnsafeUtility.AddressOf(ref a);
77-
var bptr = UnsafeUtility.AddressOf(ref b);
78-
79-
// compare addresses
80-
return UnsafeUtility.MemCmp(aptr, bptr, sizeof(T)) == 0;
81-
}
82-
8367
/// <summary>
8468
/// Sets the <see cref="Value"/>, marks the <see cref="NetworkVariable{T}"/> dirty, and invokes the <see cref="OnValueChanged"/> callback
8569
/// if there are subscribers to that event.
@@ -115,7 +99,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
11599
// would be stored in different fields
116100

117101
T previousValue = m_InternalValue;
118-
NetworkVariableSerialization<T>.Read(reader, out m_InternalValue);
102+
NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
119103

120104
if (keepDirtyDelta)
121105
{
@@ -128,7 +112,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
128112
/// <inheritdoc />
129113
public override void ReadField(FastBufferReader reader)
130114
{
131-
NetworkVariableSerialization<T>.Read(reader, out m_InternalValue);
115+
NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
132116
}
133117

134118
/// <inheritdoc />

0 commit comments

Comments
 (0)