Mirror Networking
NetworkWriter.cs
1using System;
2using System.Runtime.CompilerServices;
3using Unity.Collections.LowLevel.Unsafe;
4using UnityEngine;
5
6namespace Mirror
7{
9 public class NetworkWriter
10 {
11 public const int MaxStringLength = 1024 * 32;
12
13 // create writer immediately with it's own buffer so no one can mess with it and so that we can resize it.
14 // note: BinaryWriter allocates too much, so we only use a MemoryStream
15 // => 1500 bytes by default because on average, most packets will be <= MTU
16 byte[] buffer = new byte[1500];
17
19 public int Position;
20
22 // Leaves the capacity the same so that we can reuse this writer without
23 // extra allocations
24 [MethodImpl(MethodImplOptions.AggressiveInlining)]
25 public void Reset()
26 {
27 Position = 0;
28 }
29
30 // NOTE that our runtime resizing comes at no extra cost because:
31 // 1. 'has space' checks are necessary even for fixed sized writers.
32 // 2. all writers will eventually be large enough to stop resizing.
33 [MethodImpl(MethodImplOptions.AggressiveInlining)]
34 void EnsureCapacity(int value)
35 {
36 if (buffer.Length < value)
37 {
38 int capacity = Math.Max(value, buffer.Length * 2);
39 Array.Resize(ref buffer, capacity);
40 }
41 }
42
44 [MethodImpl(MethodImplOptions.AggressiveInlining)]
45 public byte[] ToArray()
46 {
47 byte[] data = new byte[Position];
48 Array.ConstrainedCopy(buffer, 0, data, 0, Position);
49 return data;
50 }
51
53 [MethodImpl(MethodImplOptions.AggressiveInlining)]
54 public ArraySegment<byte> ToArraySegment()
55 {
56 return new ArraySegment<byte>(buffer, 0, Position);
57 }
58
59 // WriteBlittable<T> from DOTSNET.
60 // this is extremely fast, but only works for blittable types.
61 //
62 // Benchmark:
63 // WriteQuaternion x 100k, Macbook Pro 2015 @ 2.2Ghz, Unity 2018 LTS (debug mode)
64 //
65 // | Median | Min | Max | Avg | Std | (ms)
66 // before | 30.35 | 29.86 | 48.99 | 32.54 | 4.93 |
67 // blittable* | 5.69 | 5.52 | 27.51 | 7.78 | 5.65 |
68 //
69 // * without IsBlittable check
70 // => 4-6x faster!
71 //
72 // WriteQuaternion x 100k, Macbook Pro 2015 @ 2.2Ghz, Unity 2020.1 (release mode)
73 //
74 // | Median | Min | Max | Avg | Std | (ms)
75 // before | 9.41 | 8.90 | 23.02 | 10.72 | 3.07 |
76 // blittable* | 1.48 | 1.40 | 16.03 | 2.60 | 2.71 |
77 //
78 // * without IsBlittable check
79 // => 6x faster!
80 //
81 // Note:
82 // WriteBlittable assumes same endianness for server & client.
83 // All Unity 2018+ platforms are little endian.
84 // => run NetworkWriterTests.BlittableOnThisPlatform() to verify!
85 //
86 // This is not safe to expose to random structs.
87 // * StructLayout.Sequential is the default, which is safe.
88 // if the struct contains a reference type, it is converted to Auto.
89 // but since all structs here are unmanaged blittable, it's safe.
90 // see also: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.layoutkind?view=netframework-4.8#system-runtime-interopservices-layoutkind-sequential
91 // * StructLayout.Pack depends on CPU word size.
92 // this may be different 4 or 8 on some ARM systems, etc.
93 // this is not safe, and would cause bytes/shorts etc. to be padded.
94 // see also: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute.pack?view=net-6.0
95 // * If we force pack all to '1', they would have no padding which is
96 // great for bandwidth. but on some android systems, CPU can't read
97 // unaligned memory.
98 // see also: https://github.com/vis2k/Mirror/issues/3044
99 // * The only option would be to force explicit layout with multiples
100 // of word size. but this requires lots of weaver checking and is
101 // still questionable (IL2CPP etc.).
102 //
103 // Note: inlining WriteBlittable is enough. don't inline WriteInt etc.
104 // we don't want WriteBlittable to be copied in place everywhere.
105 [MethodImpl(MethodImplOptions.AggressiveInlining)]
106 internal unsafe void WriteBlittable<T>(T value)
107 where T : unmanaged
108 {
109 // check if blittable for safety
110#if UNITY_EDITOR
111 if (!UnsafeUtility.IsBlittable(typeof(T)))
112 {
113 Debug.LogError($"{typeof(T)} is not blittable!");
114 return;
115 }
116#endif
117 // calculate size
118 // sizeof(T) gets the managed size at compile time.
119 // Marshal.SizeOf<T> gets the unmanaged size at runtime (slow).
120 // => our 1mio writes benchmark is 6x slower with Marshal.SizeOf<T>
121 // => for blittable types, sizeof(T) is even recommended:
122 // https://docs.microsoft.com/en-us/dotnet/standard/native-interop/best-practices
123 int size = sizeof(T);
124
125 // ensure capacity
126 // NOTE that our runtime resizing comes at no extra cost because:
127 // 1. 'has space' checks are necessary even for fixed sized writers.
128 // 2. all writers will eventually be large enough to stop resizing.
129 EnsureCapacity(Position + size);
130
131 // write blittable
132 fixed (byte* ptr = &buffer[Position])
133 {
134#if UNITY_ANDROID
135 // on some android systems, assigning *(T*)ptr throws a NRE if
136 // the ptr isn't aligned (i.e. if Position is 1,2,3,5, etc.).
137 // here we have to use memcpy.
138 //
139 // => we can't get a pointer of a struct in C# without
140 // marshalling allocations
141 // => instead, we stack allocate an array of type T and use that
142 // => stackalloc avoids GC and is very fast. it only works for
143 // value types, but all blittable types are anyway.
144 //
145 // this way, we can still support blittable reads on android.
146 // see also: https://github.com/vis2k/Mirror/issues/3044
147 // (solution discovered by AIIO, FakeByte, mischa)
148 T* valueBuffer = stackalloc T[1]{value};
149 UnsafeUtility.MemCpy(ptr, valueBuffer, size);
150#else
151 // cast buffer to T* pointer, then assign value to the area
152 *(T*)ptr = value;
153#endif
154 }
155 Position += size;
156 }
157
158 // blittable'?' template for code reuse
159 [MethodImpl(MethodImplOptions.AggressiveInlining)]
160 internal void WriteBlittableNullable<T>(T? value)
161 where T : unmanaged
162 {
163 // bool isn't blittable. write as byte.
164 WriteByte((byte)(value.HasValue ? 0x01 : 0x00));
165
166 // only write value if exists. saves bandwidth.
167 if (value.HasValue)
168 WriteBlittable(value.Value);
169 }
170
171 public void WriteByte(byte value) => WriteBlittable(value);
172
173 // for byte arrays with consistent size, where the reader knows how many to read
174 // (like a packet opcode that's always the same)
175 public void WriteBytes(byte[] buffer, int offset, int count)
176 {
177 EnsureCapacity(Position + count);
178 Array.ConstrainedCopy(buffer, offset, this.buffer, Position, count);
179 Position += count;
180 }
181
183 [MethodImpl(MethodImplOptions.AggressiveInlining)]
184 public void Write<T>(T value)
185 {
186 Action<NetworkWriter, T> writeDelegate = Writer<T>.write;
187 if (writeDelegate == null)
188 {
189 Debug.LogError($"No writer found for {typeof(T)}. This happens either if you are missing a NetworkWriter extension for your custom type, or if weaving failed. Try to reimport a script to weave again.");
190 }
191 else
192 {
193 writeDelegate(this, value);
194 }
195 }
196 }
197
199 // Note that c# creates a different static variable for each type
200 // -> Weaver.ReaderWriterProcessor.InitializeReaderAndWriters() populates it
201 public static class Writer<T>
202 {
203 public static Action<NetworkWriter, T> write;
204 }
205}
Network Writer for most simple types like floats, ints, buffers, structs, etc. Use NetworkWriterPool....
void Reset()
Reset both the position and length of the stream
void Write< T >(T value)
Writes any type that mirror supports. Uses weaver populated Writer(T).write.
ArraySegment< byte > ToArraySegment()
Returns allocation-free ArraySegment until 'Position'.
byte[] ToArray()
Copies buffer until 'Position' to a new array.
int Position
Next position to write to the buffer
Helper class that weaver populates with all writer types.