-
Notifications
You must be signed in to change notification settings - Fork 459
Expand file tree
/
Copy pathNetworkManager.cs
More file actions
1364 lines (1158 loc) · 54.9 KB
/
NetworkManager.cs
File metadata and controls
1364 lines (1158 loc) · 54.9 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
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
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine.SceneManagement;
using Debug = UnityEngine.Debug;
namespace Unity.Netcode
{
/// <summary>
/// The main component of the library
/// </summary>
[AddComponentMenu("Netcode/Network Manager", -100)]
public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
{
/// <summary>
/// Subscribe to this static event to get notifications when a <see cref="NetworkManager"/> instance has been instantiated.
/// </summary>
public static event Action<NetworkManager> OnInstantiated;
/// <summary>
/// Subscribe to this static event to get notifications when a <see cref="NetworkManager"/> instance is being destroyed.
/// </summary>
public static event Action<NetworkManager> OnDestroying;
// TODO: Deprecate...
// The following internal values are not used, but because ILPP makes them public in the assembly, they cannot
// be removed thanks to our semver validation.
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `public`
internal delegate void RpcReceiveHandler(NetworkBehaviour behaviour, FastBufferReader reader, __RpcParams parameters);
// RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<uint, RpcReceiveHandler> __rpc_func_table = new Dictionary<uint, RpcReceiveHandler>();
#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
// RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<uint, string> __rpc_name_table = new Dictionary<uint, string>();
#endif
#pragma warning restore IDE1006 // restore naming rule violation check
/// <inheritdoc/>
public void NetworkUpdate(NetworkUpdateStage updateStage)
{
switch (updateStage)
{
case NetworkUpdateStage.EarlyUpdate:
{
ConnectionManager.ProcessPendingApprovals();
ConnectionManager.PollAndHandleNetworkEvents();
DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnNextFrame, 0);
AnticipationSystem.SetupForUpdate();
MessageManager.ProcessIncomingMessageQueue();
MessageManager.CleanupDisconnectedClients();
AnticipationSystem.ProcessReanticipation();
}
break;
case NetworkUpdateStage.PreUpdate:
{
NetworkTimeSystem.UpdateTime();
AnticipationSystem.Update();
}
break;
case NetworkUpdateStage.PostScriptLateUpdate:
AnticipationSystem.Sync();
AnticipationSystem.SetupForRender();
break;
case NetworkUpdateStage.PostLateUpdate:
{
// This should be invoked just prior to the MessageManager processes its outbound queue.
SceneManager.CheckForAndSendNetworkObjectSceneChanged();
// Process outbound messages
MessageManager.ProcessSendQueues();
// Metrics update needs to be driven by NetworkConnectionManager's update to assure metrics are dispatched after the send queue is processed.
MetricsManager.UpdateMetrics();
// TODO: Determine a better way to handle this
NetworkObject.VerifyParentingStatus();
// This is "ok" to invoke when not processing messages since it is just cleaning up messages that never got handled within their timeout period.
DeferredMessageManager.CleanupStaleTriggers();
if (m_ShuttingDown)
{
// Host-server will disconnect any connected clients prior to finalizing its shutdown
if (IsServer)
{
ProcessServerShutdown();
}
else
{
// Clients just disconnect immediately
ShutdownInternal();
}
}
}
break;
}
}
/// <summary>
/// Used to provide a graceful shutdown sequence
/// </summary>
internal enum ServerShutdownStates
{
None,
WaitForClientDisconnects,
InternalShutdown,
ShuttingDown,
};
internal ServerShutdownStates ServerShutdownState;
private float m_ShutdownTimeout;
/// <summary>
/// This is a "soft shutdown" where the host or server will disconnect
/// all clients, with a provided reasons, prior to invoking its final
/// internal shutdown.
/// </summary>
internal void ProcessServerShutdown()
{
var minClientCount = IsHost ? 2 : 1;
switch (ServerShutdownState)
{
case ServerShutdownStates.None:
{
if (ConnectedClients.Count >= minClientCount)
{
var hostServer = IsHost ? "host" : "server";
var disconnectReason = $"Disconnected due to {hostServer} shutting down.";
for (int i = ConnectedClientsIds.Count - 1; i >= 0; i--)
{
var clientId = ConnectedClientsIds[i];
if (clientId == ServerClientId)
{
continue;
}
ConnectionManager.DisconnectClient(clientId, disconnectReason);
}
ServerShutdownState = ServerShutdownStates.WaitForClientDisconnects;
m_ShutdownTimeout = Time.realtimeSinceStartup + 5.0f;
}
else
{
ServerShutdownState = ServerShutdownStates.InternalShutdown;
ProcessServerShutdown();
}
break;
}
case ServerShutdownStates.WaitForClientDisconnects:
{
if (ConnectedClients.Count < minClientCount || m_ShutdownTimeout < Time.realtimeSinceStartup)
{
ServerShutdownState = ServerShutdownStates.InternalShutdown;
ProcessServerShutdown();
}
break;
}
case ServerShutdownStates.InternalShutdown:
{
ServerShutdownState = ServerShutdownStates.ShuttingDown;
ShutdownInternal();
break;
}
}
}
/// <summary>
/// The client id used to represent the server
/// </summary>
public const ulong ServerClientId = 0;
/// <summary>
/// Returns ServerClientId if IsServer or LocalClientId if not
/// </summary>
public ulong LocalClientId
{
get => ConnectionManager.LocalClient.ClientId;
internal set => ConnectionManager.LocalClient.ClientId = value;
}
/// <summary>
/// Gets a dictionary of connected clients and their clientId keys. This is only accessible on the server.
/// </summary>
public IReadOnlyDictionary<ulong, NetworkClient> ConnectedClients => IsServer ? ConnectionManager.ConnectedClients : throw new NotServerException($"{nameof(ConnectionManager.ConnectedClients)} should only be accessed on server.");
/// <summary>
/// Gets a list of connected clients. This is only accessible on the server.
/// </summary>
public IReadOnlyList<NetworkClient> ConnectedClientsList => IsServer ? ConnectionManager.ConnectedClientsList : throw new NotServerException($"{nameof(ConnectionManager.ConnectedClientsList)} should only be accessed on server.");
/// <summary>
/// Gets a list of just the IDs of all connected clients. This is only accessible on the server.
/// </summary>
public IReadOnlyList<ulong> ConnectedClientsIds => ConnectionManager.ConnectedClientIds;
/// <summary>
/// Gets the local <see cref="NetworkClient"/> for this client.
/// </summary>
public NetworkClient LocalClient => ConnectionManager.LocalClient;
/// <summary>
/// Gets a dictionary of the clients that have been accepted by the transport but are still pending by the Netcode. This is only populated on the server.
/// </summary>
// See NetworkConnectionManager.AddPendingClient and NetworkConnectionManager.RemovePendingClient to see how this is now populated
public readonly Dictionary<ulong, PendingClient> PendingClients = new Dictionary<ulong, PendingClient>();
/// <summary>
/// Gets Whether or not a server is running
/// </summary>
public bool IsServer => ConnectionManager.LocalClient.IsServer;
/// <summary>
/// Gets whether or not the current server (local or remote) is a host - i.e., also a client
/// </summary>
public bool ServerIsHost => ConnectionManager.ConnectedClientIds.Contains(ServerClientId);
/// <summary>
/// Gets Whether or not a client is running
/// </summary>
public bool IsClient => ConnectionManager.LocalClient.IsClient;
/// <summary>
/// Gets if we are running as host
/// </summary>
public bool IsHost => ConnectionManager.LocalClient.IsHost;
/// <summary>
/// When disconnected from the server, the server may send a reason. If a reason was sent, this property will
/// tell client code what the reason was. It should be queried after the OnClientDisconnectCallback is called
/// </summary>
public string DisconnectReason => ConnectionManager.DisconnectReason;
/// <summary>
/// Is true when a server or host is listening for connections.
/// Is true when a client is connecting or connected to a network session.
/// Is false when not listening, connecting, or connected.
/// </summary>
public bool IsListening
{
get => ConnectionManager.IsListening;
internal set => ConnectionManager.IsListening = value;
}
/// <summary>
/// When true, the client is connected, approved, and synchronized with
/// the server.
/// <see cref="NetworkClient.IsConnected"/> <br />
/// <see cref="NetworkClient.IsApproved"/> <br />
/// </summary>
public bool IsConnectedClient
{
get => ConnectionManager.LocalClient.IsConnected;
internal set => ConnectionManager.LocalClient.IsConnected = value;
}
/// <summary>
/// Is true when the client has been approved.
/// </summary>
/// <remarks>
/// This only reflects the client's approved status and does not mean the client
/// has finished the connection and synchronization process. The server-host will
/// always be approved upon being starting the <see cref="NetworkManager"/>
/// <see cref="NetworkClient.IsConnectedClient"/>
/// </remarks>
public bool IsApproved
{
get => ConnectionManager.LocalClient.IsApproved;
internal set => ConnectionManager.LocalClient.IsApproved = value;
}
/// <summary>
/// The callback to invoke if the <see cref="NetworkTransport"/> fails.
/// </summary>
/// <remarks>
/// A failure of the transport is always followed by the <see cref="NetworkManager"/> shutting down. Recovering
/// from a transport failure would normally entail reconfiguring the transport (e.g. re-authenticating, or
/// recreating a new service allocation depending on the transport) and restarting the client/server/host.
/// </remarks>
public event Action OnTransportFailure
{
add => ConnectionManager.OnTransportFailure += value;
remove => ConnectionManager.OnTransportFailure -= value;
}
/// <summary>
/// Delegate for handling network state reanticipation events.
/// </summary>
/// <param name="lastRoundTripTime">The most recent round-trip time measurement in seconds between client and server.</param>
public delegate void ReanticipateDelegate(double lastRoundTripTime);
/// <summary>
/// This callback is called after all individual OnReanticipate calls on AnticipatedNetworkVariable
/// and AnticipatedNetworkTransform values have been invoked. The first parameter is a hash set of
/// all the variables that have been changed on this frame (you can detect a particular variable by
/// checking if the set contains it), while the second parameter is a set of all anticipated network
/// transforms that have been changed. Both are passed as their base class type.
///
/// The third parameter is the local time corresponding to the current authoritative server state
/// (i.e., to determine the amount of time that needs to be re-simulated, you will use
/// NetworkManager.LocalTime.Time - authorityTime).
/// </summary>
public event ReanticipateDelegate OnReanticipate
{
add => AnticipationSystem.OnReanticipate += value;
remove => AnticipationSystem.OnReanticipate -= value;
}
/// <summary>
/// The callback to invoke during connection approval. Allows client code to decide whether or not to allow incoming client connection
/// </summary>
public Action<ConnectionApprovalRequest, ConnectionApprovalResponse> ConnectionApprovalCallback
{
get => ConnectionManager.ConnectionApprovalCallback;
set
{
if (value != null && value.GetInvocationList().Length > 1)
{
throw new InvalidOperationException($"Only one {nameof(ConnectionApprovalCallback)} can be registered at a time.");
}
ConnectionManager.ConnectionApprovalCallback = value;
}
}
/// <summary>
/// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects.
///
/// It is recommended to use OnConnectionEvent instead, as this will eventually be deprecated
/// </summary>
public event Action<ulong> OnClientConnectedCallback
{
add => ConnectionManager.OnClientConnectedCallback += value;
remove => ConnectionManager.OnClientConnectedCallback -= value;
}
/// <summary>
/// The callback to invoke when a client disconnects. This callback is only ran on the server and on the local client that disconnects.
///
/// It is recommended to use OnConnectionEvent instead, as this will eventually be deprecated
/// </summary>
public event Action<ulong> OnClientDisconnectCallback
{
add => ConnectionManager.OnClientDisconnectCallback += value;
remove => ConnectionManager.OnClientDisconnectCallback -= value;
}
/// <summary>
/// The callback to invoke on any connection event. See <see cref="ConnectionEvent"/> and <see cref="ConnectionEventData"/>
/// for more info.
/// </summary>
public event Action<NetworkManager, ConnectionEventData> OnConnectionEvent
{
add => ConnectionManager.OnConnectionEvent += value;
remove => ConnectionManager.OnConnectionEvent -= value;
}
/// <summary>
/// The current host name we are connected to, used to validate certificate
/// </summary>
public string ConnectedHostname => string.Empty;
/// <summary>
/// Connection Approval Response
/// </summary>
public class ConnectionApprovalResponse
{
/// <summary>
/// Whether or not the client was approved
/// </summary>
public bool Approved;
/// <summary>
/// If true, a player object will be created. Otherwise the client will have no object.
/// </summary>
public bool CreatePlayerObject;
/// <summary>
/// The prefabHash to use for the client. If createPlayerObject is false, this is ignored. If playerPrefabHash is null, the default player prefab is used.
/// </summary>
public uint? PlayerPrefabHash;
/// <summary>
/// The position to spawn the client at. If null, the prefab position is used.
/// </summary>
public Vector3? Position;
/// <summary>
/// The rotation to spawn the client with. If null, the prefab position is used.
/// </summary>
public Quaternion? Rotation;
/// <summary>
/// If the Approval decision cannot be made immediately, the client code can set Pending to true, keep a reference to the ConnectionApprovalResponse object and write to it later. Client code must exercise care to setting all the members to the value it wants before marking Pending to false, to indicate completion. If the field is set as Pending = true, we'll monitor the object until it gets set to not pending anymore and use the parameters then.
/// </summary>
public bool Pending;
/// <summary>
/// Optional reason. If Approved is false, this reason will be sent to the client so they know why they
/// were not approved.
/// </summary>
public string Reason;
}
/// <summary>
/// Connection Approval Request
/// </summary>
public struct ConnectionApprovalRequest
{
/// <summary>
/// The connection data payload
/// </summary>
public byte[] Payload;
/// <summary>
/// The Network Id of the client we are about to handle
/// </summary>
public ulong ClientNetworkId;
}
/// <summary>
/// Can be used to determine if the <see cref="NetworkManager"/> is currently shutting itself down
/// </summary>
public bool ShutdownInProgress => m_ShuttingDown;
private bool m_ShuttingDown;
/// <summary>
/// The current netcode project configuration
/// </summary>
[HideInInspector]
public NetworkConfig NetworkConfig;
/// <summary>
/// The local <see cref="NetworkTime"/>
/// </summary>
public NetworkTime LocalTime => NetworkTickSystem?.LocalTime ?? default;
/// <summary>
/// The <see cref="NetworkTime"/> on the server
/// </summary>
public NetworkTime ServerTime => NetworkTickSystem?.ServerTime ?? default;
/// <summary>
/// Gets or sets if the application should be set to run in background
/// </summary>
[HideInInspector]
public bool RunInBackground = true;
/// <summary>
/// The log level to use
/// </summary>
[HideInInspector]
public LogLevel LogLevel = LogLevel.Normal;
/// <summary>
/// The singleton instance of the NetworkManager
/// </summary>
public static NetworkManager Singleton { get; private set; }
internal static event Action OnSingletonReady;
/// <summary>
/// This callback is invoked when the local server is started and listening for incoming connections.
/// </summary>
public event Action OnServerStarted = null;
/// <summary>
/// The callback to invoke once the local client is ready.
/// </summary>
public event Action OnClientStarted = null;
/// <summary>
/// Subscribe to this event to get notifications before a <see cref="NetworkManager"/> instance is being destroyed.
/// This is useful if you want to use the state of anything the NetworkManager cleans up during its shutdown.
/// </summary>
public event Action OnPreShutdown = null;
/// <summary>
/// This callback is invoked once the local server is stopped.
/// </summary>
/// <remarks>
/// The bool parameter will be set to true when stopping a host instance and false when stopping a server instance.
/// The first parameter of this event will be set to <see cref="true"/> when stopping a host instance and <see cref="false"/> when stopping a server instance.
/// </remarks>
public event Action<bool> OnServerStopped = null;
/// <summary>
/// The callback to invoke once the local client stops.
/// </summary>
/// <remarks>
/// The bool parameter will be set to true when stopping a host client and false when stopping a standard client instance.
/// The first parameter of this event will be set to <see cref="true"/> when stopping the host client and <see cref="false"/> when stopping a standard client instance.
/// </remarks>
public event Action<bool> OnClientStopped = null;
/// <summary>
/// The <see cref="NetworkPrefabHandler"/> instance created after starting the <see cref="NetworkManager"/>
/// </summary>
public NetworkPrefabHandler PrefabHandler
{
get
{
if (m_PrefabHandler == null)
{
m_PrefabHandler = new NetworkPrefabHandler();
m_PrefabHandler.Initialize(this);
}
return m_PrefabHandler;
}
}
private NetworkPrefabHandler m_PrefabHandler;
/// <summary>
/// Gets the SpawnManager for this NetworkManager
/// </summary>
public NetworkSpawnManager SpawnManager { get; private set; }
internal IDeferredNetworkMessageManager DeferredMessageManager { get; private set; }
// This erroneously tries to simplify these method references but the docs do not pick it up correctly
// because they try to resolve it on the field rather than the class of the same name.
#pragma warning disable IDE0001
/// <summary>
/// Provides access to the various <see cref="SendTo"/> targets at runtime, as well as
/// runtime-bound targets like <see cref="Unity.Netcode.RpcTarget.Single"/>,
/// <see cref="Unity.Netcode.RpcTarget.Group(NativeArray{ulong})"/>,
/// <see cref="Unity.Netcode.RpcTarget.Group(NativeList{ulong})"/>,
/// <see cref="Unity.Netcode.RpcTarget.Group(ulong[])"/>,
/// <see cref="Unity.Netcode.RpcTarget.Group{T}(T)"/>, <see cref="Unity.Netcode.RpcTarget.Not(ulong)"/>,
/// <see cref="Unity.Netcode.RpcTarget.Not(NativeArray{ulong})"/>,
/// <see cref="Unity.Netcode.RpcTarget.Not(NativeList{ulong})"/>,
/// <see cref="Unity.Netcode.RpcTarget.Not(ulong[])"/>, and
/// <see cref="Unity.Netcode.RpcTarget.Not{T}(T)"/>
/// </summary>
#pragma warning restore IDE0001
public RpcTarget RpcTarget;
/// <summary>
/// Gets the CustomMessagingManager for this NetworkManager
/// </summary>
public CustomMessagingManager CustomMessagingManager { get; private set; }
/// <summary>
/// The <see cref="NetworkSceneManager"/> instance created after starting the <see cref="NetworkManager"/>
/// </summary>
public NetworkSceneManager SceneManager { get; private set; }
internal NetworkBehaviourUpdater BehaviourUpdater { get; set; }
/// <summary>
/// Accessor property for the <see cref="NetworkTimeSystem"/> of the NetworkManager.
/// Prefer the use of the LocalTime and ServerTime properties
/// </summary>
public NetworkTimeSystem NetworkTimeSystem { get; private set; }
/// <summary>
/// Accessor property for the <see cref="NetworkTickSystem"/> of the NetworkManager.
/// </summary>
public NetworkTickSystem NetworkTickSystem { get; private set; }
internal AnticipationSystem AnticipationSystem { get; private set; }
/// <summary>
/// Used for time mocking in tests
/// </summary>
internal IRealTimeProvider RealTimeProvider { get; private set; }
internal INetworkMetrics NetworkMetrics => MetricsManager.NetworkMetrics;
internal NetworkMetricsManager MetricsManager = new NetworkMetricsManager();
internal NetworkConnectionManager ConnectionManager = new NetworkConnectionManager();
internal NetworkMessageManager MessageManager = null;
internal struct Override<T>
{
private T m_Value;
public bool Overidden { get; private set; }
internal T Value
{
get { return Overidden ? m_Value : default(T); }
set { Overidden = true; m_Value = value; }
}
};
internal Override<ushort> PortOverride;
#if UNITY_EDITOR
internal static INetworkManagerHelper NetworkManagerHelper;
/// <summary>
/// Interface for NetworkManagerHelper
/// </summary>
internal interface INetworkManagerHelper
{
bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool ignoreNetworkManagerCache = false, bool editorTest = false);
void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false);
}
internal delegate void ResetNetworkManagerDelegate(NetworkManager manager);
internal static ResetNetworkManagerDelegate OnNetworkManagerReset;
private void Reset()
{
OnNetworkManagerReset?.Invoke(this);
}
internal void OnValidate()
{
if (NetworkConfig == null)
{
return; // May occur when the component is added
}
if (GetComponentInChildren<NetworkObject>() != null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{nameof(NetworkManager)} cannot be a {nameof(NetworkObject)}.");
}
}
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
// If the scene is not dirty or the asset database is currently updating then we can skip updating the NetworkPrefab information
if (!activeScene.isDirty || EditorApplication.isUpdating)
{
return;
}
// During OnValidate we will always clear out NetworkPrefabOverrideLinks and rebuild it
NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.Clear();
var prefabs = NetworkConfig.Prefabs.Prefabs;
// Check network prefabs and assign to dictionary for quick look up
for (int i = 0; i < prefabs.Count; i++)
{
var networkPrefab = prefabs[i];
var networkPrefabGo = networkPrefab?.Prefab;
if (networkPrefabGo == null)
{
continue;
}
var networkObject = networkPrefabGo.GetComponent<NetworkObject>();
if (networkObject == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogError($"Cannot register {NetworkPrefabHandler.PrefabDebugHelper(networkPrefab)}, it does not have a {nameof(NetworkObject)} component at its root");
}
continue;
}
{
var childNetworkObjects = new List<NetworkObject>();
networkPrefabGo.GetComponentsInChildren(true, childNetworkObjects);
if (childNetworkObjects.Count > 1) // total count = 1 root NetworkObject + n child NetworkObjects
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{NetworkPrefabHandler.PrefabDebugHelper(networkPrefab)} has child {nameof(NetworkObject)}(s) but they will not be spawned across the network (unsupported {nameof(NetworkPrefab)} setup)");
}
}
}
}
}
private void ModeChanged(PlayModeStateChange change)
{
if (IsListening && change == PlayModeStateChange.ExitingPlayMode)
{
// Make sure we are not holding onto anything in case domain reload is disabled
ShutdownInternal();
}
}
#endif
/// <summary>
/// Determines if the NetworkManager's GameObject is parented under another GameObject and
/// notifies the user that this is not allowed for the NetworkManager.
/// </summary>
internal bool NetworkManagerCheckForParent(bool ignoreNetworkManagerCache = false)
{
#if UNITY_EDITOR
var isParented = NetworkManagerHelper.NotifyUserOfNestedNetworkManager(this, ignoreNetworkManagerCache);
#else
var isParented = transform.root != transform;
if (isParented)
{
throw new Exception(GenerateNestedNetworkManagerMessage(transform));
}
#endif
return isParented;
}
internal static string GenerateNestedNetworkManagerMessage(Transform transform)
{
return $"{transform.name} is nested under {transform.root.name}. NetworkManager cannot be nested.\n";
}
/// <summary>
/// Handle runtime detection for parenting the NetworkManager's GameObject under another GameObject
/// </summary>
private void OnTransformParentChanged()
{
NetworkManagerCheckForParent();
}
/// <summary>
/// Set this NetworkManager instance as the static NetworkManager singleton
/// </summary>
public void SetSingleton()
{
Singleton = this;
OnSingletonReady?.Invoke();
}
private void Awake()
{
NetworkConfig?.InitializePrefabs();
UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
#if UNITY_EDITOR
EditorApplication.playModeStateChanged += ModeChanged;
#endif
// Notify we have instantiated a new instance of NetworkManager.
OnInstantiated?.Invoke(this);
}
private void OnEnable()
{
if (RunInBackground)
{
Application.runInBackground = true;
}
if (Singleton == null)
{
SetSingleton();
}
if (!NetworkManagerCheckForParent())
{
DontDestroyOnLoad(gameObject);
}
}
/// <summary>
/// <see cref="NetworkPrefabHandler.GetNetworkPrefabOverride(GameObject)"/>
/// </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) => PrefabHandler.GetNetworkPrefabOverride(gameObject);
/// <summary>
/// <see cref="NetworkPrefabHandler.AddNetworkPrefab(GameObject)"/>
/// </summary>
/// <param name="prefab">The GameObject prefab to be registered. Must not be null and must have a NetworkObject component.</param>
/// <exception cref="Exception">Thrown when the prefab is null or missing required components.</exception>
public void AddNetworkPrefab(GameObject prefab) => PrefabHandler.AddNetworkPrefab(prefab);
/// <summary>
/// <see cref="NetworkPrefabHandler.RemoveNetworkPrefab(GameObject)"/>
/// </summary>
/// <param name="prefab">The GameObject prefab to be unregistered. Must not be null and must have been previously registered.</param>
public void RemoveNetworkPrefab(GameObject prefab) => PrefabHandler.RemoveNetworkPrefab(prefab);
/// <summary>
/// Sets the maximum size of a single non-fragmented message (or message batch) passed through the transport.
/// This should represent the transport's default MTU size, minus any transport-level overhead.
/// This value will be used for any remote endpoints that haven't had per-endpoint MTUs set.
/// This value is also used as the size of the temporary buffer used when serializing
/// a single message (to avoid serializing multiple times when sending to multiple endpoints),
/// and thus should be large enough to ensure it can hold each message type.
/// This value defaults to 1296.
/// </summary>
public int MaximumTransmissionUnitSize
{
set => MessageManager.NonFragmentedMessageMaxSize = value & ~7; // Round down to nearest word aligned size
get => MessageManager.NonFragmentedMessageMaxSize;
}
/// <summary>
/// Set the maximum transmission unit for a specific peer.
/// This determines the maximum size of a message batch that can be sent to that client.
/// If not set for any given client, <see cref="MaximumTransmissionUnitSize"/> will be used instead.
/// </summary>
/// <param name="clientId">The unique identifier of the client to set the MTU for.</param>
/// <param name="size">The maximum transmission unit size in bytes for this specific client.</param>
public void SetPeerMTU(ulong clientId, int size)
{
MessageManager.PeerMTUSizes[clientId] = size;
}
/// <summary>
/// Queries the current MTU size for a client.
/// If no MTU has been set for that client, will return <see cref="MaximumTransmissionUnitSize"/>
/// </summary>
/// <param name="clientId">The unique identifier of the client to query.</param>
/// <returns>The MTU size in bytes for the specified client. If no MTU has been set for that client, returns <see cref="MaximumTransmissionUnitSize"/>.</returns>
public int GetPeerMTU(ulong clientId)
{
if (MessageManager.PeerMTUSizes.TryGetValue(clientId, out var ret))
{
return ret;
}
return MessageManager.NonFragmentedMessageMaxSize;
}
/// <summary>
/// Sets the maximum size of a message (or message batch) passed through the transport with the ReliableFragmented delivery.
/// Warning: setting this value too low may result in the SDK becoming non-functional with projects that have a large number of NetworkBehaviours or NetworkVariables, as the SDK relies on the transport's ability to fragment some messages when they grow beyond the MTU size.
/// </summary>
public int MaximumFragmentedMessageSize
{
set => MessageManager.FragmentedMessageMaxSize = value;
get => MessageManager.FragmentedMessageMaxSize;
}
internal void Initialize(bool server)
{
// Make sure the ServerShutdownState is reset when initializing
if (server)
{
ServerShutdownState = ServerShutdownStates.None;
}
// Don't allow the user to start a network session if the NetworkManager is
// still parented under another GameObject
if (NetworkManagerCheckForParent(true))
{
return;
}
ParseCommandLineOptions();
if (NetworkConfig.NetworkTransport == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogError("No transport has been selected!");
}
return;
}
// Logging initializes first for any logging during systems initialization
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo(nameof(Initialize));
}
this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PostScriptLateUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PostLateUpdate);
// ComponentFactory needs to set its defaults next
ComponentFactory.SetDefaults();
// UnityTransport dependencies are then initialized
RealTimeProvider = ComponentFactory.Create<IRealTimeProvider>(this);
MetricsManager.Initialize(this);
{
MessageManager = new NetworkMessageManager(new DefaultMessageSender(this), this);
MessageManager.Hook(new NetworkManagerHooks(this));
#if DEVELOPMENT_BUILD || UNITY_EDITOR
MessageManager.Hook(new ProfilingHooks());
#endif
#if MULTIPLAYER_TOOLS
MessageManager.Hook(new MetricHooks(this));
#endif
// Assures there is a server message queue available
MessageManager.ClientConnected(ServerClientId);
}
// Now the connection manager can initialize (which initializes transport)
ConnectionManager.Initialize(this);
// The remaining systems can then be initialized
NetworkTimeSystem = server ? NetworkTimeSystem.ServerTimeSystem() : new NetworkTimeSystem(1.0 / NetworkConfig.TickRate);
NetworkTickSystem = NetworkTimeSystem.Initialize(this);
AnticipationSystem = new AnticipationSystem(this);
// Create spawn manager instance
SpawnManager = new NetworkSpawnManager(this);
DeferredMessageManager = ComponentFactory.Create<IDeferredNetworkMessageManager>(this);
RpcTarget = new RpcTarget(this);
CustomMessagingManager = new CustomMessagingManager(this);
SceneManager = new NetworkSceneManager(this);
BehaviourUpdater = new NetworkBehaviourUpdater();
BehaviourUpdater.Initialize(this);
NetworkConfig.InitializePrefabs();
PrefabHandler.RegisterPlayerPrefab();
}
private enum StartType
{
Server,
Host,
Client
}
/// <summary>
/// Determines if NetworkManager can start based on the current
/// NetworkManager instance state(s)
/// </summary>
private bool CanStart(StartType type)
{
if (IsListening)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning("Cannot start " + type + " while an instance is already running");
}
return false;
}
// Only if it is starting as a server or host do we need to check this
// Clients don't invoke the ConnectionApprovalCallback
if (NetworkConfig.ConnectionApproval && type != StartType.Client)
{
if (ConnectionApprovalCallback == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning("No ConnectionApproval callback defined. Connection approval will timeout");
}
}
}
if (ConnectionApprovalCallback != null)
{
if (!NetworkConfig.ConnectionApproval)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning("A ConnectionApproval callback is defined but ConnectionApproval is disabled. In order to use ConnectionApproval it has to be explicitly enabled ");
}
}
}
return true;
}
/// <summary>
/// Starts a server
/// </summary>
/// <returns>(<see cref="true"/>/<see cref="false"/>) returns true if <see cref="NetworkManager"/> started in server mode successfully.</returns>
public bool StartServer()
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo(nameof(StartServer));
}
if (!CanStart(StartType.Server))
{
return false;
}
ConnectionManager.LocalClient.SetRole(true, false, this);
ConnectionManager.LocalClient.ClientId = ServerClientId;
Initialize(true);
try
{
IsListening = NetworkConfig.NetworkTransport.StartServer();
// If we failed to start then shutdown and notify user that the transport failed to start