diff --git a/Assets/Mirage/Runtime/NetworkIdentity.cs b/Assets/Mirage/Runtime/NetworkIdentity.cs index d4f21cb992..4a3abfde4b 100644 --- a/Assets/Mirage/Runtime/NetworkIdentity.cs +++ b/Assets/Mirage/Runtime/NetworkIdentity.cs @@ -1026,7 +1026,10 @@ private bool RemoveOldObservers() // add all newObservers that aren't in .observers yet private bool AddNewObservers(bool initialize) { - var changed = false; + using var addedWrapper = AutoPool>.Take(); + var added = addedWrapper.Item; + Debug.Assert(added.Count == 0); + foreach (var player in newObservers) { // only add ready connections. @@ -1036,13 +1039,15 @@ private bool AddNewObservers(bool initialize) // new observer player.AddToVisList(this); // spawn identity for this conn - ServerObjectManager.ShowToPlayer(this, player); + added.Add(player); if (logger.LogEnabled()) logger.Log($"Added new observer '{player}' for {gameObject}"); - changed = true; } } - return changed; + if (added.Count > 0) + ServerObjectManager.ShowToPlayerMany(this, added); + + return added.Count > 0; } /// diff --git a/Assets/Mirage/Runtime/ServerObjectManager.cs b/Assets/Mirage/Runtime/ServerObjectManager.cs index 7eb156cb43..2a29fc6fac 100644 --- a/Assets/Mirage/Runtime/ServerObjectManager.cs +++ b/Assets/Mirage/Runtime/ServerObjectManager.cs @@ -255,11 +255,39 @@ internal void ShowToPlayer(NetworkIdentity identity, INetworkPlayer player) if (player.SceneIsReady) SendSpawnMessage(identity, player); } + /// + /// Sends spawn message to player if it is not loading a scene + /// + /// + /// + internal void ShowToPlayerMany(NetworkIdentity identity, List players) + { + // make new list so that we can filter out SceneIsReady + using var addedWrapper = AutoPool>.Take(); + var sendTo = addedWrapper.Item; + Debug.Assert(sendTo.Count == 0); + + foreach (var player in players) + { + var visibility = identity.Visibility; + if (visibility is NetworkVisibility networkVisibility) + networkVisibility.InvokeVisibilityChanged(player, true); + + if (player.SceneIsReady) + sendTo.Add(player); + } + + if (sendTo.Count == 1) + SendSpawnMessage(identity, sendTo[0]); + else if (sendTo.Count > 1) + SendSpawnMessageMany(identity, sendTo); + } + internal void HideToPlayer(NetworkIdentity identity, INetworkPlayer player) { - var visiblity = identity.Visibility; - if (visiblity is NetworkVisibility networkVisibility) + var visibility = identity.Visibility; + if (visibility is NetworkVisibility networkVisibility) networkVisibility.InvokeVisibilityChanged(player, false); player.Send(new ObjectHideMessage { NetId = identity.NetId }); @@ -388,7 +416,14 @@ internal void SendSpawnMessage(NetworkIdentity identity, INetworkPlayer player) { var isOwner = identity.Owner == player; - var payload = CreateSpawnMessagePayload(isOwner, identity, ownerWriter, observersWriter); + ArraySegment payload = default; + var hasPayload = CreateSpawnMessagePayload(identity, ownerWriter, observersWriter); + if (hasPayload) + { + payload = isOwner + ? ownerWriter.ToArraySegment() + : observersWriter.ToArraySegment(); + } var prefabHash = identity.IsPrefab ? identity.PrefabHash : default(int?); var sceneId = identity.IsSceneObject ? identity.SceneId : default(ulong?); @@ -404,10 +439,64 @@ internal void SendSpawnMessage(NetworkIdentity identity, INetworkPlayer player) msg.SpawnValues = CreateSpawnValues(identity); - player.Send(msg); } } + internal void SendSpawnMessageMany(NetworkIdentity identity, List players) + { + if (logger.LogEnabled()) logger.Log($"Server SendSpawnMessage: name={identity.name} sceneId={identity.SceneId:X} netId={identity.NetId}"); + + // one writer for owner, one for observers + using (PooledNetworkWriter ownerWriter = NetworkWriterPool.GetWriter(), observersWriter = NetworkWriterPool.GetWriter()) + { + ArraySegment payload = default; + var hasPayload = CreateSpawnMessagePayload(identity, ownerWriter, observersWriter); + + var prefabHash = identity.IsPrefab ? identity.PrefabHash : default(int?); + var sceneId = identity.IsSceneObject ? identity.SceneId : default(ulong?); + var msg = new SpawnMessage + { + NetId = identity.NetId, + SceneId = sceneId, + PrefabHash = prefabHash, + Payload = payload, + }; + msg.SpawnValues = CreateSpawnValues(identity); + + // we have to send local/Owner values as their own message. + // but observers can be sent using SendToMany to avoid copying bytes multiple times + using var observersList = AutoPool>.Take(); + var observerPlayers = observersList.Item; + Debug.Assert(observerPlayers.Count == 0); + + foreach (var player in players) + { + if (identity.Owner == player) + { + // send to owner + msg.IsLocalPlayer = player.Identity == identity; + msg.IsOwner = true; + if (hasPayload) + msg.Payload = ownerWriter.ToArraySegment(); + + player.Send(msg); + } + else + { + // add all others players to list and send after + observerPlayers.Add(player); + } + } + + // we only call this function with atleast 2 players, so there should always be observers + Debug.Assert(observerPlayers.Count > 0); + msg.IsLocalPlayer = false; + msg.IsOwner = false; + if (hasPayload) + msg.Payload = observersWriter.ToArraySegment(); + NetworkServer.SendToMany(observerPlayers, msg); + } + } private SpawnValues CreateSpawnValues(NetworkIdentity identity) { @@ -443,25 +532,19 @@ internal void SendRemoveAuthorityMessage(NetworkIdentity identity, INetworkPlaye }); } - private static ArraySegment CreateSpawnMessagePayload(bool isOwner, NetworkIdentity identity, PooledNetworkWriter ownerWriter, PooledNetworkWriter observersWriter) + private static bool CreateSpawnMessagePayload(NetworkIdentity identity, PooledNetworkWriter ownerWriter, PooledNetworkWriter observersWriter) { // Only call OnSerializeAllSafely if there are NetworkBehaviours if (identity.NetworkBehaviours.Length == 0) { - return default; + return false; } // serialize all components with initialState = true // (can be null if has none) identity.OnSerializeAll(true, ownerWriter, observersWriter); - // use owner segment if 'conn' owns this identity, otherwise - // use observers segment - var payload = isOwner ? - ownerWriter.ToArraySegment() : - observersWriter.ToArraySegment(); - - return payload; + return true; } ///