mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 11:00: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
|
// unpack message
|
||||||
using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(buffer))
|
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))
|
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.");
|
Debug.LogError("Closed connection: " + this + ". Invalid message header.");
|
||||||
Disconnect();
|
Disconnect();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,40 @@ namespace Mirror
|
|||||||
{
|
{
|
||||||
public class NetworkConnectionToClient : NetworkConnection
|
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);
|
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)
|
internal override bool Send(ArraySegment<byte> segment, int channelId = Channels.DefaultReliable)
|
||||||
{
|
{
|
||||||
//Debug.Log("ConnectionSend " + this + " bytes:" + BitConverter.ToString(segment.Array, segment.Offset, segment.Count));
|
//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.
|
// validate packet size first.
|
||||||
if (ValidatePacketSize(segment, channelId))
|
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;
|
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>
|
/// <summary>
|
||||||
/// Disconnects this connection.
|
/// Disconnects this connection.
|
||||||
/// </summary>
|
/// </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.")]
|
[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;
|
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
|
// transport layer
|
||||||
[Header("Network Info")]
|
[Header("Network Info")]
|
||||||
[Tooltip("Transport component attached to this object that server and client will use to connect")]
|
[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);
|
authenticator.OnServerAuthenticated.AddListener(OnServerAuthenticated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NetworkServer.batchInterval = serverBatchInterval;
|
||||||
|
|
||||||
ConfigureServerFrameRate();
|
ConfigureServerFrameRate();
|
||||||
|
|
||||||
// start listening to network connections
|
// start listening to network connections
|
||||||
|
@ -46,6 +46,12 @@ public static class NetworkServer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool active { get; internal set; }
|
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>
|
/// <summary>
|
||||||
/// This shuts down the server and disconnects all clients.
|
/// This shuts down the server and disconnects all clients.
|
||||||
/// </summary>
|
/// </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.");
|
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)
|
static void OnConnected(int connectionId)
|
||||||
@ -412,7 +424,7 @@ static void OnConnected(int connectionId)
|
|||||||
if (connections.Count < maxConnections)
|
if (connections.Count < maxConnections)
|
||||||
{
|
{
|
||||||
// add connection
|
// add connection
|
||||||
NetworkConnectionToClient conn = new NetworkConnectionToClient(connectionId);
|
NetworkConnectionToClient conn = new NetworkConnectionToClient(connectionId, batchInterval);
|
||||||
OnConnected(conn);
|
OnConnected(conn);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -4,7 +4,7 @@ namespace Mirror.Tests
|
|||||||
{
|
{
|
||||||
public class FakeNetworkConnection : NetworkConnectionToClient
|
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
|
// creates .observers and generates a netId
|
||||||
identity.OnStartServer();
|
identity.OnStartServer();
|
||||||
uint netId = identity.netId;
|
uint netId = identity.netId;
|
||||||
identity.connectionToClient = new NetworkConnectionToClient(1);
|
identity.connectionToClient = new NetworkConnectionToClient(1, 0);
|
||||||
identity.connectionToServer = new NetworkConnectionToServer();
|
identity.connectionToServer = new NetworkConnectionToServer();
|
||||||
identity.observers.Add(new NetworkConnectionToClient(2));
|
identity.observers.Add(new NetworkConnectionToClient(2, 0));
|
||||||
|
|
||||||
// mark for reset and reset
|
// mark for reset and reset
|
||||||
identity.Reset();
|
identity.Reset();
|
||||||
@ -882,7 +882,7 @@ public void HandleCommand()
|
|||||||
{
|
{
|
||||||
// add component
|
// add component
|
||||||
CommandTestNetworkBehaviour comp0 = gameObject.AddComponent<CommandTestNetworkBehaviour>();
|
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.called, Is.EqualTo(0));
|
||||||
Assert.That(comp0.senderConnectionInCall, Is.Null);
|
Assert.That(comp0.senderConnectionInCall, Is.Null);
|
||||||
|
|
||||||
|
@ -256,7 +256,7 @@ public void AddConnectionTest()
|
|||||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
|
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
|
||||||
|
|
||||||
// add first connection
|
// add first connection
|
||||||
NetworkConnectionToClient conn42 = new NetworkConnectionToClient(42);
|
NetworkConnectionToClient conn42 = new NetworkConnectionToClient(42, 0);
|
||||||
bool result42 = NetworkServer.AddConnection(conn42);
|
bool result42 = NetworkServer.AddConnection(conn42);
|
||||||
Assert.That(result42, Is.True);
|
Assert.That(result42, Is.True);
|
||||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(1));
|
Assert.That(NetworkServer.connections.Count, Is.EqualTo(1));
|
||||||
@ -264,7 +264,7 @@ public void AddConnectionTest()
|
|||||||
Assert.That(NetworkServer.connections[42], Is.EqualTo(conn42));
|
Assert.That(NetworkServer.connections[42], Is.EqualTo(conn42));
|
||||||
|
|
||||||
// add second connection
|
// add second connection
|
||||||
NetworkConnectionToClient conn43 = new NetworkConnectionToClient(43);
|
NetworkConnectionToClient conn43 = new NetworkConnectionToClient(43, 0);
|
||||||
bool result43 = NetworkServer.AddConnection(conn43);
|
bool result43 = NetworkServer.AddConnection(conn43);
|
||||||
Assert.That(result43, Is.True);
|
Assert.That(result43, Is.True);
|
||||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(2));
|
Assert.That(NetworkServer.connections.Count, Is.EqualTo(2));
|
||||||
@ -274,7 +274,7 @@ public void AddConnectionTest()
|
|||||||
Assert.That(NetworkServer.connections[43], Is.EqualTo(conn43));
|
Assert.That(NetworkServer.connections[43], Is.EqualTo(conn43));
|
||||||
|
|
||||||
// add duplicate connectionId
|
// add duplicate connectionId
|
||||||
NetworkConnectionToClient connDup = new NetworkConnectionToClient(42);
|
NetworkConnectionToClient connDup = new NetworkConnectionToClient(42, 0);
|
||||||
bool resultDup = NetworkServer.AddConnection(connDup);
|
bool resultDup = NetworkServer.AddConnection(connDup);
|
||||||
Assert.That(resultDup, Is.False);
|
Assert.That(resultDup, Is.False);
|
||||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(2));
|
Assert.That(NetworkServer.connections.Count, Is.EqualTo(2));
|
||||||
@ -297,7 +297,7 @@ public void RemoveConnectionTest()
|
|||||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
|
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
|
||||||
|
|
||||||
// add connection
|
// add connection
|
||||||
NetworkConnectionToClient conn42 = new NetworkConnectionToClient(42);
|
NetworkConnectionToClient conn42 = new NetworkConnectionToClient(42, 0);
|
||||||
bool result42 = NetworkServer.AddConnection(conn42);
|
bool result42 = NetworkServer.AddConnection(conn42);
|
||||||
Assert.That(result42, Is.True);
|
Assert.That(result42, Is.True);
|
||||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(1));
|
Assert.That(NetworkServer.connections.Count, Is.EqualTo(1));
|
||||||
@ -323,7 +323,7 @@ public void DisconnectAllConnectionsTest()
|
|||||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
|
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
|
||||||
|
|
||||||
// add connection
|
// add connection
|
||||||
NetworkConnectionToClient conn42 = new NetworkConnectionToClient(42);
|
NetworkConnectionToClient conn42 = new NetworkConnectionToClient(42, 0);
|
||||||
NetworkServer.AddConnection(conn42);
|
NetworkServer.AddConnection(conn42);
|
||||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(1));
|
Assert.That(NetworkServer.connections.Count, Is.EqualTo(1));
|
||||||
|
|
||||||
@ -356,7 +356,7 @@ public void OnDataReceivedTest()
|
|||||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
|
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
|
||||||
|
|
||||||
// add a connection
|
// add a connection
|
||||||
NetworkConnectionToClient connection = new NetworkConnectionToClient(42);
|
NetworkConnectionToClient connection = new NetworkConnectionToClient(42, 0);
|
||||||
NetworkServer.AddConnection(connection);
|
NetworkServer.AddConnection(connection);
|
||||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(1));
|
Assert.That(NetworkServer.connections.Count, Is.EqualTo(1));
|
||||||
|
|
||||||
@ -440,7 +440,7 @@ public void RegisterUnregisterClearHandlerTest()
|
|||||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
|
Assert.That(NetworkServer.connections.Count, Is.EqualTo(0));
|
||||||
|
|
||||||
// add a connection
|
// add a connection
|
||||||
NetworkConnectionToClient connection = new NetworkConnectionToClient(42);
|
NetworkConnectionToClient connection = new NetworkConnectionToClient(42, 0);
|
||||||
NetworkServer.AddConnection(connection);
|
NetworkServer.AddConnection(connection);
|
||||||
Assert.That(NetworkServer.connections.Count, Is.EqualTo(1));
|
Assert.That(NetworkServer.connections.Count, Is.EqualTo(1));
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ public void TearDown()
|
|||||||
public IEnumerator DestroyPlayerForConnectionTest()
|
public IEnumerator DestroyPlayerForConnectionTest()
|
||||||
{
|
{
|
||||||
GameObject player = new GameObject("testPlayer", typeof(NetworkIdentity));
|
GameObject player = new GameObject("testPlayer", typeof(NetworkIdentity));
|
||||||
NetworkConnectionToClient conn = new NetworkConnectionToClient(1);
|
NetworkConnectionToClient conn = new NetworkConnectionToClient(1, 0);
|
||||||
|
|
||||||
NetworkServer.AddPlayerForConnection(conn, player);
|
NetworkServer.AddPlayerForConnection(conn, player);
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ public IEnumerator DestroyPlayerForConnectionTest()
|
|||||||
public IEnumerator RemovePlayerForConnectionTest()
|
public IEnumerator RemovePlayerForConnectionTest()
|
||||||
{
|
{
|
||||||
GameObject player = new GameObject("testPlayer", typeof(NetworkIdentity));
|
GameObject player = new GameObject("testPlayer", typeof(NetworkIdentity));
|
||||||
NetworkConnectionToClient conn = new NetworkConnectionToClient(1);
|
NetworkConnectionToClient conn = new NetworkConnectionToClient(1, 0);
|
||||||
|
|
||||||
NetworkServer.AddPlayerForConnection(conn, player);
|
NetworkServer.AddPlayerForConnection(conn, player);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user