-
Notifications
You must be signed in to change notification settings - Fork 459
Expand file tree
/
Copy pathNetworkVariableDeltaMessage.cs
More file actions
408 lines (361 loc) · 21.7 KB
/
NetworkVariableDeltaMessage.cs
File metadata and controls
408 lines (361 loc) · 21.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Unity.Collections;
namespace Unity.Netcode
{
/// <summary>
/// This particular struct is a little weird because it doesn't actually contain the data
/// it's serializing. Instead, it contains references to the data it needs to do the
/// serialization. This is due to the generally amorphous nature of network variable
/// deltas, since they're all driven by custom virtual method overloads.
/// </summary>
/// <remarks>
/// Version 1:
/// This version -does not- use the "KeepDirty" approach. Instead, the server will forward any state updates
/// to the connected clients that are not the sender or the server itself. Each NetworkVariable state update
/// included, on a per client basis, is first validated that the client can read the NetworkVariable before
/// being added to the m_ForwardUpdates table.
/// Version 0:
/// The original version uses the "KeepDirty" approach in a client-server network topology where the server
/// proxies state updates by "keeping the NetworkVariable(s) dirty" so it will send state updates
/// at the end of the frame (but could delay until the next tick).
/// </remarks>
internal struct NetworkVariableDeltaMessage : INetworkMessage
{
private const int k_ServerDeltaForwardingAndNetworkDelivery = 1;
public int Version => k_ServerDeltaForwardingAndNetworkDelivery;
public ulong NetworkObjectId;
public ushort NetworkBehaviourIndex;
public HashSet<int> DeliveryMappedNetworkVariableIndex;
public ulong TargetClientId;
public NetworkBehaviour NetworkBehaviour;
public NetworkDelivery NetworkDelivery;
private FastBufferReader m_ReceivedNetworkVariableData;
private bool m_ForwardingMessage;
private int m_ReceivedMessageVersion;
private const string k_Name = "NetworkVariableDeltaMessage";
private Dictionary<ulong, List<int>> m_ForwardUpdates;
private List<int> m_UpdatedNetworkVariables;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteNetworkVariable(ref FastBufferWriter writer, ref NetworkVariableBase networkVariable, bool ensureNetworkVariableLengthSafety, int nonfragmentedSize, int fragmentedSize)
{
if (ensureNetworkVariableLengthSafety)
{
var tempWriter = new FastBufferWriter(nonfragmentedSize, Allocator.Temp, fragmentedSize);
networkVariable.WriteDelta(tempWriter);
BytePacker.WriteValueBitPacked(writer, tempWriter.Length);
if (!writer.TryBeginWrite(tempWriter.Length))
{
throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
}
tempWriter.CopyTo(writer);
}
else
{
networkVariable.WriteDelta(writer);
}
}
public void Serialize(FastBufferWriter writer, int targetVersion)
{
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
{
throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
}
var obj = NetworkBehaviour.NetworkObject;
var networkManager = obj.NetworkManagerOwner;
var typeName = NetworkBehaviour.__getTypeName();
var nonFragmentedMessageMaxSize = networkManager.MessageManager.NonFragmentedMessageMaxSize;
var fragmentedMessageMaxSize = networkManager.MessageManager.FragmentedMessageMaxSize;
var ensureNetworkVariableLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety;
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
BytePacker.WriteValueBitPacked(writer, NetworkBehaviourIndex);
// If using k_IncludeNetworkDelivery version, then we want to write the network delivery used and if we
// are forwarding state updates then serialize any NetworkVariable states specific to this client.
if (targetVersion >= k_ServerDeltaForwardingAndNetworkDelivery)
{
writer.WriteValueSafe(NetworkDelivery);
// If we are forwarding the message, then proceed to forward state updates specific to the targeted client
if (m_ForwardingMessage)
{
for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++)
{
var startingSize = writer.Length;
var networkVariable = NetworkBehaviour.NetworkVariableFields[i];
var shouldWrite = m_ForwardUpdates[TargetClientId].Contains(i);
if (ensureNetworkVariableLengthSafety)
{
if (!shouldWrite)
{
BytePacker.WriteValueBitPacked(writer, 0);
}
}
else
{
writer.WriteValueSafe(shouldWrite);
}
if (shouldWrite)
{
WriteNetworkVariable(ref writer, ref networkVariable, ensureNetworkVariableLengthSafety, nonFragmentedMessageMaxSize, fragmentedMessageMaxSize);
networkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(TargetClientId, obj, networkVariable.Name, typeName, writer.Length - startingSize);
}
}
return;
}
}
for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++)
{
if (!DeliveryMappedNetworkVariableIndex.Contains(i))
{
if (ensureNetworkVariableLengthSafety)
{
BytePacker.WriteValueBitPacked(writer, (ushort)0);
}
else
{
writer.WriteValueSafe(false);
}
continue;
}
var startingSize = writer.Length;
var networkVariable = NetworkBehaviour.NetworkVariableFields[i];
var shouldWrite = networkVariable.IsDirty() &&
networkVariable.CanClientRead(TargetClientId)
&& (networkManager.IsServer ||
(networkVariable.CanWrite && networkVariable.CanSend()));
// Prevent the server from writing to the client that owns a given NetworkVariable
// Allowing the write would send an old value to the client and cause jitter
if (networkVariable.WritePerm == NetworkVariableWritePermission.Owner &&
networkVariable.OwnerClientId() == TargetClientId)
{
shouldWrite = false;
}
// The object containing the behaviour we're about to process is about to be shown to this client
// As a result, the client will get the fully serialized NetworkVariable and would be confused by
// an extraneous delta
if (networkManager.SpawnManager.ObjectsToShowToClient.ContainsKey(TargetClientId) &&
networkManager.SpawnManager.ObjectsToShowToClient[TargetClientId]
.Contains(obj))
{
shouldWrite = false;
}
if (ensureNetworkVariableLengthSafety)
{
if (!shouldWrite)
{
BytePacker.WriteValueBitPacked(writer, 0);
}
}
else
{
writer.WriteValueSafe(shouldWrite);
}
if (shouldWrite)
{
WriteNetworkVariable(ref writer, ref networkVariable, ensureNetworkVariableLengthSafety, nonFragmentedMessageMaxSize, fragmentedMessageMaxSize);
networkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(TargetClientId, obj, networkVariable.Name, typeName, writer.Length - startingSize);
}
}
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
m_ReceivedMessageVersion = receivedMessageVersion;
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
ByteUnpacker.ReadValueBitPacked(reader, out NetworkBehaviourIndex);
// If we are using the k_IncludeNetworkDelivery message version, then read the NetworkDelivery used
if (receivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery)
{
reader.ReadValueSafe(out NetworkDelivery);
}
m_ReceivedNetworkVariableData = reader;
return true;
}
public void Handle(ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject))
{
var ensureNetworkVariableLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety;
var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex);
var isServerAndDeltaForwarding = m_ReceivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery && networkManager.IsServer;
var markNetworkVariableDirty = m_ReceivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery ? false : networkManager.IsServer;
m_UpdatedNetworkVariables = new List<int>();
if (networkBehaviour == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"Network variable delta message received for a non-existent behaviour. {nameof(NetworkObjectId)}: {NetworkObjectId}, {nameof(NetworkBehaviourIndex)}: {NetworkBehaviourIndex}");
}
}
else
{
// (For client-server) As opposed to worrying about adding additional processing on the server to send NetworkVariable
// updates at the end of the frame, we now track all NetworkVariable state updates, per client, that need to be forwarded
// to the client. This creates a list of all remaining connected clients that could have updates applied.
if (isServerAndDeltaForwarding)
{
m_ForwardUpdates = new Dictionary<ulong, List<int>>();
foreach (var clientId in networkManager.ConnectedClientsIds)
{
if (clientId == context.SenderId || clientId == networkManager.LocalClientId || !networkObject.Observers.Contains(clientId))
{
continue;
}
m_ForwardUpdates.Add(clientId, new List<int>());
}
}
// Update NetworkVariable Fields
for (int i = 0; i < networkBehaviour.NetworkVariableFields.Count; i++)
{
int expectedBytesToRead = 0;
var networkVariable = networkBehaviour.NetworkVariableFields[i];
if (ensureNetworkVariableLengthSafety)
{
ByteUnpacker.ReadValueBitPacked(m_ReceivedNetworkVariableData, out expectedBytesToRead);
if (expectedBytesToRead == 0)
{
continue;
}
}
else
{
m_ReceivedNetworkVariableData.ReadValueSafe(out bool deltaExists);
if (!deltaExists)
{
continue;
}
}
if (networkManager.IsServer && !networkVariable.CanClientWrite(context.SenderId))
{
// we are choosing not to fire an exception here, because otherwise a malicious client could use this to crash the server
if (ensureNetworkVariableLengthSafety)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogWarning($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
NetworkLog.LogError($"[{networkVariable.GetType().Name}]");
}
m_ReceivedNetworkVariableData.Seek(m_ReceivedNetworkVariableData.Position + expectedBytesToRead);
continue;
}
//This client wrote somewhere they are not allowed. This is critical
//We can't just skip this field. Because we don't actually know how to dummy read
//That is, we don't know how many bytes to skip. Because the interface doesn't have a
//Read that gives us the value. Only a Read that applies the value straight away
//A dummy read COULD be added to the interface for this situation, but it's just being too nice.
//This is after all a developer fault. A critical error should be fine.
// - TwoTen
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogError($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. No more variables can be read. This is critical. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
NetworkLog.LogError($"[{networkVariable.GetType().Name}]");
}
return;
}
var readStartPos = m_ReceivedNetworkVariableData.Position;
if (ensureNetworkVariableLengthSafety)
{
var remainingBufferSize = m_ReceivedNetworkVariableData.Length - readStartPos;
if (expectedBytesToRead > remainingBufferSize)
{
UnityEngine.Debug.LogError($"[{networkBehaviour.name}][Delta State Read Error] Expecting to read {expectedBytesToRead} but only {remainingBufferSize} remains!");
return;
}
}
// Added a try catch here to assure any failure will only fail on this one message and not disrupt the stack
try
{
// Read the delta
networkVariable.ReadDelta(m_ReceivedNetworkVariableData, markNetworkVariableDirty);
// Add the NetworkVariable field index so we can invoke the PostDeltaRead
m_UpdatedNetworkVariables.Add(i);
}
catch (Exception ex)
{
UnityEngine.Debug.LogException(ex);
return;
}
if (ensureNetworkVariableLengthSafety)
{
var totalBytesRead = m_ReceivedNetworkVariableData.Position - readStartPos;
if (totalBytesRead != expectedBytesToRead)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"[{nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}][Delta State Read] NetworkVariable read {totalBytesRead} bytes but was expected to read {expectedBytesToRead} bytes!");
}
m_ReceivedNetworkVariableData.Seek(readStartPos + expectedBytesToRead);
}
}
// (For client-server) As opposed to worrying about adding additional processing on the server to send NetworkVariable
// updates at the end of the frame, we now track all NetworkVariable state updates, per client, that need to be forwarded
// to the client. This happens once the server is finished processing all state updates for this message.
if (isServerAndDeltaForwarding)
{
foreach (var forwardEntry in m_ForwardUpdates)
{
// Only track things that the client can read
if (networkVariable.CanClientRead(forwardEntry.Key))
{
// If the object is about to be shown to the client then don't send an update as it will
// send a full update when shown.
if (networkManager.SpawnManager.ObjectsToShowToClient.ContainsKey(forwardEntry.Key) &&
networkManager.SpawnManager.ObjectsToShowToClient[forwardEntry.Key]
.Contains(networkObject))
{
continue;
}
forwardEntry.Value.Add(i);
}
}
}
networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived(
context.SenderId,
networkObject,
networkVariable.Name,
networkBehaviour.__getTypeName(),
context.MessageSize);
}
// If we are using the version of this message that includes network delivery, then
// forward this update to all connected clients (other than the sender and the server).
if (isServerAndDeltaForwarding)
{
var message = new NetworkVariableDeltaMessage()
{
NetworkBehaviour = networkBehaviour,
NetworkBehaviourIndex = NetworkBehaviourIndex,
NetworkObjectId = NetworkObjectId,
m_ForwardingMessage = true,
m_ForwardUpdates = m_ForwardUpdates,
};
foreach (var forwardEntry in m_ForwardUpdates)
{
// Only forward updates to any client that has visibility to the state updates included in this message
if (forwardEntry.Value.Count > 0)
{
message.TargetClientId = forwardEntry.Key;
networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery, forwardEntry.Key);
}
}
}
// This should be always invoked (client & server) to assure the previous values are set
// !! IMPORTANT ORDER OF OPERATIONS !! (Has to happen after forwarding deltas)
// When a server forwards delta updates to connected clients, it needs to preserve the previous value
// until it is done serializing all valid NetworkVariable field deltas (relative to each client). This
// is invoked after it is done forwarding the deltas.
foreach (var fieldIndex in m_UpdatedNetworkVariables)
{
networkBehaviour.NetworkVariableFields[fieldIndex].PostDeltaRead();
}
}
}
else
{
// DANGO-TODO: Fix me!
// When a client-spawned NetworkObject is despawned by the owner client, the owner client will still get messages for deltas and cause this to
// log a warning. The issue is primarily how NetworkVariables handle updating and will require some additional re-factoring.
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context, k_Name);
}
}
}
}