Mirror Networking
Batcher.cs
1// batching functionality encapsulated into one class.
2// -> less complexity
3// -> easy to test
4//
5// IMPORTANT: we use THRESHOLD batching, not MAXED SIZE batching.
6// see threshold comments below.
7//
8// includes timestamp for tick batching.
9// -> allows NetworkTransform etc. to use timestamp without including it in
10// every single message
11using System;
12using System.Collections.Generic;
13
14namespace Mirror
15{
16 public class Batcher
17 {
18 // batching threshold instead of max size.
19 // -> small messages are fit into threshold sized batches
20 // -> messages larger than threshold are single batches
21 //
22 // in other words, we fit up to 'threshold' but still allow larger ones
23 // for two reasons:
24 // 1.) data races: skipping batching for larger messages would send a
25 // large spawn message immediately, while others are batched and
26 // only flushed at the end of the frame
27 // 2) timestamp batching: if each batch is expected to contain a
28 // timestamp, then large messages have to be a batch too. otherwise
29 // they would not contain a timestamp
30 readonly int threshold;
31
32 // TimeStamp header size for those who need it
33 public const int HeaderSize = sizeof(double);
34
35 // full batches ready to be sent.
36 // DO NOT queue NetworkMessage, it would box.
37 // DO NOT queue each serialization separately.
38 // it would allocate too many writers.
39 // https://github.com/vis2k/Mirror/pull/3127
40 // => best to build batches on the fly.
41 Queue<NetworkWriterPooled> batches = new Queue<NetworkWriterPooled>();
42
43 // current batch in progress
45
46 public Batcher(int threshold)
47 {
48 this.threshold = threshold;
49 }
50
51 // add a message for batching
52 // we allow any sized messages.
53 // caller needs to make sure they are within max packet size.
54 public void AddMessage(ArraySegment<byte> message, double timeStamp)
55 {
56 // when appending to a batch in progress, check final size.
57 // if it expands beyond threshold, then we should finalize it first.
58 // => less than or exactly threshold is fine.
59 // GetBatch() will finalize it.
60 // => see unit tests.
61 if (batch != null &&
62 batch.Position + message.Count > threshold)
63 {
64 batches.Enqueue(batch);
65 batch = null;
66 }
67
68 // initialize a new batch if necessary
69 if (batch == null)
70 {
71 // borrow from pool. we return it in GetBatch.
72 batch = NetworkWriterPool.Get();
73
74 // write timestamp first.
75 // -> double precision for accuracy over long periods of time
76 // -> batches are per-frame, it doesn't matter which message's
77 // timestamp we use.
78 batch.WriteDouble(timeStamp);
79 }
80
81 // add serialization to current batch. even if > threshold.
82 // -> we do allow > threshold sized messages as single batch
83 // -> WriteBytes instead of WriteSegment because the latter
84 // would add a size header. we want to write directly.
85 batch.WriteBytes(message.Array, message.Offset, message.Count);
86 }
87
88 // helper function to copy a batch to writer and return it to pool
89 static void CopyAndReturn(NetworkWriterPooled batch, NetworkWriter writer)
90 {
91 // make sure the writer is fresh to avoid uncertain situations
92 if (writer.Position != 0)
93 throw new ArgumentException($"GetBatch needs a fresh writer!");
94
95 // copy to the target writer
96 ArraySegment<byte> segment = batch.ToArraySegment();
97 writer.WriteBytes(segment.Array, segment.Offset, segment.Count);
98
99 // return batch to pool for reuse
101 }
102
103 // get the next batch which is available for sending (if any).
104 // TODO safely get & return a batch instead of copying to writer?
105 // TODO could return pooled writer & use GetBatch in a 'using' statement!
106 public bool GetBatch(NetworkWriter writer)
107 {
108 // get first batch from queue (if any)
109 if (batches.TryDequeue(out NetworkWriterPooled first))
110 {
111 CopyAndReturn(first, writer);
112 return true;
113 }
114
115 // if queue was empty, we can send the batch in progress.
116 if (batch != null)
117 {
118 CopyAndReturn(batch, writer);
119 batch = null;
120 return true;
121 }
122
123 // nothing was written
124 return false;
125 }
126 }
127}
Network Writer for most simple types like floats, ints, buffers, structs, etc. Use NetworkWriterPool....
ArraySegment< byte > ToArraySegment()
Returns allocation-free ArraySegment until 'Position'.
int Position
Next position to write to the buffer
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'