Unbatcher.cs 5.1 KB

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