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