Mirror Networking
NetworkServer.cs
1using System;
2using System.Collections.Generic;
3using System.Linq;
5using UnityEngine;
6
7namespace Mirror
8{
10 public static class NetworkServer
11 {
12 static bool initialized;
13 public static int maxConnections;
14
16 public static NetworkConnectionToClient localConnection { get; private set; }
17
19 public static bool localClientActive => localConnection != null;
20
22 public static Dictionary<int, NetworkConnectionToClient> connections =
23 new Dictionary<int, NetworkConnectionToClient>();
24
26 internal static Dictionary<ushort, NetworkMessageDelegate> handlers =
27 new Dictionary<ushort, NetworkMessageDelegate>();
28
30 // server sees ALL spawned ones.
31 public static readonly Dictionary<uint, NetworkIdentity> spawned =
32 new Dictionary<uint, NetworkIdentity>();
33
35 // see also: https://github.com/vis2k/Mirror/pull/2595
36 public static bool dontListen;
37
39 public static bool active { get; internal set; }
40
41 // scene loading
42 public static bool isLoadingScene;
43
44 // interest management component (optional)
45 // by default, everyone observes everyone
46 public static InterestManagement aoi;
47
48 // OnConnected / OnDisconnected used to be NetworkMessages that were
49 // invoked. this introduced a bug where external clients could send
50 // Connected/Disconnected messages over the network causing undefined
51 // behaviour.
52 // => public so that custom NetworkManagers can hook into it
53 public static Action<NetworkConnectionToClient> OnConnectedEvent;
54 public static Action<NetworkConnectionToClient> OnDisconnectedEvent;
55 public static Action<NetworkConnectionToClient, TransportError, string> OnErrorEvent;
56
57 // initialization / shutdown ///////////////////////////////////////////
58 static void Initialize()
59 {
60 if (initialized)
61 return;
62
63 // Debug.Log($"NetworkServer Created version {Version.Current}");
64
65 //Make sure connections are cleared in case any old connections references exist from previous sessions
66 connections.Clear();
67
68 // reset Interest Management so that rebuild intervals
69 // start at 0 when starting again.
70 if (aoi != null) aoi.Reset();
71
72 // reset NetworkTime
73 NetworkTime.ResetStatics();
74
75 Debug.Assert(Transport.activeTransport != null, "There was no active transport when calling NetworkServer.Listen, If you are calling Listen manually then make sure to set 'Transport.activeTransport' first");
76 AddTransportHandlers();
77
78 initialized = true;
79 }
80
81 static void AddTransportHandlers()
82 {
83 // += so that other systems can also hook into it (i.e. statistics)
84 Transport.activeTransport.OnServerConnected += OnTransportConnected;
85 Transport.activeTransport.OnServerDataReceived += OnTransportData;
86 Transport.activeTransport.OnServerDisconnected += OnTransportDisconnected;
87 Transport.activeTransport.OnServerError += OnTransportError;
88 }
89
90 static void RemoveTransportHandlers()
91 {
92 // -= so that other systems can also hook into it (i.e. statistics)
93 Transport.activeTransport.OnServerConnected -= OnTransportConnected;
94 Transport.activeTransport.OnServerDataReceived -= OnTransportData;
95 Transport.activeTransport.OnServerDisconnected -= OnTransportDisconnected;
96 Transport.activeTransport.OnServerError -= OnTransportError;
97 }
98
99 // calls OnStartClient for all SERVER objects in host mode once.
100 // client doesn't get spawn messages for those, so need to call manually.
101 public static void ActivateHostScene()
102 {
103 foreach (NetworkIdentity identity in spawned.Values)
104 {
105 if (!identity.isClient)
106 {
107 // Debug.Log($"ActivateHostScene {identity.netId} {identity}");
108 identity.OnStartClient();
109 }
110 }
111 }
112
113 internal static void RegisterMessageHandlers()
114 {
115 RegisterHandler<ReadyMessage>(OnClientReadyMessage);
116 RegisterHandler<CommandMessage>(OnCommandMessage);
117 RegisterHandler<NetworkPingMessage>(NetworkTime.OnServerPing, false);
118 }
119
121 public static void Listen(int maxConns)
122 {
123 Initialize();
124 maxConnections = maxConns;
125
126 // only start server if we want to listen
127 if (!dontListen)
128 {
130 //Debug.Log("Server started listening");
131 }
132
133 active = true;
134 RegisterMessageHandlers();
135 }
136
137 // Note: NetworkClient.DestroyAllClientObjects does the same on client.
138 static void CleanupSpawned()
139 {
140 // iterate a COPY of spawned.
141 // DestroyObject removes them from the original collection.
142 // removing while iterating is not allowed.
143 foreach (NetworkIdentity identity in spawned.Values.ToList())
144 {
145 if (identity != null)
146 {
147 // scene object
148 if (identity.sceneId != 0)
149 {
150 // spawned scene objects are unspawned and reset.
151 // afterwards we disable them again.
152 // (they always stay in the scene, we don't destroy them)
153 DestroyObject(identity, DestroyMode.Reset);
154 identity.gameObject.SetActive(false);
155 }
156 // spawned prefabs
157 else
158 {
159 // spawned prefabs are unspawned and destroyed.
160 DestroyObject(identity, DestroyMode.Destroy);
161 }
162 }
163 }
164
165 spawned.Clear();
166 }
167
169 // RuntimeInitializeOnLoadMethod -> fast playmode without domain reload
170 [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
171 public static void Shutdown()
172 {
173 if (initialized)
174 {
176
177 // stop the server.
178 // we do NOT call Transport.Shutdown, because someone only
179 // called NetworkServer.Shutdown. we can't assume that the
180 // client is supposed to be shut down too!
181 //
182 // NOTE: stop no matter what, even if 'dontListen':
183 // someone might enabled dontListen at runtime.
184 // but we still need to stop the server.
185 // fixes https://github.com/vis2k/Mirror/issues/2536
187
188 // transport handlers are hooked into when initializing.
189 // so only remove them when shutting down.
190 RemoveTransportHandlers();
191
192 initialized = false;
193 }
194
195 // Reset all statics here....
196 dontListen = false;
197 active = false;
198 isLoadingScene = false;
199
200 localConnection = null;
201
202 connections.Clear();
203 connectionsCopy.Clear();
204 handlers.Clear();
205 newObservers.Clear();
206
207 // this calls spawned.Clear()
208 CleanupSpawned();
209
210 // sets nextNetworkId to 1
211 // sets clientAuthorityCallback to null
212 // sets previousLocalPlayer to null
213 NetworkIdentity.ResetStatics();
214
215 // clear events. someone might have hooked into them before, but
216 // we don't want to use those hooks after Shutdown anymore.
217 OnConnectedEvent = null;
218 OnDisconnectedEvent = null;
219 OnErrorEvent = null;
220
221 if (aoi != null) aoi.Reset();
222 }
223
224 // connections /////////////////////////////////////////////////////////
227 {
228 if (!connections.ContainsKey(conn.connectionId))
229 {
230 // connection cannot be null here or conn.connectionId
231 // would throw NRE
232 connections[conn.connectionId] = conn;
233 return true;
234 }
235 // already a connection with this id
236 return false;
237 }
238
240 public static bool RemoveConnection(int connectionId) =>
241 connections.Remove(connectionId);
242
243 // called by LocalClient to add itself. don't call directly.
244 // TODO consider internal setter instead?
245 internal static void SetLocalConnection(LocalConnectionToClient conn)
246 {
247 if (localConnection != null)
248 {
249 Debug.LogError("Local Connection already exists");
250 return;
251 }
252
253 localConnection = conn;
254 }
255
256 // removes local connection to client
257 internal static void RemoveLocalConnection()
258 {
259 if (localConnection != null)
260 {
262 localConnection = null;
263 }
265 }
266
268 public static bool HasExternalConnections()
269 {
270 // any connections?
271 if (connections.Count > 0)
272 {
273 // only host connection?
274 if (connections.Count == 1 && localConnection != null)
275 return false;
276
277 // otherwise we have real external connections
278 return true;
279 }
280 return false;
281 }
282
283 // send ////////////////////////////////////////////////////////////////
285 public static void SendToAll<T>(T message, int channelId = Channels.Reliable, bool sendToReadyOnly = false)
286 where T : struct, NetworkMessage
287 {
288 if (!active)
289 {
290 Debug.LogWarning("Can not send using NetworkServer.SendToAll<T>(T msg) because NetworkServer is not active");
291 return;
292 }
293
294 // Debug.Log($"Server.SendToAll {typeof(T)}");
295 using (NetworkWriterPooled writer = NetworkWriterPool.Get())
296 {
297 // pack message only once
298 MessagePacking.Pack(message, writer);
299 ArraySegment<byte> segment = writer.ToArraySegment();
300
301 // filter and then send to all internet connections at once
302 // -> makes code more complicated, but is HIGHLY worth it to
303 // avoid allocations, allow for multicast, etc.
304 int count = 0;
305 foreach (NetworkConnectionToClient conn in connections.Values)
306 {
307 if (sendToReadyOnly && !conn.isReady)
308 continue;
309
310 count++;
311 conn.Send(segment, channelId);
312 }
313
314 NetworkDiagnostics.OnSend(message, channelId, segment.Count, count);
315 }
316 }
317
319 // TODO put rpcs into NetworkServer.Update WorldState packet, then finally remove SendToReady!
320 public static void SendToReady<T>(T message, int channelId = Channels.Reliable)
321 where T : struct, NetworkMessage
322 {
323 if (!active)
324 {
325 Debug.LogWarning("Can not send using NetworkServer.SendToReady<T>(T msg) because NetworkServer is not active");
326 return;
327 }
328
329 SendToAll(message, channelId, true);
330 }
331
332 // this is like SendToReadyObservers - but it doesn't check the ready flag on the connection.
333 // this is used for ObjectDestroy messages.
334 static void SendToObservers<T>(NetworkIdentity identity, T message, int channelId = Channels.Reliable)
335 where T : struct, NetworkMessage
336 {
337 // Debug.Log($"Server.SendToObservers {typeof(T)}");
338 if (identity == null || identity.observers == null || identity.observers.Count == 0)
339 return;
340
341 using (NetworkWriterPooled writer = NetworkWriterPool.Get())
342 {
343 // pack message into byte[] once
344 MessagePacking.Pack(message, writer);
345 ArraySegment<byte> segment = writer.ToArraySegment();
346
347 foreach (NetworkConnectionToClient conn in identity.observers.Values)
348 {
349 conn.Send(segment, channelId);
350 }
351
352 NetworkDiagnostics.OnSend(message, channelId, segment.Count, identity.observers.Count);
353 }
354 }
355
357 // TODO put rpcs into NetworkServer.Update WorldState packet, then finally remove SendToReady!
358 public static void SendToReadyObservers<T>(NetworkIdentity identity, T message, bool includeOwner = true, int channelId = Channels.Reliable)
359 where T : struct, NetworkMessage
360 {
361 // Debug.Log($"Server.SendToReady {typeof(T)}");
362 if (identity == null || identity.observers == null || identity.observers.Count == 0)
363 return;
364
365 using (NetworkWriterPooled writer = NetworkWriterPool.Get())
366 {
367 // pack message only once
368 MessagePacking.Pack(message, writer);
369 ArraySegment<byte> segment = writer.ToArraySegment();
370
371 int count = 0;
372 foreach (NetworkConnection conn in identity.observers.Values)
373 {
374 bool isOwner = conn == identity.connectionToClient;
375 if ((!isOwner || includeOwner) && conn.isReady)
376 {
377 count++;
378 conn.Send(segment, channelId);
379 }
380 }
381
382 NetworkDiagnostics.OnSend(message, channelId, segment.Count, count);
383 }
384 }
385
387 // TODO put rpcs into NetworkServer.Update WorldState packet, then finally remove SendToReady!
388 public static void SendToReadyObservers<T>(NetworkIdentity identity, T message, int channelId)
389 where T : struct, NetworkMessage
390 {
391 SendToReadyObservers(identity, message, true, channelId);
392 }
393
394 // transport events ////////////////////////////////////////////////////
395 // called by transport
396 static void OnTransportConnected(int connectionId)
397 {
398 // Debug.Log($"Server accepted client:{connectionId}");
399
400 // connectionId needs to be != 0 because 0 is reserved for local player
401 // note that some transports like kcp generate connectionId by
402 // hashing which can be < 0 as well, so we need to allow < 0!
403 if (connectionId == 0)
404 {
405 Debug.LogError($"Server.HandleConnect: invalid connectionId: {connectionId} . Needs to be != 0, because 0 is reserved for local player.");
407 return;
408 }
409
410 // connectionId not in use yet?
411 if (connections.ContainsKey(connectionId))
412 {
414 // Debug.Log($"Server connectionId {connectionId} already in use...kicked client");
415 return;
416 }
417
418 // are more connections allowed? if not, kick
419 // (it's easier to handle this in Mirror, so Transports can have
420 // less code and third party transport might not do that anyway)
421 // (this way we could also send a custom 'tooFull' message later,
422 // Transport can't do that)
423 if (connections.Count < maxConnections)
424 {
425 // add connection
427 OnConnected(conn);
428 }
429 else
430 {
431 // kick
433 // Debug.Log($"Server full, kicked client {connectionId}");
434 }
435 }
436
437 internal static void OnConnected(NetworkConnectionToClient conn)
438 {
439 // Debug.Log($"Server accepted client:{conn}");
440
441 // add connection and invoke connected event
442 AddConnection(conn);
443 OnConnectedEvent?.Invoke(conn);
444 }
445
446 static bool UnpackAndInvoke(NetworkConnectionToClient connection, NetworkReader reader, int channelId)
447 {
448 if (MessagePacking.Unpack(reader, out ushort msgType))
449 {
450 // try to invoke the handler for that message
451 if (handlers.TryGetValue(msgType, out NetworkMessageDelegate handler))
452 {
453 handler.Invoke(connection, reader, channelId);
454 connection.lastMessageTime = Time.time;
455 return true;
456 }
457 else
458 {
459 // message in a batch are NOT length prefixed to save bandwidth.
460 // every message needs to be handled and read until the end.
461 // otherwise it would overlap into the next message.
462 // => need to warn and disconnect to avoid undefined behaviour.
463 // => WARNING, not error. can happen if attacker sends random data.
464 Debug.LogWarning($"Unknown message id: {msgType} for connection: {connection}. This can happen if no handler was registered for this message.");
465 // simply return false. caller is responsible for disconnecting.
466 //connection.Disconnect();
467 return false;
468 }
469 }
470 else
471 {
472 // => WARNING, not error. can happen if attacker sends random data.
473 Debug.LogWarning($"Invalid message header for connection: {connection}.");
474 // simply return false. caller is responsible for disconnecting.
475 //connection.Disconnect();
476 return false;
477 }
478 }
479
480 // called by transport
481 internal static void OnTransportData(int connectionId, ArraySegment<byte> data, int channelId)
482 {
483 if (connections.TryGetValue(connectionId, out NetworkConnectionToClient connection))
484 {
485 // client might batch multiple messages into one packet.
486 // feed it to the Unbatcher.
487 // NOTE: we don't need to associate a channelId because we
488 // always process all messages in the batch.
489 if (!connection.unbatcher.AddBatch(data))
490 {
491 Debug.LogWarning($"NetworkServer: received Message was too short (messages should start with message id)");
492 connection.Disconnect();
493 return;
494 }
495
496 // process all messages in the batch.
497 // only while NOT loading a scene.
498 // if we get a scene change message, then we need to stop
499 // processing. otherwise we might apply them to the old scene.
500 // => fixes https://github.com/vis2k/Mirror/issues/2651
501 //
502 // NOTE: if scene starts loading, then the rest of the batch
503 // would only be processed when OnTransportData is called
504 // the next time.
505 // => consider moving processing to NetworkEarlyUpdate.
506 while (!isLoadingScene &&
507 connection.unbatcher.GetNextMessage(out NetworkReader reader, out double remoteTimestamp))
508 {
509 // enough to read at least header size?
510 if (reader.Remaining >= MessagePacking.HeaderSize)
511 {
512 // make remoteTimeStamp available to the user
513 connection.remoteTimeStamp = remoteTimestamp;
514
515 // handle message
516 if (!UnpackAndInvoke(connection, reader, channelId))
517 {
518 // warn, disconnect and return if failed
519 // -> warning because attackers might send random data
520 // -> messages in a batch aren't length prefixed.
521 // failing to read one would cause undefined
522 // behaviour for every message afterwards.
523 // so we need to disconnect.
524 // -> return to avoid the below unbatches.count error.
525 // we already disconnected and handled it.
526 Debug.LogWarning($"NetworkServer: failed to unpack and invoke message. Disconnecting {connectionId}.");
527 connection.Disconnect();
528 return;
529 }
530 }
531 // otherwise disconnect
532 else
533 {
534 // WARNING, not error. can happen if attacker sends random data.
535 Debug.LogWarning($"NetworkServer: received Message was too short (messages should start with message id). Disconnecting {connectionId}");
536 connection.Disconnect();
537 return;
538 }
539 }
540
541 // if we weren't interrupted by a scene change,
542 // then all batched messages should have been processed now.
543 // otherwise batches would silently grow.
544 // we need to log an error to avoid debugging hell.
545 //
546 // EXAMPLE: https://github.com/vis2k/Mirror/issues/2882
547 // -> UnpackAndInvoke silently returned because no handler for id
548 // -> Reader would never be read past the end
549 // -> Batch would never be retired because end is never reached
550 //
551 // NOTE: prefixing every message in a batch with a length would
552 // avoid ever not reading to the end. for extra bandwidth.
553 //
554 // IMPORTANT: always keep this check to detect memory leaks.
555 // this took half a day to debug last time.
556 if (!isLoadingScene && connection.unbatcher.BatchesCount > 0)
557 {
558 Debug.LogError($"Still had {connection.unbatcher.BatchesCount} batches remaining after processing, even though processing was not interrupted by a scene change. This should never happen, as it would cause ever growing batches.\nPossible reasons:\n* A message didn't deserialize as much as it serialized\n*There was no message handler for a message id, so the reader wasn't read until the end.");
559 }
560 }
561 else Debug.LogError($"HandleData Unknown connectionId:{connectionId}");
562 }
563
564 // called by transport
565 // IMPORTANT: often times when disconnecting, we call this from Mirror
566 // too because we want to remove the connection and handle
567 // the disconnect immediately.
568 // => which is fine as long as we guarantee it only runs once
569 // => which we do by removing the connection!
570 internal static void OnTransportDisconnected(int connectionId)
571 {
572 // Debug.Log($"Server disconnect client:{connectionId}");
573 if (connections.TryGetValue(connectionId, out NetworkConnectionToClient conn))
574 {
575 RemoveConnection(connectionId);
576 // Debug.Log($"Server lost client:{connectionId}");
577
578 // NetworkManager hooks into OnDisconnectedEvent to make
579 // DestroyPlayerForConnection(conn) optional, e.g. for PvP MMOs
580 // where players shouldn't be able to escape combat instantly.
581 if (OnDisconnectedEvent != null)
582 {
583 OnDisconnectedEvent.Invoke(conn);
584 }
585 // if nobody hooked into it, then simply call DestroyPlayerForConnection
586 else
587 {
589 }
590 }
591 }
592
593 // transport errors are forwarded to high level
594 static void OnTransportError(int connectionId, TransportError error, string reason)
595 {
596 // transport errors will happen. logging a warning is enough.
597 // make sure the user does not panic.
598 Debug.LogWarning($"Server Transport Error for connId={connectionId}: {error}: {reason}. This is fine.");
599 // try get connection. passes null otherwise.
600 connections.TryGetValue(connectionId, out NetworkConnectionToClient conn);
601 OnErrorEvent?.Invoke(conn, error, reason);
602 }
603
604 // message handlers ////////////////////////////////////////////////////
606 // TODO obsolete this some day to always use the channelId version.
607 // all handlers in this version are wrapped with 1 extra action.
608 public static void RegisterHandler<T>(Action<NetworkConnectionToClient, T> handler, bool requireAuthentication = true)
609 where T : struct, NetworkMessage
610 {
611 ushort msgType = MessagePacking.GetId<T>();
612 if (handlers.ContainsKey(msgType))
613 {
614 Debug.LogWarning($"NetworkServer.RegisterHandler replacing handler for {typeof(T).FullName}, id={msgType}. If replacement is intentional, use ReplaceHandler instead to avoid this warning.");
615 }
616 handlers[msgType] = MessagePacking.WrapHandler(handler, requireAuthentication);
617 }
618
620 // This version passes channelId to the handler.
621 public static void RegisterHandler<T>(Action<NetworkConnectionToClient, T, int> handler, bool requireAuthentication = true)
622 where T : struct, NetworkMessage
623 {
624 ushort msgType = MessagePacking.GetId<T>();
625 if (handlers.ContainsKey(msgType))
626 {
627 Debug.LogWarning($"NetworkServer.RegisterHandler replacing handler for {typeof(T).FullName}, id={msgType}. If replacement is intentional, use ReplaceHandler instead to avoid this warning.");
628 }
629 handlers[msgType] = MessagePacking.WrapHandler(handler, requireAuthentication);
630 }
631
633 public static void ReplaceHandler<T>(Action<NetworkConnectionToClient, T> handler, bool requireAuthentication = true)
634 where T : struct, NetworkMessage
635 {
636 ushort msgType = MessagePacking.GetId<T>();
637 handlers[msgType] = MessagePacking.WrapHandler(handler, requireAuthentication);
638 }
639
641 public static void ReplaceHandler<T>(Action<T> handler, bool requireAuthentication = true)
642 where T : struct, NetworkMessage
643 {
644 ReplaceHandler<T>((_, value) => { handler(value); }, requireAuthentication);
645 }
646
648 public static void UnregisterHandler<T>()
649 where T : struct, NetworkMessage
650 {
651 ushort msgType = MessagePacking.GetId<T>();
652 handlers.Remove(msgType);
653 }
654
656 public static void ClearHandlers() => handlers.Clear();
657
658 internal static bool GetNetworkIdentity(GameObject go, out NetworkIdentity identity)
659 {
660 identity = go.GetComponent<NetworkIdentity>();
661 if (identity == null)
662 {
663 Debug.LogError($"GameObject {go.name} doesn't have NetworkIdentity.");
664 return false;
665 }
666 return true;
667 }
668
669 // disconnect //////////////////////////////////////////////////////////
671 // synchronous: handles disconnect events and cleans up fully before returning!
672 public static void DisconnectAll()
673 {
674 // disconnect and remove all connections.
675 // we can not use foreach here because if
676 // conn.Disconnect -> Transport.ServerDisconnect calls
677 // OnDisconnect -> NetworkServer.OnDisconnect(connectionId)
678 // immediately then OnDisconnect would remove the connection while
679 // we are iterating here.
680 // see also: https://github.com/vis2k/Mirror/issues/2357
681 // this whole process should be simplified some day.
682 // until then, let's copy .Values to avoid InvalidOperatinException.
683 // note that this is only called when stopping the server, so the
684 // copy is no performance problem.
685 foreach (NetworkConnectionToClient conn in connections.Values.ToList())
686 {
687 // disconnect via connection->transport
688 conn.Disconnect();
689
690 // we want this function to be synchronous: handle disconnect
691 // events and clean up fully before returning.
692 // -> OnTransportDisconnected can safely be called without
693 // waiting for the Transport's callback.
694 // -> it has checks to only run once.
695
696 // call OnDisconnected unless local player in host mod
697 // TODO unnecessary check?
698 if (conn.connectionId != NetworkConnection.LocalConnectionId)
699 OnTransportDisconnected(conn.connectionId);
700 }
701
702 // cleanup
703 connections.Clear();
704 localConnection = null;
705 active = false;
706 }
707
708 // add/remove/replace player ///////////////////////////////////////////
710 // When a player is added for a connection, the client for that
711 // connection is made ready automatically. The player object is
712 // automatically spawned, so you do not need to call NetworkServer.Spawn
713 // for that object. This function is used for "adding" a player, not for
714 // "replacing" the player on a connection. If there is already a player
715 // on this playerControllerId for this connection, this will fail.
716 public static bool AddPlayerForConnection(NetworkConnectionToClient conn, GameObject player)
717 {
718 NetworkIdentity identity = player.GetComponent<NetworkIdentity>();
719 if (identity == null)
720 {
721 Debug.LogWarning($"AddPlayer: playerGameObject has no NetworkIdentity. Please add a NetworkIdentity to {player}");
722 return false;
723 }
724
725 // cannot have a player object in "Add" version
726 if (conn.identity != null)
727 {
728 Debug.Log("AddPlayer: player object already exists");
729 return false;
730 }
731
732 // make sure we have a controller before we call SetClientReady
733 // because the observers will be rebuilt only if we have a controller
734 conn.identity = identity;
735
736 // Set the connection on the NetworkIdentity on the server, NetworkIdentity.SetLocalPlayer is not called on the server (it is on clients)
737 identity.SetClientOwner(conn);
738
739 // special case, we are in host mode, set hasAuthority to true so that all overrides see it
740 if (conn is LocalConnectionToClient)
741 {
742 identity.hasAuthority = true;
743 NetworkClient.InternalAddPlayer(identity);
744 }
745
746 // set ready if not set yet
747 SetClientReady(conn);
748
749 // Debug.Log($"Adding new playerGameObject object netId: {identity.netId} asset ID: {identity.assetId}");
750
751 Respawn(identity);
752 return true;
753 }
754
756 // When a player is added for a connection, the client for that
757 // connection is made ready automatically. The player object is
758 // automatically spawned, so you do not need to call NetworkServer.Spawn
759 // for that object. This function is used for "adding" a player, not for
760 // "replacing" the player on a connection. If there is already a player
761 // on this playerControllerId for this connection, this will fail.
762 public static bool AddPlayerForConnection(NetworkConnectionToClient conn, GameObject player, Guid assetId)
763 {
764 if (GetNetworkIdentity(player, out NetworkIdentity identity))
765 {
766 identity.assetId = assetId;
767 }
768 return AddPlayerForConnection(conn, player);
769 }
770
772 // This does NOT change the ready state of the connection, so it can
773 // safely be used while changing scenes.
774 public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, GameObject player, bool keepAuthority = false)
775 {
776 NetworkIdentity identity = player.GetComponent<NetworkIdentity>();
777 if (identity == null)
778 {
779 Debug.LogError($"ReplacePlayer: playerGameObject has no NetworkIdentity. Please add a NetworkIdentity to {player}");
780 return false;
781 }
782
783 if (identity.connectionToClient != null && identity.connectionToClient != conn)
784 {
785 Debug.LogError($"Cannot replace player for connection. New player is already owned by a different connection{player}");
786 return false;
787 }
788
789 //NOTE: there can be an existing player
790 //Debug.Log("NetworkServer ReplacePlayer");
791
792 NetworkIdentity previousPlayer = conn.identity;
793
794 conn.identity = identity;
795
796 // Set the connection on the NetworkIdentity on the server, NetworkIdentity.SetLocalPlayer is not called on the server (it is on clients)
797 identity.SetClientOwner(conn);
798
799 // special case, we are in host mode, set hasAuthority to true so that all overrides see it
800 if (conn is LocalConnectionToClient)
801 {
802 identity.hasAuthority = true;
803 NetworkClient.InternalAddPlayer(identity);
804 }
805
806 // add connection to observers AFTER the playerController was set.
807 // by definition, there is nothing to observe if there is no player
808 // controller.
809 //
810 // IMPORTANT: do this in AddPlayerForConnection & ReplacePlayerForConnection!
811 SpawnObserversForConnection(conn);
812
813 //Debug.Log($"Replacing playerGameObject object netId:{player.GetComponent<NetworkIdentity>().netId} asset ID {player.GetComponent<NetworkIdentity>().assetId}");
814
815 Respawn(identity);
816
817 if (keepAuthority)
818 {
819 // This needs to be sent to clear isLocalPlayer on
820 // client while keeping hasAuthority true
821 SendChangeOwnerMessage(previousPlayer, conn);
822 }
823 else
824 {
825 // This clears both isLocalPlayer and hasAuthority on client
826 previousPlayer.RemoveClientAuthority();
827 }
828
829 return true;
830 }
831
833 // This does NOT change the ready state of the connection, so it can
834 // safely be used while changing scenes.
835 public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, GameObject player, Guid assetId, bool keepAuthority = false)
836 {
837 if (GetNetworkIdentity(player, out NetworkIdentity identity))
838 {
839 identity.assetId = assetId;
840 }
841 return ReplacePlayerForConnection(conn, player, keepAuthority);
842 }
843
844 // ready ///////////////////////////////////////////////////////////////
846 // When a client has signaled that it is ready, this method tells the
847 // server that the client is ready to receive spawned objects and state
848 // synchronization updates. This is usually called in a handler for the
849 // SYSTEM_READY message. If there is not specific action a game needs to
850 // take for this message, relying on the default ready handler function
851 // is probably fine, so this call wont be needed.
853 {
854 // Debug.Log($"SetClientReadyInternal for conn:{conn}");
855
856 // set ready
857 conn.isReady = true;
858
859 // client is ready to start spawning objects
860 if (conn.identity != null)
861 SpawnObserversForConnection(conn);
862 }
863
865 // Clients that are not ready do not receive spawned objects or state
866 // synchronization updates. They client can be made ready again by
867 // calling SetClientReady().
869 {
870 conn.isReady = false;
871 conn.RemoveFromObservingsObservers();
872 conn.Send(new NotReadyMessage());
873 }
874
876 // All clients will no longer be sent state synchronization updates. The
877 // player's clients can call ClientManager.Ready() again to re-enter the
878 // ready state. This is useful when switching scenes.
879 public static void SetAllClientsNotReady()
880 {
881 foreach (NetworkConnectionToClient conn in connections.Values)
882 {
883 SetClientNotReady(conn);
884 }
885 }
886
887 // default ready handler.
888 static void OnClientReadyMessage(NetworkConnectionToClient conn, ReadyMessage msg)
889 {
890 // Debug.Log($"Default handler for ready message from {conn}");
891 SetClientReady(conn);
892 }
893
894 // show / hide for connection //////////////////////////////////////////
895 internal static void ShowForConnection(NetworkIdentity identity, NetworkConnection conn)
896 {
897 if (conn.isReady)
898 SendSpawnMessage(identity, conn);
899 }
900
901 internal static void HideForConnection(NetworkIdentity identity, NetworkConnection conn)
902 {
904 {
905 netId = identity.netId
906 };
907 conn.Send(msg);
908 }
909
911 // destroyServerObject: Indicates whether the server object should be destroyed
912 public static void RemovePlayerForConnection(NetworkConnection conn, bool destroyServerObject)
913 {
914 if (conn.identity != null)
915 {
916 if (destroyServerObject)
917 Destroy(conn.identity.gameObject);
918 else
919 UnSpawn(conn.identity.gameObject);
920
921 conn.identity = null;
922 }
923 //else Debug.Log($"Connection {conn} has no identity");
924 }
925
926 // remote calls ////////////////////////////////////////////////////////
927 // Handle command from specific player, this could be one of multiple
928 // players on a single client
929 static void OnCommandMessage(NetworkConnectionToClient conn, CommandMessage msg, int channelId)
930 {
931 if (!conn.isReady)
932 {
933 // Clients may be set NotReady due to scene change or other game logic by user, e.g. respawning.
934 // Ignore commands that may have been in flight before client received NotReadyMessage message.
935 // Unreliable messages may be out of order, so don't spam warnings for those.
936 if (channelId == Channels.Reliable)
937 Debug.LogWarning("Command received while client is not ready.\nThis may be ignored if client intentionally set NotReady.");
938 return;
939 }
940
941 if (!spawned.TryGetValue(msg.netId, out NetworkIdentity identity))
942 {
943 // over reliable channel, commands should always come after spawn.
944 // over unreliable, they might come in before the object was spawned.
945 // for example, NetworkTransform.
946 // let's not spam the console for unreliable out of order messages.
947 if (channelId == Channels.Reliable)
948 Debug.LogWarning($"Spawned object not found when handling Command message [netId={msg.netId}]");
949 return;
950 }
951
952 // Commands can be for player objects, OR other objects with client-authority
953 // -> so if this connection's controller has a different netId then
954 // only allow the command if clientAuthorityOwner
955 bool requiresAuthority = RemoteProcedureCalls.CommandRequiresAuthority(msg.functionHash);
956 if (requiresAuthority && identity.connectionToClient != conn)
957 {
958 Debug.LogWarning($"Command for object without authority [netId={msg.netId}]");
959 return;
960 }
961
962 // Debug.Log($"OnCommandMessage for netId:{msg.netId} conn:{conn}");
963
964 using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(msg.payload))
965 identity.HandleRemoteCall(msg.componentIndex, msg.functionHash, RemoteCallType.Command, networkReader, conn as NetworkConnectionToClient);
966 }
967
968 // spawning ////////////////////////////////////////////////////////////
969 static ArraySegment<byte> CreateSpawnMessagePayload(bool isOwner, NetworkIdentity identity, NetworkWriterPooled ownerWriter, NetworkWriterPooled observersWriter)
970 {
971 // Only call OnSerializeAllSafely if there are NetworkBehaviours
972 if (identity.NetworkBehaviours.Length == 0)
973 {
974 return default;
975 }
976
977 // serialize all components with initialState = true
978 // (can be null if has none)
979 identity.OnSerializeAllSafely(true, ownerWriter, observersWriter);
980
981 // convert to ArraySegment to avoid reader allocations
982 // if nothing was written, .ToArraySegment returns an empty segment.
983 ArraySegment<byte> ownerSegment = ownerWriter.ToArraySegment();
984 ArraySegment<byte> observersSegment = observersWriter.ToArraySegment();
985
986 // use owner segment if 'conn' owns this identity, otherwise
987 // use observers segment
988 ArraySegment<byte> payload = isOwner ? ownerSegment : observersSegment;
989
990 return payload;
991 }
992
993 internal static void SendSpawnMessage(NetworkIdentity identity, NetworkConnection conn)
994 {
995 if (identity.serverOnly) return;
996
997 //Debug.Log($"Server SendSpawnMessage: name:{identity.name} sceneId:{identity.sceneId:X} netid:{identity.netId}");
998
999 // one writer for owner, one for observers
1000 using (NetworkWriterPooled ownerWriter = NetworkWriterPool.Get(), observersWriter = NetworkWriterPool.Get())
1001 {
1002 bool isOwner = identity.connectionToClient == conn;
1003 ArraySegment<byte> payload = CreateSpawnMessagePayload(isOwner, identity, ownerWriter, observersWriter);
1004 SpawnMessage message = new SpawnMessage
1005 {
1006 netId = identity.netId,
1007 isLocalPlayer = conn.identity == identity,
1008 isOwner = isOwner,
1009 sceneId = identity.sceneId,
1010 assetId = identity.assetId,
1011 // use local values for VR support
1012 position = identity.transform.localPosition,
1013 rotation = identity.transform.localRotation,
1014 scale = identity.transform.localScale,
1015 payload = payload
1016 };
1017 conn.Send(message);
1018 }
1019 }
1020
1021 internal static void SendChangeOwnerMessage(NetworkIdentity identity, NetworkConnectionToClient conn)
1022 {
1023 // Don't send if identity isn't spawned or only exists on server
1024 if (identity.netId == 0 || identity.serverOnly) return;
1025
1026 // Don't send if conn doesn't have the identity spawned yet
1027 // May be excluded from the client by interest management
1028 if (!conn.observing.Contains(identity)) return;
1029
1030 //Debug.Log($"Server SendChangeOwnerMessage: name={identity.name} netid={identity.netId}");
1031
1032 conn.Send(new ChangeOwnerMessage
1033 {
1034 netId = identity.netId,
1035 isOwner = identity.connectionToClient == conn,
1036 isLocalPlayer = conn.identity == identity
1037 });
1038 }
1039
1040 static void SpawnObject(GameObject obj, NetworkConnection ownerConnection)
1041 {
1042 // verify if we can spawn this
1043 if (Utils.IsPrefab(obj))
1044 {
1045 Debug.LogError($"GameObject {obj.name} is a prefab, it can't be spawned. Instantiate it first.");
1046 return;
1047 }
1048
1049 if (!active)
1050 {
1051 Debug.LogError($"SpawnObject for {obj}, NetworkServer is not active. Cannot spawn objects without an active server.");
1052 return;
1053 }
1054
1055 NetworkIdentity identity = obj.GetComponent<NetworkIdentity>();
1056 if (identity == null)
1057 {
1058 Debug.LogError($"SpawnObject {obj} has no NetworkIdentity. Please add a NetworkIdentity to {obj}");
1059 return;
1060 }
1061
1062 if (identity.SpawnedFromInstantiate)
1063 {
1064 // Using Instantiate on SceneObject is not allowed, so stop spawning here
1065 // NetworkIdentity.Awake already logs error, no need to log a second error here
1066 return;
1067 }
1068
1069 identity.connectionToClient = (NetworkConnectionToClient)ownerConnection;
1070
1071 // special case to make sure hasAuthority is set
1072 // on start server in host mode
1073 if (ownerConnection is LocalConnectionToClient)
1074 identity.hasAuthority = true;
1075
1076 identity.OnStartServer();
1077
1078 // Debug.Log($"SpawnObject instance ID {identity.netId} asset ID {identity.assetId}");
1079
1080 if (aoi)
1081 {
1082 // This calls user code which might throw exceptions
1083 // We don't want this to leave us in bad state
1084 try
1085 {
1086 aoi.OnSpawned(identity);
1087 }
1088 catch (Exception e)
1089 {
1090 Debug.LogException(e);
1091 }
1092 }
1093
1094 RebuildObservers(identity, true);
1095 }
1096
1098 // This will cause a new object to be instantiated from the registered
1099 // prefab, or from a custom spawn function.
1100 public static void Spawn(GameObject obj, NetworkConnection ownerConnection = null)
1101 {
1102 SpawnObject(obj, ownerConnection);
1103 }
1104
1106 // This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
1107 public static void Spawn(GameObject obj, GameObject ownerPlayer)
1108 {
1109 NetworkIdentity identity = ownerPlayer.GetComponent<NetworkIdentity>();
1110 if (identity == null)
1111 {
1112 Debug.LogError("Player object has no NetworkIdentity");
1113 return;
1114 }
1115
1116 if (identity.connectionToClient == null)
1117 {
1118 Debug.LogError("Player object is not a player.");
1119 return;
1120 }
1121
1122 Spawn(obj, identity.connectionToClient);
1123 }
1124
1126 // This is the same as calling NetworkIdentity.AssignClientAuthority on the spawned object.
1127 public static void Spawn(GameObject obj, Guid assetId, NetworkConnection ownerConnection = null)
1128 {
1129 if (GetNetworkIdentity(obj, out NetworkIdentity identity))
1130 {
1131 identity.assetId = assetId;
1132 }
1133 SpawnObject(obj, ownerConnection);
1134 }
1135
1136 internal static bool ValidateSceneObject(NetworkIdentity identity)
1137 {
1138 if (identity.gameObject.hideFlags == HideFlags.NotEditable ||
1139 identity.gameObject.hideFlags == HideFlags.HideAndDontSave)
1140 return false;
1141
1142#if UNITY_EDITOR
1143 if (UnityEditor.EditorUtility.IsPersistent(identity.gameObject))
1144 return false;
1145#endif
1146
1147 // If not a scene object
1148 return identity.sceneId != 0;
1149 }
1150
1152 // NetworkIdentity objects in a scene are disabled by default. Calling
1153 // SpawnObjects() causes these scene objects to be enabled and spawned.
1154 // It is like calling NetworkServer.Spawn() for each of them.
1155 public static bool SpawnObjects()
1156 {
1157 // only if server active
1158 if (!active)
1159 return false;
1160
1161 NetworkIdentity[] identities = Resources.FindObjectsOfTypeAll<NetworkIdentity>();
1162
1163 // first pass: activate all scene objects
1164 foreach (NetworkIdentity identity in identities)
1165 {
1166 if (ValidateSceneObject(identity))
1167 {
1168 // Debug.Log($"SpawnObjects sceneId:{identity.sceneId:X} name:{identity.gameObject.name}");
1169 identity.gameObject.SetActive(true);
1170
1171 // fix https://github.com/vis2k/Mirror/issues/2778:
1172 // -> SetActive(true) does NOT call Awake() if the parent
1173 // is inactive
1174 // -> we need Awake() to initialize NetworkBehaviours[] etc.
1175 // because our second pass below spawns and works with it
1176 // => detect this situation and manually call Awake for
1177 // proper initialization
1178 if (!identity.gameObject.activeInHierarchy)
1179 identity.Awake();
1180 }
1181 }
1182
1183 // second pass: spawn all scene objects
1184 foreach (NetworkIdentity identity in identities)
1185 {
1186 if (ValidateSceneObject(identity))
1187 // pass connection so that authority is not lost when server loads a scene
1188 // https://github.com/vis2k/Mirror/pull/2987
1189 Spawn(identity.gameObject, identity.connectionToClient);
1190 }
1191
1192 return true;
1193 }
1194
1195 static void Respawn(NetworkIdentity identity)
1196 {
1197 if (identity.netId == 0)
1198 {
1199 // If the object has not been spawned, then do a full spawn and update observers
1200 Spawn(identity.gameObject, identity.connectionToClient);
1201 }
1202 else
1203 {
1204 // otherwise just replace his data
1205 SendSpawnMessage(identity, identity.connectionToClient);
1206 }
1207 }
1208
1209 static void SpawnObserversForConnection(NetworkConnectionToClient conn)
1210 {
1211 //Debug.Log($"Spawning {spawned.Count} objects for conn {conn}");
1212
1213 if (!conn.isReady)
1214 {
1215 // client needs to finish initializing before we can spawn objects
1216 // otherwise it would not find them.
1217 return;
1218 }
1219
1220 // let connection know that we are about to start spawning...
1221 conn.Send(new ObjectSpawnStartedMessage());
1222
1223 // add connection to each nearby NetworkIdentity's observers, which
1224 // internally sends a spawn message for each one to the connection.
1225 foreach (NetworkIdentity identity in spawned.Values)
1226 {
1227 // try with far away ones in ummorpg!
1228 if (identity.gameObject.activeSelf) //TODO this is different
1229 {
1230 //Debug.Log($"Sending spawn message for current server objects name:{identity.name} netId:{identity.netId} sceneId:{identity.sceneId:X}");
1231
1232 // we need to support three cases:
1233 // - legacy system (identity has .visibility)
1234 // - new system (networkserver has .aoi)
1235 // - default case: no .visibility and no .aoi means add all
1236 // connections by default)
1237 //
1238 // ForceHidden/ForceShown overwrite all systems so check it
1239 // first!
1240
1241 // ForceShown: add no matter what
1242 if (identity.visible == Visibility.ForceShown)
1243 {
1244 identity.AddObserver(conn);
1245 }
1246 // ForceHidden: don't show no matter what
1247 else if (identity.visible == Visibility.ForceHidden)
1248 {
1249 // do nothing
1250 }
1251 // default: legacy system / new system / no system support
1252 else if (identity.visible == Visibility.Default)
1253 {
1254 // aoi system
1255 if (aoi != null)
1256 {
1257 // call OnCheckObserver
1258 if (aoi.OnCheckObserver(identity, conn))
1259 identity.AddObserver(conn);
1260 }
1261 // no system: add all observers by default
1262 else
1263 {
1264 identity.AddObserver(conn);
1265 }
1266 }
1267 }
1268 }
1269
1270 // let connection know that we finished spawning, so it can call
1271 // OnStartClient on each one (only after all were spawned, which
1272 // is how Unity's Start() function works too)
1273 conn.Send(new ObjectSpawnFinishedMessage());
1274 }
1275
1277 // The object will be removed from clients that it was spawned on, or
1278 // the custom spawn handler function on the client will be called for
1279 // the object.
1280 // Unlike when calling NetworkServer.Destroy(), on the server the object
1281 // will NOT be destroyed. This allows the server to re-use the object,
1282 // even spawn it again later.
1283 public static void UnSpawn(GameObject obj) => DestroyObject(obj, DestroyMode.Reset);
1284
1285 // destroy /////////////////////////////////////////////////////////////
1287 // This is used when a client disconnects, to remove the players for
1288 // that client. This also destroys non-player objects that have client
1289 // authority set for this connection.
1291 {
1292 // destroy all objects owned by this connection, including the player object
1293 conn.DestroyOwnedObjects();
1294 // remove connection from all of its observing entities observers
1295 // fixes https://github.com/vis2k/Mirror/issues/2737
1296 // -> cleaning those up in NetworkConnection.Disconnect is NOT enough
1297 // because voluntary disconnects from the other end don't call
1298 // NetworkConnectionn.Disconnect()
1299 conn.RemoveFromObservingsObservers();
1300 conn.identity = null;
1301 }
1302
1303 // sometimes we want to GameObject.Destroy it.
1304 // sometimes we want to just unspawn on clients and .Reset() it on server.
1305 // => 'bool destroy' isn't obvious enough. it's really destroy OR reset!
1306 enum DestroyMode { Destroy, Reset }
1307
1308 static void DestroyObject(NetworkIdentity identity, DestroyMode mode)
1309 {
1310 // Debug.Log($"DestroyObject instance:{identity.netId}");
1311
1312 // only call OnRebuildObservers while active,
1313 // not while shutting down
1314 // (https://github.com/vis2k/Mirror/issues/2977)
1315 if (active && aoi)
1316 {
1317 // This calls user code which might throw exceptions
1318 // We don't want this to leave us in bad state
1319 try
1320 {
1321 aoi.OnDestroyed(identity);
1322 }
1323 catch (Exception e)
1324 {
1325 Debug.LogException(e);
1326 }
1327 }
1328
1329 // remove from NetworkServer (this) dictionary
1330 spawned.Remove(identity.netId);
1331
1332 identity.connectionToClient?.RemoveOwnedObject(identity);
1333
1334 // send object destroy message to all observers, clear observers
1335 SendToObservers(identity, new ObjectDestroyMessage{netId = identity.netId});
1336 identity.ClearObservers();
1337
1338 // in host mode, call OnStopClient/OnStopLocalPlayer manually
1340 {
1341 if (identity.isLocalPlayer)
1342 identity.OnStopLocalPlayer();
1343
1344 identity.OnStopClient();
1345 // The object may have been spawned with host client ownership,
1346 // e.g. a pet so we need to clear hasAuthority and call
1347 // NotifyAuthority which invokes OnStopAuthority if hasAuthority.
1348 identity.hasAuthority = false;
1349 identity.NotifyAuthority();
1350
1351 // remove from NetworkClient dictionary
1352 NetworkClient.spawned.Remove(identity.netId);
1353 }
1354
1355 // we are on the server. call OnStopServer.
1356 identity.OnStopServer();
1357
1358 // are we supposed to GameObject.Destroy() it completely?
1359 if (mode == DestroyMode.Destroy)
1360 {
1361 identity.destroyCalled = true;
1362
1363 // Destroy if application is running
1364 if (Application.isPlaying)
1365 {
1366 UnityEngine.Object.Destroy(identity.gameObject);
1367 }
1368 // Destroy can't be used in Editor during tests. use DestroyImmediate.
1369 else
1370 {
1371 GameObject.DestroyImmediate(identity.gameObject);
1372 }
1373 }
1374 // otherwise simply .Reset() and set inactive again
1375 else if (mode == DestroyMode.Reset)
1376 {
1377 identity.Reset();
1378 }
1379 }
1380
1381 static void DestroyObject(GameObject obj, DestroyMode mode)
1382 {
1383 if (obj == null)
1384 {
1385 Debug.Log("NetworkServer DestroyObject is null");
1386 return;
1387 }
1388
1389 if (GetNetworkIdentity(obj, out NetworkIdentity identity))
1390 {
1391 DestroyObject(identity, mode);
1392 }
1393 }
1394
1396 // In some cases it is useful to remove an object but not delete it on
1397 // the server. For that, use NetworkServer.UnSpawn() instead of
1398 // NetworkServer.Destroy().
1399 public static void Destroy(GameObject obj) => DestroyObject(obj, DestroyMode.Destroy);
1400
1401 // interest management /////////////////////////////////////////////////
1402 // Helper function to add all server connections as observers.
1403 // This is used if none of the components provides their own
1404 // OnRebuildObservers function.
1405 internal static void AddAllReadyServerConnectionsToObservers(NetworkIdentity identity)
1406 {
1407 // add all server connections
1408 foreach (NetworkConnectionToClient conn in connections.Values)
1409 {
1410 // only if authenticated (don't send to people during logins)
1411 if (conn.isReady)
1412 identity.AddObserver(conn);
1413 }
1414
1415 // add local host connection (if any)
1417 {
1418 identity.AddObserver(localConnection);
1419 }
1420 }
1421
1422 // allocate newObservers helper HashSet only once
1423 // internal for tests
1424 internal static readonly HashSet<NetworkConnectionToClient> newObservers =
1425 new HashSet<NetworkConnectionToClient>();
1426
1427 // rebuild observers default method (no AOI) - adds all connections
1428 static void RebuildObserversDefault(NetworkIdentity identity, bool initialize)
1429 {
1430 // only add all connections when rebuilding the first time.
1431 // second time we just keep them without rebuilding anything.
1432 if (initialize)
1433 {
1434 // not force hidden?
1435 if (identity.visible != Visibility.ForceHidden)
1436 {
1437 AddAllReadyServerConnectionsToObservers(identity);
1438 }
1439 }
1440 }
1441
1442 // rebuild observers via interest management system
1443 static void RebuildObserversCustom(NetworkIdentity identity, bool initialize)
1444 {
1445 // clear newObservers hashset before using it
1446 newObservers.Clear();
1447
1448 // not force hidden?
1449 if (identity.visible != Visibility.ForceHidden)
1450 {
1451 aoi.OnRebuildObservers(identity, newObservers);
1452 }
1453
1454 // IMPORTANT: AFTER rebuilding add own player connection in any case
1455 // to ensure player always sees himself no matter what.
1456 // -> OnRebuildObservers might clear observers, so we need to add
1457 // the player's own connection AFTER. 100% fail safe.
1458 // -> fixes https://github.com/vis2k/Mirror/issues/692 where a
1459 // player might teleport out of the ProximityChecker's cast,
1460 // losing the own connection as observer.
1461 if (identity.connectionToClient != null)
1462 {
1463 newObservers.Add(identity.connectionToClient);
1464 }
1465
1466 bool changed = false;
1467
1468 // add all newObservers that aren't in .observers yet
1469 foreach (NetworkConnectionToClient conn in newObservers)
1470 {
1471 // only add ready connections.
1472 // otherwise the player might not be in the world yet or anymore
1473 if (conn != null && conn.isReady)
1474 {
1475 if (initialize || !identity.observers.ContainsKey(conn.connectionId))
1476 {
1477 // new observer
1478 conn.AddToObserving(identity);
1479 // Debug.Log($"New Observer for {gameObject} {conn}");
1480 changed = true;
1481 }
1482 }
1483 }
1484
1485 // remove all old .observers that aren't in newObservers anymore
1486 foreach (NetworkConnectionToClient conn in identity.observers.Values)
1487 {
1488 if (!newObservers.Contains(conn))
1489 {
1490 // removed observer
1491 conn.RemoveFromObserving(identity, false);
1492 // Debug.Log($"Removed Observer for {gameObjec} {conn}");
1493 changed = true;
1494 }
1495 }
1496
1497 // copy new observers to observers
1498 if (changed)
1499 {
1500 identity.observers.Clear();
1501 foreach (NetworkConnectionToClient conn in newObservers)
1502 {
1503 if (conn != null && conn.isReady)
1504 identity.observers.Add(conn.connectionId, conn);
1505 }
1506 }
1507
1508 // special case for host mode: we use SetHostVisibility to hide
1509 // NetworkIdentities that aren't in observer range from host.
1510 // this is what games like Dota/Counter-Strike do too, where a host
1511 // does NOT see all players by default. they are in memory, but
1512 // hidden to the host player.
1513 //
1514 // this code is from UNET, it's a bit strange but it works:
1515 // * it hides newly connected identities in host mode
1516 // => that part was the intended behaviour
1517 // * it hides ALL NetworkIdentities in host mode when the host
1518 // connects but hasn't selected a character yet
1519 // => this only works because we have no .localConnection != null
1520 // check. at this stage, localConnection is null because
1521 // StartHost starts the server first, then calls this code,
1522 // then starts the client and sets .localConnection. so we can
1523 // NOT add a null check without breaking host visibility here.
1524 // * it hides ALL NetworkIdentities in server-only mode because
1525 // observers never contain the 'null' .localConnection
1526 // => that was not intended, but let's keep it as it is so we
1527 // don't break anything in host mode. it's way easier than
1528 // iterating all identities in a special function in StartHost.
1529 if (initialize)
1530 {
1531 if (!newObservers.Contains(localConnection))
1532 {
1533 if (aoi != null)
1534 aoi.SetHostVisibility(identity, false);
1535 }
1536 }
1537 }
1538
1539 // RebuildObservers does a local rebuild for the NetworkIdentity.
1540 // This causes the set of players that can see this object to be rebuild.
1541 //
1542 // IMPORTANT:
1543 // => global rebuild would be more simple, BUT
1544 // => local rebuild is way faster for spawn/despawn because we can
1545 // simply rebuild a select NetworkIdentity only
1546 // => having both .observers and .observing is necessary for local
1547 // rebuilds
1548 //
1549 // in other words, this is the perfect solution even though it's not
1550 // completely simple (due to .observers & .observing)
1551 //
1552 // Mirror maintains .observing automatically in the background. best of
1553 // both worlds without any worrying now!
1554 public static void RebuildObservers(NetworkIdentity identity, bool initialize)
1555 {
1556 // observers are null until OnStartServer creates them
1557 if (identity.observers == null)
1558 return;
1559
1560 // if there is no interest management system,
1561 // or if 'force shown' then add all connections
1562 if (aoi == null || identity.visible == Visibility.ForceShown)
1563 {
1564 RebuildObserversDefault(identity, initialize);
1565 }
1566 // otherwise let interest management system rebuild
1567 else
1568 {
1569 RebuildObserversCustom(identity, initialize);
1570 }
1571 }
1572
1573 // broadcasting ////////////////////////////////////////////////////////
1574 // helper function to get the right serialization for a connection
1575 static NetworkWriter GetEntitySerializationForConnection(NetworkIdentity identity, NetworkConnectionToClient connection)
1576 {
1577 // get serialization for this entity (cached)
1578 // IMPORTANT: int tick avoids floating point inaccuracy over days/weeks
1579 NetworkIdentitySerialization serialization = identity.GetSerializationAtTick(Time.frameCount);
1580
1581 // is this entity owned by this connection?
1582 bool owned = identity.connectionToClient == connection;
1583
1584 // send serialized data
1585 // owner writer if owned
1586 if (owned)
1587 {
1588 // was it dirty / did we actually serialize anything?
1589 if (serialization.ownerWriter.Position > 0)
1590 return serialization.ownerWriter;
1591 }
1592 // observers writer if not owned
1593 else
1594 {
1595 // was it dirty / did we actually serialize anything?
1596 if (serialization.observersWriter.Position > 0)
1597 return serialization.observersWriter;
1598 }
1599
1600 // nothing was serialized
1601 return null;
1602 }
1603
1604 // helper function to broadcast the world to a connection
1605 static void BroadcastToConnection(NetworkConnectionToClient connection)
1606 {
1607 // for each entity that this connection is seeing
1608 foreach (NetworkIdentity identity in connection.observing)
1609 {
1610 // make sure it's not null or destroyed.
1611 // (which can happen if someone uses
1612 // GameObject.Destroy instead of
1613 // NetworkServer.Destroy)
1614 if (identity != null)
1615 {
1616 // get serialization for this entity viewed by this connection
1617 // (if anything was serialized this time)
1618 NetworkWriter serialization = GetEntitySerializationForConnection(identity, connection);
1619 if (serialization != null)
1620 {
1622 {
1623 netId = identity.netId,
1624 payload = serialization.ToArraySegment()
1625 };
1626 connection.Send(message);
1627 }
1628 }
1629 // spawned list should have no null entries because we
1630 // always call Remove in OnObjectDestroy everywhere.
1631 // if it does have null then someone used
1632 // GameObject.Destroy instead of NetworkServer.Destroy.
1633 else Debug.LogWarning($"Found 'null' entry in observing list for connectionId={connection.connectionId}. Please call NetworkServer.Destroy to destroy networked objects. Don't use GameObject.Destroy.");
1634 }
1635 }
1636
1637 // NetworkLateUpdate called after any Update/FixedUpdate/LateUpdate
1638 // (we add this to the UnityEngine in NetworkLoop)
1639 // internal for tests
1640 internal static readonly List<NetworkConnectionToClient> connectionsCopy =
1641 new List<NetworkConnectionToClient>();
1642
1643 static void Broadcast()
1644 {
1645 // copy all connections into a helper collection so that
1646 // OnTransportDisconnected can be called while iterating.
1647 // -> OnTransportDisconnected removes from the collection
1648 // -> which would throw 'can't modify while iterating' errors
1649 // => see also: https://github.com/vis2k/Mirror/issues/2739
1650 // (copy nonalloc)
1651 // TODO remove this when we move to 'lite' transports with only
1652 // socket send/recv later.
1653 connectionsCopy.Clear();
1654 connections.Values.CopyTo(connectionsCopy);
1655
1656 // go through all connections
1657 foreach (NetworkConnectionToClient connection in connectionsCopy)
1658 {
1659 // has this connection joined the world yet?
1660 // for each READY connection:
1661 // pull in UpdateVarsMessage for each entity it observes
1662 if (connection.isReady)
1663 {
1664 // broadcast world state to this connection
1665 BroadcastToConnection(connection);
1666 }
1667
1668 // update connection to flush out batched messages
1669 connection.Update();
1670 }
1671
1672 // TODO this is way too slow because we iterate ALL spawned :/
1673 // TODO this is way too complicated :/
1674 // to understand what this tries to prevent, consider this example:
1675 // monster has health=100
1676 // we change health=200, dirty bit is set
1677 // player comes in range, gets full serialization spawn packet.
1678 // next Broadcast(), player gets the health=200 change because dirty bit was set.
1679 //
1680 // this code clears all dirty bits if no players are around to prevent it.
1681 // BUT there are two issues:
1682 // 1. what if a playerB was around the whole time?
1683 // 2. why don't we handle broadcast and spawn packets both HERE?
1684 // handling spawn separately is why we need this complex magic
1685 //
1686 // see test: DirtyBitsAreClearedForSpawnedWithoutObservers()
1687 // see test: SyncObjectChanges_DontGrowWithoutObservers()
1688 //
1689 // PAUL: we also do this to avoid ever growing SyncList .changes
1690 //ClearSpawnedDirtyBits();
1691 //
1692 // this was moved to NetworkIdentity.AddObserver!
1693 // same result, but no more O(N) loop in here!
1694 // TODO remove this comment after moving spawning into Broadcast()!
1695 }
1696
1697 // update //////////////////////////////////////////////////////////////
1698 // NetworkEarlyUpdate called before any Update/FixedUpdate
1699 // (we add this to the UnityEngine in NetworkLoop)
1700 internal static void NetworkEarlyUpdate()
1701 {
1702 // process all incoming messages first before updating the world
1703 if (Transport.activeTransport != null)
1704 Transport.activeTransport.ServerEarlyUpdate();
1705 }
1706
1707 internal static void NetworkLateUpdate()
1708 {
1709 // only broadcast world if active
1710 if (active)
1711 Broadcast();
1712
1713 // process all outgoing messages after updating the world
1714 // (even if not active. still want to process disconnects etc.)
1715 if (Transport.activeTransport != null)
1716 Transport.activeTransport.ServerLateUpdate();
1717 }
1718 }
1719}
virtual void OnDestroyed(NetworkIdentity identity)
Called on the server when a networked object is destroyed.
virtual void OnSpawned(NetworkIdentity identity)
Called on the server when a new networked object is spawned.
NetworkClient with connection to server.
static bool active
active is true while a client is connecting/connected
static readonly Dictionary< uint, NetworkIdentity > spawned
All spawned NetworkIdentities by netId.
Base NetworkConnection class for server-to-client and client-to-server connection.
readonly int connectionId
Unique identifier for this connection that is assigned by the transport layer.
NetworkIdentity identity
This connection's main object (usually the player object).
bool isReady
A server connection is ready after joining the game world.
override void Disconnect()
Disconnects this connection.
new readonly HashSet< NetworkIdentity > observing
NetworkIdentities that this connection can see
Profiling statistics for tool to subscribe to (profiler etc.)
NetworkIdentity identifies objects across the network.
bool isClient
Returns true if running as a client and this object was spawned by a server.
bool serverOnly
Make this object only exist when the game is running as a server (or host).
uint netId
The unique network Id of this object (unique at runtime).
Guid assetId
Prefab GUID used to spawn prefabs across the network.
ulong sceneId
Unique identifier for NetworkIdentity objects within a scene, used for spawning scene objects.
bool isLocalPlayer
Return true if this object represents the player on the local machine.
Dictionary< int, NetworkConnectionToClient > observers
The set of network connections (players) that can see this object.
void RemoveClientAuthority()
Removes ownership for an object.
NetworkConnectionToClient connectionToClient
Server's network connection to the client. This is only valid for client-owned objects (including the...
Network Reader for most simple types like floats, ints, buffers, structs, etc. Use NetworkReaderPool....
Pool of NetworkReaders to avoid allocations.
static NetworkReaderPooled Get(byte[] bytes)
Get the next reader in the pool. If pool is empty, creates a new Reader
Pooled NetworkReader, automatically returned to pool when using 'using'
NetworkServer handles remote connections and has a local connection for a local client.
static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, GameObject player, bool keepAuthority=false)
Replaces connection's player object. The old object is not destroyed.
static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, GameObject player, Guid assetId, bool keepAuthority=false)
Replaces connection's player object. The old object is not destroyed.
static bool RemoveConnection(int connectionId)
Removes a connection by connectionId. Returns true if removed.
static void SendToAll< T >(T message, int channelId=Channels.Reliable, bool sendToReadyOnly=false)
Send a message to all clients, even those that haven't joined the world yet (non ready)
static void SendToReady< T >(T message, int channelId=Channels.Reliable)
Send a message to all clients which have joined the world (are ready).
static bool HasExternalConnections()
True if we have external connections (that are not host)
static bool SpawnObjects()
Spawns NetworkIdentities in the scene on the server.
static bool AddPlayerForConnection(NetworkConnectionToClient conn, GameObject player, Guid assetId)
Called by server after AddPlayer message to add the player for the connection.
static void UnregisterHandler< T >()
Unregister a handler for a message type T.
static void Spawn(GameObject obj, NetworkConnection ownerConnection=null)
Spawn the given game object on all clients which are ready.
static bool AddConnection(NetworkConnectionToClient conn)
Add a connection and setup callbacks. Returns true if not added yet.
static void SetClientReady(NetworkConnectionToClient conn)
Flags client connection as ready (=joined world).
static Dictionary< int, NetworkConnectionToClient > connections
Dictionary of all server connections, with connectionId as key
static void Spawn(GameObject obj, Guid assetId, NetworkConnection ownerConnection=null)
Spawns an object and also assigns Client Authority to the specified client.
static bool AddPlayerForConnection(NetworkConnectionToClient conn, GameObject player)
Called by server after AddPlayer message to add the player for the connection.
static NetworkConnectionToClient localConnection
Connection to host mode client (if any)
static void UnSpawn(GameObject obj)
This takes an object that has been spawned and un-spawns it.
static void RemovePlayerForConnection(NetworkConnection conn, bool destroyServerObject)
Removes the player object from the connection
static void Listen(int maxConns)
Starts server and listens to incoming connections with max connections limit.
static void ClearHandlers()
Clears all registered message handlers.
static bool localClientActive
True is a local client is currently active on the server
static void ReplaceHandler< T >(Action< NetworkConnectionToClient, T > handler, bool requireAuthentication=true)
Replace a handler for message type T. Most should require authentication.
static bool active
active checks if the server has been started
static readonly Dictionary< uint, NetworkIdentity > spawned
All spawned NetworkIdentities by netId.
static void Spawn(GameObject obj, GameObject ownerPlayer)
Spawns an object and also assigns Client Authority to the specified client.
static void RegisterHandler< T >(Action< NetworkConnectionToClient, T > handler, bool requireAuthentication=true)
Register a handler for message type T. Most should require authentication.
static void DisconnectAll()
Disconnect all connections, including the local connection.
static void Destroy(GameObject obj)
Destroys this object and corresponding objects on all clients.
static bool dontListen
Single player mode can use dontListen to not accept incoming connections
static void DestroyPlayerForConnection(NetworkConnectionToClient conn)
Destroys all of the connection's owned objects on the server.
static void Shutdown()
Shuts down the server and disconnects all clients
static void SetAllClientsNotReady()
Marks all connected clients as no longer ready.
static void SendToReadyObservers< T >(NetworkIdentity identity, T message, bool includeOwner=true, int channelId=Channels.Reliable)
Send a message to only clients which are ready with option to include the owner of the object identit...
static void SetClientNotReady(NetworkConnectionToClient conn)
Marks the client of the connection to be not-ready.
Synchronizes server time to clients.
Definition: NetworkTime.cs:12
Network Writer for most simple types like floats, ints, buffers, structs, etc. Use NetworkWriterPool....
ArraySegment< byte > ToArraySegment()
Returns allocation-free ArraySegment until 'Position'.
int Position
Next position to write to the buffer
Pool of NetworkWriters to avoid allocations.
static NetworkWriterPooled Get()
Get a writer from the pool. Creates new one if pool is empty.
Pooled NetworkWriter, automatically returned to pool when using 'using'
Used to help manage remote calls for NetworkBehaviours
Definition: RemoteCalls.cs:32
Abstract transport layer component
Definition: Transport.cs:32
abstract void ServerStart()
Start listening for connections.
static Transport activeTransport
The current transport used by Mirror.
Definition: Transport.cs:35
abstract void ServerStop()
Stop listening and disconnect all connections.
abstract void ServerDisconnect(int connectionId)
Disconnect a client from the server.