Skip to content

Commit 1c74129

Browse files
authored
feat: Native container serialization (#2375)
1 parent 96def2c commit 1c74129

23 files changed

+6111
-113
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

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

1111
### Added
1212

13+
- Added support for serializing `NativeArray<>` and `NativeList<>` in `FastBufferReader`/`FastBufferWriter`, `BufferSerializer`, `NetworkVariable`, and RPCs. (#2375)
1314
- The location of the automatically-created default network prefab list can now be configured (#2544)
1415
- Added: Message size limits (max single message and max fragmented message) can now be set using NetworkManager.SetMaxSingleMessageSize() and NetworkManager.SetMaxFragmentedMessageSize() for transports that don't work with the default values (#2530)
1516
- Added `NetworkObject.SpawnWithObservers` property (default is true) that when set to false will spawn a `NetworkObject` with no observers and will not be spawned on any client until `NetworkObject.NetworkShow` is invoked. (#2568)

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

Lines changed: 238 additions & 31 deletions
Large diffs are not rendered by default.

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,10 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
123123
if (methodDefinition.Name == nameof(NetworkBehaviour.__beginSendServerRpc) ||
124124
methodDefinition.Name == nameof(NetworkBehaviour.__endSendServerRpc) ||
125125
methodDefinition.Name == nameof(NetworkBehaviour.__beginSendClientRpc) ||
126-
methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc) || methodDefinition.Name == nameof(NetworkBehaviour.__initializeVariables) || methodDefinition.Name == nameof(NetworkBehaviour.__nameNetworkVariable))
126+
methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc) ||
127+
methodDefinition.Name == nameof(NetworkBehaviour.__initializeVariables) ||
128+
methodDefinition.Name == nameof(NetworkBehaviour.__nameNetworkVariable) ||
129+
methodDefinition.Name == nameof(NetworkBehaviour.__createNativeList))
127130
{
128131
methodDefinition.IsFamily = true;
129132
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,14 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth
275275
#endif
276276
}
277277

278+
#pragma warning disable IDE1006 // disable naming rule violation check
279+
// RuntimeAccessModifiersILPP will make this `protected`
280+
internal static NativeList<T> __createNativeList<T>() where T : unmanaged
281+
#pragma warning restore IDE1006 // restore naming rule violation check
282+
{
283+
return new NativeList<T>(Allocator.Temp);
284+
}
285+
278286
internal string GenerateObserverErrorMessage(ClientRpcParams clientRpcParams, ulong targetClientId)
279287
{
280288
var containerNameHoldingId = clientRpcParams.Send.TargetClientIds != null ? nameof(ClientRpcParams.Send.TargetClientIds) : nameof(ClientRpcParams.Send.TargetClientIdsNativeArray);

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

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ public NetworkVariable(T value = default,
3333
: base(readPerm, writePerm)
3434
{
3535
m_InternalValue = value;
36+
// Since we start with IsDirty = true, this doesn't need to be duplicated
37+
// right away. It won't get read until after ResetDirty() is called, and
38+
// the duplicate will be made there. Avoiding calling
39+
// NetworkVariableSerialization<T>.Duplicate() is important because calling
40+
// it in the constructor might not give users enough time to set the
41+
// DuplicateValue callback if they're using UserNetworkVariableSerialization
42+
m_PreviousValue = default;
3643
}
3744

3845
/// <summary>
@@ -41,6 +48,11 @@ public NetworkVariable(T value = default,
4148
[SerializeField]
4249
private protected T m_InternalValue;
4350

51+
private protected T m_PreviousValue;
52+
53+
private bool m_HasPreviousValue;
54+
private bool m_IsDisposed;
55+
4456
/// <summary>
4557
/// The value of the NetworkVariable container
4658
/// </summary>
@@ -61,9 +73,83 @@ public virtual T Value
6173
}
6274

6375
Set(value);
76+
m_IsDisposed = false;
6477
}
6578
}
6679

80+
internal ref T RefValue()
81+
{
82+
return ref m_InternalValue;
83+
}
84+
85+
public override void Dispose()
86+
{
87+
if (m_IsDisposed)
88+
{
89+
return;
90+
}
91+
92+
m_IsDisposed = true;
93+
if (m_InternalValue is IDisposable internalValueDisposable)
94+
{
95+
internalValueDisposable.Dispose();
96+
}
97+
98+
m_InternalValue = default;
99+
if (m_HasPreviousValue && m_PreviousValue is IDisposable previousValueDisposable)
100+
{
101+
m_HasPreviousValue = false;
102+
previousValueDisposable.Dispose();
103+
}
104+
105+
m_PreviousValue = default;
106+
}
107+
108+
~NetworkVariable()
109+
{
110+
Dispose();
111+
}
112+
113+
/// <summary>
114+
/// Gets Whether or not the container is dirty
115+
/// </summary>
116+
/// <returns>Whether or not the container is dirty</returns>
117+
public override bool IsDirty()
118+
{
119+
// For most cases we can use the dirty flag.
120+
// This doesn't work for cases where we're wrapping more complex types
121+
// like INetworkSerializable, NativeList, NativeArray, etc.
122+
// Changes to the values in those types don't call the Value.set method,
123+
// so we can't catch those changes and need to compare the current value
124+
// against the previous one.
125+
if (base.IsDirty())
126+
{
127+
return true;
128+
}
129+
130+
// Cache the dirty value so we don't perform this again if we already know we're dirty
131+
// Unfortunately we can't cache the NOT dirty state, because that might change
132+
// in between to checks... but the DIRTY state won't change until ResetDirty()
133+
// is called.
134+
var dirty = !NetworkVariableSerialization<T>.AreEqual(ref m_PreviousValue, ref m_InternalValue);
135+
SetDirty(dirty);
136+
return dirty;
137+
}
138+
139+
/// <summary>
140+
/// Resets the dirty state and marks the variable as synced / clean
141+
/// </summary>
142+
public override void ResetDirty()
143+
{
144+
base.ResetDirty();
145+
// Resetting the dirty value declares that the current value is not dirty
146+
// Therefore, we set the m_PreviousValue field to a duplicate of the current
147+
// field, so that our next dirty check is made against the current "not dirty"
148+
// value.
149+
m_HasPreviousValue = true;
150+
NetworkVariableSerialization<T>.Serializer.Duplicate(m_InternalValue, ref m_PreviousValue);
151+
}
152+
67153
/// <summary>
68154
/// Sets the <see cref="Value"/>, marks the <see cref="NetworkVariable{T}"/> dirty, and invokes the <see cref="OnValueChanged"/> callback
69155
/// if there are subscribers to that event.

0 commit comments

Comments
 (0)