// un-batching functionality encapsulated into one class. // -> less complexity // -> easy to test // // includes timestamp for tick batching. // -> allows NetworkTransform etc. to use timestamp without including it in // every single message using System; using System.Collections.Generic; namespace Mirror { public class Unbatcher { // supporting adding multiple batches before GetNextMessage is called. // just in case. Queue batches = new Queue(); public int BatchesCount => batches.Count; // NetworkReader is only created once, // then pointed to the first batch. NetworkReader reader = new NetworkReader(new byte[0]); // timestamp that was written into the batch remotely. // for the batch that our reader is currently pointed at. double readerRemoteTimeStamp; // helper function to start reading a batch. void StartReadingBatch(PooledNetworkWriter batch) { // point reader to it reader.SetBuffer(batch.ToArraySegment()); // read remote timestamp (double) // -> AddBatch quarantees that we have at least 8 bytes to read readerRemoteTimeStamp = reader.ReadDouble(); } // add a new batch. // returns true if valid. // returns false if not, in which case the connection should be disconnected. public bool AddBatch(ArraySegment batch) { // IMPORTANT: ArraySegment is only valid until returning. we copy it! // // NOTE: it's not possible to create empty ArraySegments, so we // don't need to check against that. // make sure we have at least 8 bytes to read for tick timestamp if (batch.Count < Batcher.HeaderSize) return false; // 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(batch.Array, batch.Offset, batch.Count); // first batch? then point reader there if (batches.Count == 0) StartReadingBatch(writer); // add batch batches.Enqueue(writer); //Debug.Log($"Adding Batch {BitConverter.ToString(batch.Array, batch.Offset, batch.Count)} => batches={batches.Count} reader={reader}"); return true; } // get next message, unpacked from batch (if any) // timestamp is the REMOTE time when the batch was created remotely. public bool GetNextMessage(out NetworkReader message, out double remoteTimeStamp) { // getting messages would be easy via // <> // but to save A LOT of bandwidth, we use // < // in other words, we don't know where the current message ends // // BUT: it doesn't matter! // -> we simply return the reader // * if we have one yet // * and if there's more to read // -> the caller can then read one message from it // -> when the end is reached, we retire the batch! // // for example: // while (GetNextMessage(out message)) // ProcessMessage(message); // message = null; // do nothing if we don't have any batches. // otherwise the below queue.Dequeue() would throw an // InvalidOperationException if operating on empty queue. if (batches.Count == 0) { remoteTimeStamp = 0; return false; } // was our reader pointed to anything yet? if (reader.Length == 0) { remoteTimeStamp = 0; return false; } // no more data to read? if (reader.Remaining == 0) { // retire the batch PooledNetworkWriter writer = batches.Dequeue(); NetworkWriterPool.Recycle(writer); // do we have another batch? if (batches.Count > 0) { // point reader to the next batch. // we'll return the reader below. PooledNetworkWriter next = batches.Peek(); StartReadingBatch(next); } // otherwise there's nothing more to read else { remoteTimeStamp = 0; return false; } } // use the current batch's remote timestamp // AFTER potentially moving to the next batch ABOVE! remoteTimeStamp = readerRemoteTimeStamp; // if we got here, then we have more data to read. message = reader; return true; } } }