using System; using System.Collections.Generic; namespace Mirror { public class NetworkConnectionToClient : NetworkConnection { public override string address => Transport.activeTransport.ServerGetClientAddress(connectionId); // 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. // // Dictionary because we have multiple channels. internal class Batch { // batched messages // IMPORTANT: we queue the serialized messages! // queueing NetworkMessage would box and allocate! internal Queue messages = new Queue(); } Dictionary batches = new Dictionary(); // batch messages and send them out in LateUpdate (or after batchInterval) bool batching; public NetworkConnectionToClient(int networkConnectionId, bool batching) : base(networkConnectionId) { this.batching = batching; } // TODO if we only have Reliable/Unreliable, then we could initialize // two batches and avoid this code Batch GetBatchForChannelId(int channelId) { // get existing or create new writer for the channelId Batch batch; if (!batches.TryGetValue(channelId, out batch)) { batch = new Batch(); batches[channelId] = batch; } return batch; } // send a batch. internal so we can test it. internal void SendBatch(int channelId, Batch batch) { // get max batch size for this channel int max = Transport.activeTransport.GetMaxBatchSize(channelId); // we need a writer to merge queued messages into a batch using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) { // for each queued message while (batch.messages.Count > 0) { // get it PooledNetworkWriter message = batch.messages.Dequeue(); ArraySegment segment = message.ToArraySegment(); // IF adding to writer would end up >= MTU then we should // flush first. the goal is to always flush < MTU packets. // // IMPORTANT: if writer is empty and segment is > MTU // (which can happen for large max sized message) // then we would send an empty previous writer. // => don't do that. // => only send if not empty. if (writer.Position > 0 && writer.Position + segment.Count >= max) { // flush & reset writer Transport.activeTransport.ServerSend(connectionId, writer.ToArraySegment(), channelId); writer.Position = 0; } // now add to writer in any case // -> WriteBytes instead of WriteSegment because the latter // would add a size header. we want to write directly. // // NOTE: it's very possible that we add > MTU to writer if // message size is > MTU. // which is fine. next iteration will just flush it. writer.WriteBytes(segment.Array, segment.Offset, segment.Count); // return queued message to pool NetworkWriterPool.Recycle(message); } // done iterating queued messages. // batch might still contain the last message. // send it. if (writer.Position > 0) { Transport.activeTransport.ServerSend(connectionId, writer.ToArraySegment(), channelId); writer.Position = 0; } } } internal override void Send(ArraySegment segment, int channelId = Channels.Reliable) { //Debug.Log("ConnectionSend " + this + " bytes:" + BitConverter.ToString(segment.Array, segment.Offset, segment.Count)); // validate packet size first. if (ValidatePacketSize(segment, channelId)) { // batching? then add to queued messages if (batching) { // put into a (pooled) writer // -> WriteBytes instead of WriteSegment because the latter // would add a size header. we want to write directly. // -> will be returned to pool when sending! PooledNetworkWriter writer = NetworkWriterPool.GetWriter(); writer.WriteBytes(segment.Array, segment.Offset, segment.Count); // add to batch queue Batch batch = GetBatchForChannelId(channelId); batch.messages.Enqueue(writer); } // otherwise send directly to minimize latency else Transport.activeTransport.ServerSend(connectionId, segment, channelId); } } // flush batched messages at the end of every Update. internal void Update() { // batching? if (batching) { // go through batches for all channels foreach (KeyValuePair kvp in batches) { // is this channel's batch not empty? if (kvp.Value.messages.Count > 0) { // send the batch. //Debug.Log($"sending batch of {kvp.Value.writer.Position} bytes for channel={kvp.Key} connId={connectionId}"); SendBatch(kvp.Key, kvp.Value); } } } } /// Disconnects this connection. public override void Disconnect() { // set not ready and handle clientscene disconnect in any case // (might be client or host mode here) isReady = false; Transport.activeTransport.ServerDisconnect(connectionId); // IMPORTANT: NetworkConnection.Disconnect() is NOT called for // voluntary disconnects from the other end. // -> so all 'on disconnect' cleanup code needs to be in // OnTransportDisconnect, where it's called for both voluntary // and involuntary disconnects! RemoveFromObservingsObservers(); } } }