From e0e2c3627c97691fee57fe43b9b11e28be8679c1 Mon Sep 17 00:00:00 2001 From: Paul Pacheco Date: Wed, 17 Mar 2021 16:56:05 -0500 Subject: [PATCH] perf: allocation free send to observers --- .../GlobalInterestManager.cs | 10 ++++ .../InterestManagement/InterestManager.cs | 32 ++++++++++++ Assets/Mirage/Runtime/NetworkIdentity.cs | 51 ++++++++++++++----- 3 files changed, 79 insertions(+), 14 deletions(-) diff --git a/Assets/Mirage/Runtime/InterestManagement/GlobalInterestManager.cs b/Assets/Mirage/Runtime/InterestManagement/GlobalInterestManager.cs index 02e5fa0e44d..47d18db7071 100644 --- a/Assets/Mirage/Runtime/InterestManagement/GlobalInterestManager.cs +++ b/Assets/Mirage/Runtime/InterestManagement/GlobalInterestManager.cs @@ -7,6 +7,16 @@ namespace Mirage.InterestManagement /// public class GlobalInterestManager : InterestManager { + public override void ForEach(NetworkIdentity identity, T action) + { + // avoids allocations by looping directly on the HashSet + // which avoids boxing the IEnumerator + foreach (INetworkPlayer player in ServerObjectManager.Server.Players) + { + action.Run(player); + } + } + public override IReadOnlyCollection Observers(NetworkIdentity identity) { return ServerObjectManager.Server.Players; diff --git a/Assets/Mirage/Runtime/InterestManagement/InterestManager.cs b/Assets/Mirage/Runtime/InterestManagement/InterestManager.cs index 7c0c3ca8237..322ca43090e 100644 --- a/Assets/Mirage/Runtime/InterestManagement/InterestManager.cs +++ b/Assets/Mirage/Runtime/InterestManagement/InterestManager.cs @@ -13,6 +13,19 @@ namespace Mirage.InterestManagement /// public abstract class InterestManager : MonoBehaviour { + + /// + /// Action to execute per observer. + /// This is equivalent to + /// but it allows for creating an allocation free "delegate" + /// This is used in the hot path. + /// + public interface PlayerAction + { + void Run(INetworkPlayer player); + } + + public ServerObjectManager ServerObjectManager; public void Start() @@ -29,6 +42,25 @@ public void Start() server.Authenticated.AddListener(OnAuthenticated); } + /// + /// Executes an action for every player that can observe an object + /// allocation free. + /// + /// The action type + /// The object that we are observing + /// The action to execute for every observer + public virtual void ForEach(NetworkIdentity identity, T action) where T : struct, PlayerAction + { + // this is the default implementation which works, but will allocate + // by boxing the IEnumerator. + // implementations of InterestManager should override this method + // and provide an allocation free alternative. + foreach (INetworkPlayer player in Observers(identity)) + { + action.Run(player); + } + } + /// /// Invoked when a player joins the server /// It should show all objects relevant to that player diff --git a/Assets/Mirage/Runtime/NetworkIdentity.cs b/Assets/Mirage/Runtime/NetworkIdentity.cs index 84d650a749d..e6f7cf7ab2d 100644 --- a/Assets/Mirage/Runtime/NetworkIdentity.cs +++ b/Assets/Mirage/Runtime/NetworkIdentity.cs @@ -1040,6 +1040,28 @@ void SendUpdateVarsMessage() static readonly List connectionsExcludeSelf = new List(100); + + // this is basically a delegate for player.Send, + // but it is allocation free + readonly struct SendAction : InterestManager.PlayerAction + { + private readonly ArraySegment data; + private readonly int channel; + private readonly INetworkPlayer owner; + + internal SendAction(ArraySegment data, int channel, INetworkPlayer owner) + { + this.data = data; + this.channel = channel; + this.owner = owner; + } + public void Run(INetworkPlayer player) + { + if (player != owner) + player.Send(data, channel); + } + } + /// /// this is like SendToReady - but it doesn't check the ready flag on the connection. /// this is used for ObjectDestroy messages. @@ -1055,21 +1077,22 @@ internal void SendToObservers(T msg, bool includeOwner = true, int channelId if (Observers.Count == 0) return; - if (includeOwner) - { - NetworkServer.SendToMany(Observers, msg, channelId); - } - else + using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) { - connectionsExcludeSelf.Clear(); - foreach (INetworkPlayer player in Observers) - { - if (ConnectionToClient != player) - { - connectionsExcludeSelf.Add(player); - } - } - NetworkServer.SendToMany(connectionsExcludeSelf, msg, channelId); + // pack message into byte[] once + MessagePacker.Pack(msg, writer); + var segment = writer.ToArraySegment(); + int count = Observers.Count; + + InterestManager interestManager = ServerObjectManager.InterestManager; + + // just a delegate to call send, but allocation free + SendAction action = new SendAction(segment, channelId, includeOwner ? null : ConnectionToClient); + + + interestManager.ForEach(this, action); + + NetworkDiagnostics.OnSend(msg, channelId, segment.Count, count); } }