Unbatcher.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. // un-batching functionality encapsulated into one class.
  2. // -> less complexity
  3. // -> easy to test
  4. //
  5. // includes timestamp for tick batching.
  6. // -> allows NetworkTransform etc. to use timestamp without including it in
  7. // every single message
  8. using System;
  9. using System.Collections.Generic;
  10. namespace Mirror
  11. {
  12. public class Unbatcher
  13. {
  14. // supporting adding multiple batches before GetNextMessage is called.
  15. // just in case.
  16. Queue<PooledNetworkWriter> batches = new Queue<PooledNetworkWriter>();
  17. public int BatchesCount => batches.Count;
  18. // NetworkReader is only created once,
  19. // then pointed to the first batch.
  20. NetworkReader reader = new NetworkReader(new byte[0]);
  21. // timestamp that was written into the batch remotely.
  22. // for the batch that our reader is currently pointed at.
  23. double readerRemoteTimeStamp;
  24. // helper function to start reading a batch.
  25. void StartReadingBatch(PooledNetworkWriter batch)
  26. {
  27. // point reader to it
  28. reader.SetBuffer(batch.ToArraySegment());
  29. // read remote timestamp (double)
  30. // -> AddBatch quarantees that we have at least 8 bytes to read
  31. readerRemoteTimeStamp = reader.ReadDouble();
  32. }
  33. // add a new batch.
  34. // returns true if valid.
  35. // returns false if not, in which case the connection should be disconnected.
  36. public bool AddBatch(ArraySegment<byte> batch)
  37. {
  38. // IMPORTANT: ArraySegment is only valid until returning. we copy it!
  39. //
  40. // NOTE: it's not possible to create empty ArraySegments, so we
  41. // don't need to check against that.
  42. // make sure we have at least 8 bytes to read for tick timestamp
  43. if (batch.Count < Batcher.HeaderSize)
  44. return false;
  45. // put into a (pooled) writer
  46. // -> WriteBytes instead of WriteSegment because the latter
  47. // would add a size header. we want to write directly.
  48. // -> will be returned to pool when sending!
  49. PooledNetworkWriter writer = NetworkWriterPool.GetWriter();
  50. writer.WriteBytes(batch.Array, batch.Offset, batch.Count);
  51. // first batch? then point reader there
  52. if (batches.Count == 0)
  53. StartReadingBatch(writer);
  54. // add batch
  55. batches.Enqueue(writer);
  56. //Debug.Log($"Adding Batch {BitConverter.ToString(batch.Array, batch.Offset, batch.Count)} => batches={batches.Count} reader={reader}");
  57. return true;
  58. }
  59. // get next message, unpacked from batch (if any)
  60. // timestamp is the REMOTE time when the batch was created remotely.
  61. public bool GetNextMessage(out NetworkReader message, out double remoteTimeStamp)
  62. {
  63. // getting messages would be easy via
  64. // <<size, message, size, message, ...>>
  65. // but to save A LOT of bandwidth, we use
  66. // <<message, message, ...>
  67. // in other words, we don't know where the current message ends
  68. //
  69. // BUT: it doesn't matter!
  70. // -> we simply return the reader
  71. // * if we have one yet
  72. // * and if there's more to read
  73. // -> the caller can then read one message from it
  74. // -> when the end is reached, we retire the batch!
  75. //
  76. // for example:
  77. // while (GetNextMessage(out message))
  78. // ProcessMessage(message);
  79. //
  80. message = null;
  81. // do nothing if we don't have any batches.
  82. // otherwise the below queue.Dequeue() would throw an
  83. // InvalidOperationException if operating on empty queue.
  84. if (batches.Count == 0)
  85. {
  86. remoteTimeStamp = 0;
  87. return false;
  88. }
  89. // was our reader pointed to anything yet?
  90. if (reader.Length == 0)
  91. {
  92. remoteTimeStamp = 0;
  93. return false;
  94. }
  95. // no more data to read?
  96. if (reader.Remaining == 0)
  97. {
  98. // retire the batch
  99. PooledNetworkWriter writer = batches.Dequeue();
  100. NetworkWriterPool.Recycle(writer);
  101. // do we have another batch?
  102. if (batches.Count > 0)
  103. {
  104. // point reader to the next batch.
  105. // we'll return the reader below.
  106. PooledNetworkWriter next = batches.Peek();
  107. StartReadingBatch(next);
  108. }
  109. // otherwise there's nothing more to read
  110. else
  111. {
  112. remoteTimeStamp = 0;
  113. return false;
  114. }
  115. }
  116. // use the current batch's remote timestamp
  117. // AFTER potentially moving to the next batch ABOVE!
  118. remoteTimeStamp = readerRemoteTimeStamp;
  119. // if we got here, then we have more data to read.
  120. message = reader;
  121. return true;
  122. }
  123. }
  124. }