123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- // 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<PooledNetworkWriter> batches = new Queue<PooledNetworkWriter>();
- 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<byte> 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
- // <<size, message, size, message, ...>>
- // but to save A LOT of bandwidth, we use
- // <<message, message, ...>
- // 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;
- }
- }
- }
|