forked from Unity-Technologies/com.unity.netcode.gameobjects
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathNetworkPrefabHandler.cs
447 lines (411 loc) · 24.7 KB
/
NetworkPrefabHandler.cs
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
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Unity.Netcode
{
/// <summary>
/// Interface for customizing, overriding, spawning, and destroying Network Prefabs
/// Used by <see cref="NetworkPrefabHandler"/>
/// </summary>
public interface INetworkPrefabInstanceHandler
{
/// <summary>
/// Client Side Only
/// Once an implementation is registered with the <see cref="NetworkPrefabHandler"/>, this method will be called every time
/// a Network Prefab associated <see cref="NetworkObject"/> is spawned on clients
///
/// Note On Hosts: Use the <see cref="NetworkPrefabHandler.RegisterHostGlobalObjectIdHashValues(GameObject, List{GameObject})"/>
/// method to register all targeted NetworkPrefab overrides manually since the host will be acting as both a server and client.
///
/// Note on Pooling: If you are using a NetworkObject pool, don't forget to make the NetworkObject active
/// via the <see cref="GameObject.SetActive(bool)"/> method.
/// </summary>
/// <param name="ownerClientId">the owner for the <see cref="NetworkObject"/> to be instantiated</param>
/// <param name="position">the initial/default position for the <see cref="NetworkObject"/> to be instantiated</param>
/// <param name="rotation">the initial/default rotation for the <see cref="NetworkObject"/> to be instantiated</param>
/// <returns>The instantiated NetworkObject instance. Returns null if instantiation fails.</returns>
NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation);
/// <summary>
/// Invoked on Client and Server
/// Once an implementation is registered with the <see cref="NetworkPrefabHandler"/>, this method will be called when
/// a Network Prefab associated <see cref="NetworkObject"/> is:
///
/// Server Side: destroyed or despawned with the destroy parameter equal to true
/// If <see cref="NetworkObject.Despawn(bool)"/> is invoked with the default destroy parameter (i.e. false) then this method will NOT be invoked!
///
/// Client Side: destroyed when the client receives a destroy object message from the server or host.
///
/// Note on Pooling: When this method is invoked, you do not need to destroy the NetworkObject as long as you want your pool to persist.
/// The most common approach is to make the <see cref="NetworkObject"/> inactive by calling <see cref="GameObject.SetActive(bool)"/>.
/// </summary>
/// <param name="networkObject">The <see cref="NetworkObject"/> being destroyed</param>
void Destroy(NetworkObject networkObject);
}
/// <summary>
/// Primary handler to add or remove customized spawn and destroy handlers for a network prefab (i.e. a prefab with a NetworkObject component)
/// Register custom prefab handlers by implementing the <see cref="INetworkPrefabInstanceHandler"/> interface.
/// </summary>
public class NetworkPrefabHandler
{
private NetworkManager m_NetworkManager;
/// <summary>
/// Links a network prefab asset to a class with the INetworkPrefabInstanceHandler interface
/// </summary>
private readonly Dictionary<uint, INetworkPrefabInstanceHandler> m_PrefabAssetToPrefabHandler = new Dictionary<uint, INetworkPrefabInstanceHandler>();
/// <summary>
/// Links the custom prefab instance's GlobalNetworkObjectId to the original prefab asset's GlobalNetworkObjectId. (Needed for HandleNetworkPrefabDestroy)
/// [PrefabInstance][PrefabAsset]
/// </summary>
private readonly Dictionary<uint, uint> m_PrefabInstanceToPrefabAsset = new Dictionary<uint, uint>();
internal static string PrefabDebugHelper(NetworkPrefab networkPrefab) => $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.name}\"";
/// <summary>
/// Use a <see cref="GameObject"/> to register a class that implements the <see cref="INetworkPrefabInstanceHandler"/> interface with the <see cref="NetworkPrefabHandler"/>
/// </summary>
/// <param name="networkPrefabAsset">the <see cref="GameObject"/> of the network prefab asset to be overridden</param>
/// <param name="instanceHandler">class that implements the <see cref="INetworkPrefabInstanceHandler"/> interface to be registered</param>
/// <returns>true (registered) false (failed to register)</returns>
public bool AddHandler(GameObject networkPrefabAsset, INetworkPrefabInstanceHandler instanceHandler)
{
return AddHandler(networkPrefabAsset.GetComponent<NetworkObject>().GlobalObjectIdHash, instanceHandler);
}
/// <summary>
/// Use a <see cref="NetworkObject"/> to register a class that implements the <see cref="INetworkPrefabInstanceHandler"/> interface with the <see cref="NetworkPrefabHandler"/>
/// </summary>
/// <param name="prefabAssetNetworkObject"> the <see cref="NetworkObject"/> of the network prefab asset to be overridden</param>
/// <param name="instanceHandler">the class that implements the <see cref="INetworkPrefabInstanceHandler"/> interface to be registered</param>
/// <returns>true (registered) false (failed to register)</returns>
public bool AddHandler(NetworkObject prefabAssetNetworkObject, INetworkPrefabInstanceHandler instanceHandler)
{
return AddHandler(prefabAssetNetworkObject.GlobalObjectIdHash, instanceHandler);
}
/// <summary>
/// Use a <see cref="NetworkObject.GlobalObjectIdHash"/> to register a class that implements the <see cref="INetworkPrefabInstanceHandler"/> interface with the <see cref="NetworkPrefabHandler"/>
/// </summary>
/// <param name="globalObjectIdHash"> the <see cref="NetworkObject.GlobalObjectIdHash"/> value of the network prefab asset being overridden</param>
/// <param name="instanceHandler">a class that implements the <see cref="INetworkPrefabInstanceHandler"/> interface</param>
/// <returns>true (registered) false (failed to register)</returns>
public bool AddHandler(uint globalObjectIdHash, INetworkPrefabInstanceHandler instanceHandler)
{
if (!m_PrefabAssetToPrefabHandler.ContainsKey(globalObjectIdHash))
{
m_PrefabAssetToPrefabHandler.Add(globalObjectIdHash, instanceHandler);
return true;
}
return false;
}
/// <summary>
/// HOST ONLY!
/// Since a host is unique and is considered both a client and a server, for each source NetworkPrefab you must manually
/// register all potential <see cref="GameObject"/> target overrides that have the <see cref="NetworkObject"/> component.
/// </summary>
/// <param name="sourceNetworkPrefab">source NetworkPrefab to be overridden</param>
/// <param name="networkPrefabOverrides">one or more NetworkPrefabs could be used to override the source NetworkPrefab</param>
public void RegisterHostGlobalObjectIdHashValues(GameObject sourceNetworkPrefab, List<GameObject> networkPrefabOverrides)
{
if (NetworkManager.Singleton.IsListening)
{
if (NetworkManager.Singleton.IsHost)
{
var sourceNetworkObject = sourceNetworkPrefab.GetComponent<NetworkObject>();
if (sourceNetworkPrefab != null)
{
var sourceGlobalObjectIdHash = sourceNetworkObject.GlobalObjectIdHash;
// Now we register all
foreach (var gameObject in networkPrefabOverrides)
{
if (gameObject.TryGetComponent<NetworkObject>(out var targetNetworkObject))
{
if (!m_PrefabInstanceToPrefabAsset.ContainsKey(targetNetworkObject.GlobalObjectIdHash))
{
m_PrefabInstanceToPrefabAsset.Add(targetNetworkObject.GlobalObjectIdHash, sourceGlobalObjectIdHash);
}
else
{
Debug.LogWarning($"{targetNetworkObject.name} appears to be a duplicate entry!");
}
}
else
{
throw new Exception($"{targetNetworkObject.name} does not have a {nameof(NetworkObject)} component!");
}
}
}
else
{
throw new Exception($"{sourceNetworkPrefab.name} does not have a {nameof(NetworkObject)} component!");
}
}
else
{
throw new Exception($"You should only call {nameof(RegisterHostGlobalObjectIdHashValues)} as a Host!");
}
}
else
{
throw new Exception($"You can only call {nameof(RegisterHostGlobalObjectIdHashValues)} once NetworkManager is listening!");
}
}
/// <summary>
/// Use the <see cref="GameObject"/> of the overridden network prefab asset to remove a registered class that implements the <see cref="INetworkPrefabInstanceHandler"/> interface.
/// </summary>
/// <param name="networkPrefabAsset"><see cref="GameObject"/> of the network prefab asset that was being overridden</param>
/// <returns>true (success) or false (failure)</returns>
public bool RemoveHandler(GameObject networkPrefabAsset)
{
return RemoveHandler(networkPrefabAsset.GetComponent<NetworkObject>().GlobalObjectIdHash);
}
/// <summary>
/// Use the <see cref="NetworkObject"/> of the overridden network prefab asset to remove a registered class that implements the <see cref="INetworkPrefabInstanceHandler"/> interface.
/// </summary>
/// <param name="networkObject"><see cref="NetworkObject"/> of the source NetworkPrefab that was being overridden</param>
/// <returns>true (success) or false (failure)</returns>
public bool RemoveHandler(NetworkObject networkObject)
{
return RemoveHandler(networkObject.GlobalObjectIdHash);
}
/// <summary>
/// Use the <see cref="NetworkObject.GlobalObjectIdHash"/> of the overridden network prefab asset to remove a registered class that implements the <see cref="INetworkPrefabInstanceHandler"/> interface.
/// </summary>
/// <param name="globalObjectIdHash"><see cref="NetworkObject.GlobalObjectIdHash"/> of the source NetworkPrefab that was being overridden</param>
/// <returns>true (success) or false (failure)</returns>
public bool RemoveHandler(uint globalObjectIdHash)
{
if (m_PrefabInstanceToPrefabAsset.ContainsValue(globalObjectIdHash))
{
uint networkPrefabHashKey = 0;
foreach (var kvp in m_PrefabInstanceToPrefabAsset)
{
if (kvp.Value == globalObjectIdHash)
{
networkPrefabHashKey = kvp.Key;
break;
}
}
m_PrefabInstanceToPrefabAsset.Remove(networkPrefabHashKey);
}
return m_PrefabAssetToPrefabHandler.Remove(globalObjectIdHash);
}
/// <summary>
/// Check to see if a <see cref="GameObject"/> with a <see cref="NetworkObject"/> is registered to an <see cref="INetworkPrefabInstanceHandler"/> implementation
/// </summary>
/// <param name="networkPrefab"></param>
/// <returns>true or false</returns>
internal bool ContainsHandler(GameObject networkPrefab) => ContainsHandler(networkPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash);
/// <summary>
/// Check to see if a <see cref="NetworkObject"/> is registered to an <see cref="INetworkPrefabInstanceHandler"/> implementation
/// </summary>
/// <param name="networkObject"></param>
/// <returns>true or false</returns>
internal bool ContainsHandler(NetworkObject networkObject) => ContainsHandler(networkObject.GlobalObjectIdHash);
/// <summary>
/// Check to see if a <see cref="NetworkObject.GlobalObjectIdHash"/> is registered to an <see cref="INetworkPrefabInstanceHandler"/> implementation
/// </summary>
/// <param name="networkPrefabHash"></param>
/// <returns>true or false</returns>
internal bool ContainsHandler(uint networkPrefabHash) => m_PrefabAssetToPrefabHandler.ContainsKey(networkPrefabHash) || m_PrefabInstanceToPrefabAsset.ContainsKey(networkPrefabHash);
/// <summary>
/// Returns the source NetworkPrefab's <see cref="NetworkObject.GlobalObjectIdHash"/>
/// </summary>
/// <param name="networkPrefabHash"></param>
/// <returns></returns>
internal uint GetSourceGlobalObjectIdHash(uint networkPrefabHash)
{
if (m_PrefabAssetToPrefabHandler.ContainsKey(networkPrefabHash))
{
return networkPrefabHash;
}
if (m_PrefabInstanceToPrefabAsset.TryGetValue(networkPrefabHash, out var hash))
{
return hash;
}
return 0;
}
/// <summary>
/// Will return back a <see cref="NetworkObject"/> generated via an <see cref="INetworkPrefabInstanceHandler"/> implementation
/// Note: Invoked only on the client side and called within NetworkSpawnManager.CreateLocalNetworkObject
/// </summary>
/// <param name="networkPrefabAssetHash">typically the "server-side" asset's prefab hash</param>
/// <param name="ownerClientId"></param>
/// <param name="position"></param>
/// <param name="rotation"></param>
/// <returns></returns>
internal NetworkObject HandleNetworkPrefabSpawn<T>(uint networkPrefabAssetHash, ulong ownerClientId, ref BufferSerializer<T> preInstanceDataSerializer, Vector3 position, Quaternion rotation) where T : IReaderWriter
{
if (m_PrefabAssetToPrefabHandler.TryGetValue(networkPrefabAssetHash, out var prefabInstanceHandler))
{
if (prefabInstanceHandler is INetworkInstantiationPayloadSynchronizer synchronizer)
{
synchronizer.OnSynchronize(ref preInstanceDataSerializer);
}
var networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation);
if (networkObjectInstance != null)
{
if (preInstanceDataSerializer.IsReader)
{
networkObjectInstance.InstantiationPayload = preInstanceDataSerializer.GetFastBufferReader();
}
else
{
var writer = preInstanceDataSerializer.GetFastBufferWriter();
if (writer.Length > 0)
{
unsafe
{
networkObjectInstance.InstantiationPayload = new FastBufferReader(writer.GetUnsafePtr(), Collections.Allocator.Persistent, writer.Length);
}
}
}
}
//Now we must make sure this alternate PrefabAsset spawned in place of the prefab asset with the networkPrefabAssetHash (GlobalObjectIdHash)
//is registered and linked to the networkPrefabAssetHash so during the HandleNetworkPrefabDestroy process we can identify the alternate prefab asset.
if (networkObjectInstance != null && !m_PrefabInstanceToPrefabAsset.ContainsKey(networkObjectInstance.GlobalObjectIdHash))
{
m_PrefabInstanceToPrefabAsset.Add(networkObjectInstance.GlobalObjectIdHash, networkPrefabAssetHash);
}
return networkObjectInstance;
}
return null;
}
/// <summary>
/// Will invoke the <see cref="INetworkPrefabInstanceHandler"/> implementation's Destroy method
/// </summary>
/// <param name="networkObjectInstance"></param>
internal void HandleNetworkPrefabDestroy(NetworkObject networkObjectInstance)
{
var networkObjectInstanceHash = networkObjectInstance.GlobalObjectIdHash;
// Do we have custom overrides registered?
if (m_PrefabInstanceToPrefabAsset.TryGetValue(networkObjectInstanceHash, out var networkPrefabAssetHash))
{
if (m_PrefabAssetToPrefabHandler.TryGetValue(networkPrefabAssetHash, out var prefabInstanceHandler))
{
prefabInstanceHandler.Destroy(networkObjectInstance);
}
}
else // Otherwise the NetworkObject is the source NetworkPrefab
if (m_PrefabAssetToPrefabHandler.TryGetValue(networkObjectInstanceHash, out var prefabInstanceHandler))
{
prefabInstanceHandler.Destroy(networkObjectInstance);
}
}
/// <summary>
/// Returns the <see cref="GameObject"/> to use as the override as could be defined within the NetworkPrefab list
/// Note: This should be used to create <see cref="GameObject"/> pools (with <see cref="NetworkObject"/> components)
/// under the scenario where you are using the Host model as it spawns everything locally. As such, the override
/// will not be applied when spawning locally on a Host.
/// Related Classes and Interfaces:
/// <see cref="INetworkPrefabInstanceHandler"/>
/// </summary>
/// <param name="gameObject">the <see cref="GameObject"/> to be checked for a <see cref="NetworkManager"/> defined NetworkPrefab override</param>
/// <returns>a <see cref="GameObject"/> that is either the override or if no overrides exist it returns the same as the one passed in as a parameter</returns>
public GameObject GetNetworkPrefabOverride(GameObject gameObject)
{
if (gameObject.TryGetComponent<NetworkObject>(out var networkObject))
{
if (m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.ContainsKey(networkObject.GlobalObjectIdHash))
{
switch (m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[networkObject.GlobalObjectIdHash].Override)
{
case NetworkPrefabOverride.Hash:
case NetworkPrefabOverride.Prefab:
{
return m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[networkObject.GlobalObjectIdHash].OverridingTargetPrefab;
}
}
}
}
return gameObject;
}
/// <summary>
/// Adds a new prefab to the network prefab list.
/// This can be any GameObject with a NetworkObject component, from any source (addressables, asset
/// bundles, Resource.Load, dynamically created, etc)
///
/// There are three limitations to this method:
/// - If you have NetworkConfig.ForceSamePrefabs enabled, you can only do this before starting
/// networking, and the server and all connected clients must all have the same exact set of prefabs
/// added via this method before connecting
/// - Adding a prefab on the server does not automatically add it on the client - it's up to you
/// to make sure the client and server are synchronized via whatever method makes sense for your game
/// (RPCs, configurations, deterministic loading, etc)
/// - If the server sends a Spawn message to a client that has not yet added a prefab for, the spawn message
/// and any other relevant messages will be held for a configurable time (default 1 second, configured via
/// NetworkConfig.SpawnTimeout) before an error is logged. This is intended to enable the SDK to gracefully
/// handle unexpected conditions (slow disks, slow network, etc) that slow down asset loading. This timeout
/// should not be relied on and code shouldn't be written around it - your code should be written so that
/// the asset is expected to be loaded before it's needed.
/// </summary>
/// <param name="prefab">The GameObject with NetworkObject component to add as a network prefab</param>
/// <exception cref="Exception">Thrown when adding prefabs after startup with ForceSamePrefabs is enabled or prefab doesn't have a NetworkObject component</exception>
public void AddNetworkPrefab(GameObject prefab)
{
if (m_NetworkManager.IsListening && m_NetworkManager.NetworkConfig.ForceSamePrefabs)
{
throw new Exception($"All prefabs must be registered before starting {nameof(NetworkManager)} when {nameof(NetworkConfig.ForceSamePrefabs)} is enabled.");
}
var networkObject = prefab.GetComponent<NetworkObject>();
if (!networkObject)
{
throw new Exception($"All {nameof(NetworkPrefab)}s must contain a {nameof(NetworkObject)} component.");
}
var networkPrefab = new NetworkPrefab { Prefab = prefab };
bool added = m_NetworkManager.NetworkConfig.Prefabs.Add(networkPrefab);
if (m_NetworkManager.IsListening && added)
{
m_NetworkManager.DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, networkObject.GlobalObjectIdHash);
}
}
/// <summary>
/// Remove a prefab from the prefab list.
/// As with AddNetworkPrefab, this is specific to the client it's called on -
/// calling it on the server does not automatically remove anything on any of the
/// client processes.
///
/// Like AddNetworkPrefab, when NetworkConfig.ForceSamePrefabs is enabled,
/// this cannot be called after connecting.
/// </summary>
/// <param name="prefab">The GameObject to remove from the network prefab list</param>
/// <exception cref="Exception">Thrown when attempting to remove prefabs after startup with ForceSamePrefabs enabled</exception>
public void RemoveNetworkPrefab(GameObject prefab)
{
if (m_NetworkManager.IsListening && m_NetworkManager.NetworkConfig.ForceSamePrefabs)
{
throw new Exception($"Prefabs cannot be removed after starting {nameof(NetworkManager)} when {nameof(NetworkConfig.ForceSamePrefabs)} is enabled.");
}
var globalObjectIdHash = prefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
m_NetworkManager.NetworkConfig.Prefabs.Remove(prefab);
if (ContainsHandler(globalObjectIdHash))
{
RemoveHandler(globalObjectIdHash);
}
}
/// <summary>
/// If one exists, registers the player prefab
/// </summary>
internal void RegisterPlayerPrefab()
{
var networkConfig = m_NetworkManager.NetworkConfig;
// If we have a player prefab, then we need to verify it is in the list of NetworkPrefabOverrideLinks for client side spawning.
if (networkConfig.PlayerPrefab != null)
{
if (networkConfig.PlayerPrefab.TryGetComponent<NetworkObject>(out var playerPrefabNetworkObject))
{
//In the event there is no NetworkPrefab entry (i.e. no override for default player prefab)
if (!networkConfig.Prefabs.NetworkPrefabOverrideLinks.ContainsKey(playerPrefabNetworkObject.GlobalObjectIdHash))
{
//Then add a new entry for the player prefab
AddNetworkPrefab(networkConfig.PlayerPrefab);
}
}
else
{
// Provide the name of the prefab with issues so the user can more easily find the prefab and fix it
Debug.LogError($"{nameof(NetworkConfig.PlayerPrefab)} (\"{networkConfig.PlayerPrefab.name}\") has no NetworkObject assigned to it!.");
}
}
}
internal void Initialize(NetworkManager networkManager)
{
m_NetworkManager = networkManager;
}
}
}