mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
perf: Batching. Mirror will batch up to Transport.GetMaxPacketSize messages into one batch and then send them all at once. Reduces transport calls by a factor of ~1000x assuming average of 64 byte message with Telepathy 64kb MaxMessageSize.
This commit is contained in:
parent
fa53fa60f8
commit
4b9a731fc3
@ -213,6 +213,10 @@ internal void TransportReceive(ArraySegment<byte> buffer)
|
||||
{
|
||||
// unpack message
|
||||
using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(buffer))
|
||||
{
|
||||
// the other end might batch multiple messages into one packet.
|
||||
// we need to try to unpack multiple times.
|
||||
while (networkReader.Remaining > 0)
|
||||
{
|
||||
if (MessagePacker.UnpackMessage(networkReader, out int msgType))
|
||||
{
|
||||
@ -226,6 +230,8 @@ internal void TransportReceive(ArraySegment<byte> buffer)
|
||||
{
|
||||
Debug.LogError("Closed connection: " + this + ". Invalid message header.");
|
||||
Disconnect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,10 +4,40 @@ namespace Mirror
|
||||
{
|
||||
public class NetworkConnectionToClient : NetworkConnection
|
||||
{
|
||||
public NetworkConnectionToClient(int networkConnectionId) : base(networkConnectionId) { }
|
||||
// batching from server to client.
|
||||
// fewer transport calls give us significantly better performance/scale.
|
||||
//
|
||||
// for a 64KB max message transport and 64 bytes/message on average, we
|
||||
// reduce transport calls by a factor of 1000.
|
||||
//
|
||||
// depending on the transport, this can give 10x performance.
|
||||
NetworkWriter batch = new NetworkWriter();
|
||||
|
||||
// batch interval is 0 by default, meaning that we send immediately.
|
||||
// (useful to run tests without waiting for intervals too)
|
||||
float batchInterval;
|
||||
double batchSendTime;
|
||||
|
||||
public NetworkConnectionToClient(int networkConnectionId, float batchInterval) : base(networkConnectionId)
|
||||
{
|
||||
this.batchInterval = batchInterval;
|
||||
}
|
||||
|
||||
public override string address => Transport.activeTransport.ServerGetClientAddress(connectionId);
|
||||
|
||||
void SendBatch(int channelId)
|
||||
{
|
||||
// send batch
|
||||
Transport.activeTransport.ServerSend(connectionId, channelId, batch.ToArraySegment());
|
||||
|
||||
// clear batch
|
||||
batch.Position = 0;
|
||||
|
||||
// reset send time
|
||||
// (use NetworkTime for maximum precision over days)
|
||||
batchSendTime = NetworkTime.time;
|
||||
}
|
||||
|
||||
internal override bool Send(ArraySegment<byte> segment, int channelId = Channels.DefaultReliable)
|
||||
{
|
||||
//Debug.Log("ConnectionSend " + this + " bytes:" + BitConverter.ToString(segment.Array, segment.Offset, segment.Count));
|
||||
@ -15,11 +45,45 @@ internal override bool Send(ArraySegment<byte> segment, int channelId = Channels
|
||||
// validate packet size first.
|
||||
if (ValidatePacketSize(segment, channelId))
|
||||
{
|
||||
return Transport.activeTransport.ServerSend(connectionId, channelId, segment);
|
||||
// batching?
|
||||
if (batchInterval > 0)
|
||||
{
|
||||
// if batch would become bigger than MaxPacketSize then send
|
||||
// out the previous batch first
|
||||
// TODO respect channelId
|
||||
int max = Transport.activeTransport.GetMaxPacketSize(Channels.DefaultReliable);
|
||||
if (batch.Position + segment.Count > max)
|
||||
{
|
||||
// TODO respect channelId
|
||||
//Debug.LogWarning($"sending batch {batch.Position} / {max} after full for segment={segment.Count} for connectionId={connectionId}");
|
||||
SendBatch(Channels.DefaultReliable);
|
||||
}
|
||||
|
||||
// now add segment to batch
|
||||
batch.WriteBytes(segment.Array, segment.Offset, segment.Count);
|
||||
return true;
|
||||
}
|
||||
// otherwise send directly (for tests etc.)
|
||||
else return Transport.activeTransport.ServerSend(connectionId, channelId, segment);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// flush batched messages every batchInterval to make sure that they are
|
||||
// sent out every now and then, even if the batch isn't full yet.
|
||||
// (avoids 30s latency if batches would only get full every 30s)
|
||||
internal void Update()
|
||||
{
|
||||
// enough time elapsed and anything batched?
|
||||
if (NetworkTime.time > batchSendTime + batchInterval &&
|
||||
batch.Position > 0)
|
||||
{
|
||||
// TODO respect channelId
|
||||
//Debug.LogWarning($"sending batch of {batch.Position} bytes after time for connId= {connectionId}");
|
||||
SendBatch(Channels.DefaultReliable);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects this connection.
|
||||
/// </summary>
|
||||
|
@ -62,6 +62,13 @@ public class NetworkManager : MonoBehaviour
|
||||
[Tooltip("Server Update frequency, per second. Use around 60Hz for fast paced games like Counter-Strike to minimize latency. Use around 30Hz for games like WoW to minimize computations. Use around 1-10Hz for slow paced games like EVE.")]
|
||||
public int serverTickRate = 30;
|
||||
|
||||
// batching from server to client.
|
||||
// fewer transport calls give us significantly better performance/scale.
|
||||
// batch interval is 0 by default, meaning that we send immediately.
|
||||
// (useful to run tests without waiting for intervals too)
|
||||
[Tooltip("Server can batch messages up to Transport.GetMaxPacketSize to significantly reduce transport calls and improve performance/scale./nNote that this increases latency./n0 means send immediately.")]
|
||||
public float serverBatchInterval = 0.010f;
|
||||
|
||||
// transport layer
|
||||
[Header("Network Info")]
|
||||
[Tooltip("Transport component attached to this object that server and client will use to connect")]
|
||||
@ -261,6 +268,8 @@ void SetupServer()
|
||||
authenticator.OnServerAuthenticated.AddListener(OnServerAuthenticated);
|
||||
}
|
||||
|
||||
NetworkServer.batchInterval = serverBatchInterval;
|
||||
|
||||
ConfigureServerFrameRate();
|
||||
|
||||
// start listening to network connections
|
||||
|
@ -46,6 +46,12 @@ public static class NetworkServer
|
||||
/// </summary>
|
||||
public static bool active { get; internal set; }
|
||||
|
||||
// batching from server to client.
|
||||
// fewer transport calls give us significantly better performance/scale.
|
||||
// batch interval is 0 by default, meaning that we send immediately.
|
||||
// (useful to run tests without waiting for intervals too)
|
||||
public static float batchInterval = 0;
|
||||
|
||||
/// <summary>
|
||||
/// This shuts down the server and disconnects all clients.
|
||||
/// </summary>
|
||||
@ -382,6 +388,12 @@ public static void Update()
|
||||
Debug.LogWarning("Found 'null' entry in spawned list for netId=" + kvp.Key + ". Please call NetworkServer.Destroy to destroy networked objects. Don't use GameObject.Destroy.");
|
||||
}
|
||||
}
|
||||
|
||||
// update all connections to send out batched messages in interval
|
||||
foreach (NetworkConnectionToClient conn in connections.Values)
|
||||
{
|
||||
conn.Update();
|
||||
}
|
||||
}
|
||||
|
||||
static void OnConnected(int connectionId)
|
||||
@ -412,7 +424,7 @@ static void OnConnected(int connectionId)
|
||||
if (connections.Count < maxConnections)
|
||||
{
|
||||
// add connection
|
||||
NetworkConnectionToClient conn = new NetworkConnectionToClient(connectionId);
|
||||
NetworkConnectionToClient conn = new NetworkConnectionToClient(connectionId, batchInterval);
|
||||
OnConnected(conn);
|
||||
}
|
||||
else
|
||||
|
@ -4,7 +4,7 @@ namespace Mirror.Tests
|
||||
{
|
||||
public class FakeNetworkConnection : NetworkConnectionToClient
|
||||
{
|
||||
public FakeNetworkConnection() : base(1)
|
||||
public FakeNetworkConnection() : base(1, 0)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -865,9 +865,9 @@ public void Reset()
|
||||
// creates .observers and generates a netId
|
||||
identity.OnStartServer();
|
||||
uint netId = identity.netId;
|
||||
identity.connectionToClient = new NetworkConnectionToClient(1);
|
||||
identity.connectionToClient = new NetworkConnectionToClient(1, 0);
|
||||
identity.connectionToServer = new NetworkConnectionToServer();
|
||||
identity.observers.Add(new NetworkConnectionToClient(2));
|
||||
identity.observers.Add(new NetworkConnectionToClient(2, 0));
|
||||
|
||||
// mark for reset and reset
|
||||
identity.Reset();
|
||||
@ -882,7 +882,7 @@ public void HandleCommand()
|
||||
{
|
||||
// add component
|
||||
CommandTestNetworkBehaviour comp0 = gameObject.AddComponent<CommandTestNetworkBehaviour>();
|
||||
NetworkConnectionToClient connection = new NetworkConnectionToClient(1);
|
||||
NetworkConnectionToClient connection = new NetworkConnectionToClient(1, 0);
|
||||
Assert.That(comp0.called, Is.EqualTo(0));
|
||||
Assert.That(comp0.senderConnectionInCall, Is.Null);
|
||||
|
||||
|
@ -256,7 +256,7 @@ public void AddConnectionTest()
|
||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
|
||||
|
||||
// add first connection
|
||||
NetworkConnectionToClient conn42 = new NetworkConnectionToClient(42);
|
||||
NetworkConnectionToClient conn42 = new NetworkConnectionToClient(42, 0);
|
||||
bool result42 = NetworkServer.AddConnection(conn42);
|
||||
Assert.That(result42, Is.True);
|
||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(1));
|
||||
@ -264,7 +264,7 @@ public void AddConnectionTest()
|
||||
Assert.That(NetworkServer.connections[42], Is.EqualTo(conn42));
|
||||
|
||||
// add second connection
|
||||
NetworkConnectionToClient conn43 = new NetworkConnectionToClient(43);
|
||||
NetworkConnectionToClient conn43 = new NetworkConnectionToClient(43, 0);
|
||||
bool result43 = NetworkServer.AddConnection(conn43);
|
||||
Assert.That(result43, Is.True);
|
||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(2));
|
||||
@ -274,7 +274,7 @@ public void AddConnectionTest()
|
||||
Assert.That(NetworkServer.connections[43], Is.EqualTo(conn43));
|
||||
|
||||
// add duplicate connectionId
|
||||
NetworkConnectionToClient connDup = new NetworkConnectionToClient(42);
|
||||
NetworkConnectionToClient connDup = new NetworkConnectionToClient(42, 0);
|
||||
bool resultDup = NetworkServer.AddConnection(connDup);
|
||||
Assert.That(resultDup, Is.False);
|
||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(2));
|
||||
@ -297,7 +297,7 @@ public void RemoveConnectionTest()
|
||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
|
||||
|
||||
// add connection
|
||||
NetworkConnectionToClient conn42 = new NetworkConnectionToClient(42);
|
||||
NetworkConnectionToClient conn42 = new NetworkConnectionToClient(42, 0);
|
||||
bool result42 = NetworkServer.AddConnection(conn42);
|
||||
Assert.That(result42, Is.True);
|
||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(1));
|
||||
@ -323,7 +323,7 @@ public void DisconnectAllConnectionsTest()
|
||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
|
||||
|
||||
// add connection
|
||||
NetworkConnectionToClient conn42 = new NetworkConnectionToClient(42);
|
||||
NetworkConnectionToClient conn42 = new NetworkConnectionToClient(42, 0);
|
||||
NetworkServer.AddConnection(conn42);
|
||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(1));
|
||||
|
||||
@ -356,7 +356,7 @@ public void OnDataReceivedTest()
|
||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
|
||||
|
||||
// add a connection
|
||||
NetworkConnectionToClient connection = new NetworkConnectionToClient(42);
|
||||
NetworkConnectionToClient connection = new NetworkConnectionToClient(42, 0);
|
||||
NetworkServer.AddConnection(connection);
|
||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(1));
|
||||
|
||||
@ -440,7 +440,7 @@ public void RegisterUnregisterClearHandlerTest()
|
||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
|
||||
|
||||
// add a connection
|
||||
NetworkConnectionToClient connection = new NetworkConnectionToClient(42);
|
||||
NetworkConnectionToClient connection = new NetworkConnectionToClient(42, 0);
|
||||
NetworkServer.AddConnection(connection);
|
||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(1));
|
||||
|
||||
|
@ -36,7 +36,7 @@ public void TearDown()
|
||||
public IEnumerator DestroyPlayerForConnectionTest()
|
||||
{
|
||||
GameObject player = new GameObject("testPlayer", typeof(NetworkIdentity));
|
||||
NetworkConnectionToClient conn = new NetworkConnectionToClient(1);
|
||||
NetworkConnectionToClient conn = new NetworkConnectionToClient(1, 0);
|
||||
|
||||
NetworkServer.AddPlayerForConnection(conn, player);
|
||||
|
||||
@ -55,7 +55,7 @@ public IEnumerator DestroyPlayerForConnectionTest()
|
||||
public IEnumerator RemovePlayerForConnectionTest()
|
||||
{
|
||||
GameObject player = new GameObject("testPlayer", typeof(NetworkIdentity));
|
||||
NetworkConnectionToClient conn = new NetworkConnectionToClient(1);
|
||||
NetworkConnectionToClient conn = new NetworkConnectionToClient(1, 0);
|
||||
|
||||
NetworkServer.AddPlayerForConnection(conn, player);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user