Mirror Networking
NetworkReader.cs
1using System;
2using System.IO;
3using System.Runtime.CompilerServices;
4using Unity.Collections.LowLevel.Unsafe;
5using UnityEngine;
6
7namespace Mirror
8{
10 // Note: This class is intended to be extremely pedantic,
11 // and throw exceptions whenever stuff is going slightly wrong.
12 // The exceptions will be handled in NetworkServer/NetworkClient.
13 public class NetworkReader
14 {
15 // internal buffer
16 // byte[] pointer would work, but we use ArraySegment to also support
17 // the ArraySegment constructor
18 ArraySegment<byte> buffer;
19
21 // 'int' is the best type for .Position. 'short' is too small if we send >32kb which would result in negative .Position
22 // -> converting long to int is fine until 2GB of data (MAX_INT), so we don't have to worry about overflows here
23 public int Position;
24
26 public int Length
27 {
28 [MethodImpl(MethodImplOptions.AggressiveInlining)]
29 get => buffer.Count;
30 }
31
33 public int Remaining
34 {
35 [MethodImpl(MethodImplOptions.AggressiveInlining)]
36 get => Length - Position;
37 }
38
39 public NetworkReader(byte[] bytes)
40 {
41 buffer = new ArraySegment<byte>(bytes);
42 }
43
44 public NetworkReader(ArraySegment<byte> segment)
45 {
46 buffer = segment;
47 }
48
49 // sometimes it's useful to point a reader on another buffer instead of
50 // allocating a new reader (e.g. NetworkReaderPool)
51 [MethodImpl(MethodImplOptions.AggressiveInlining)]
52 public void SetBuffer(byte[] bytes)
53 {
54 buffer = new ArraySegment<byte>(bytes);
55 Position = 0;
56 }
57
58 [MethodImpl(MethodImplOptions.AggressiveInlining)]
59 public void SetBuffer(ArraySegment<byte> segment)
60 {
61 buffer = segment;
62 Position = 0;
63 }
64
65 // ReadBlittable<T> from DOTSNET
66 // this is extremely fast, but only works for blittable types.
67 // => private to make sure nobody accidentally uses it for non-blittable
68 //
69 // Benchmark: see NetworkWriter.WriteBlittable!
70 //
71 // Note:
72 // ReadBlittable assumes same endianness for server & client.
73 // All Unity 2018+ platforms are little endian.
74 //
75 // This is not safe to expose to random structs.
76 // * StructLayout.Sequential is the default, which is safe.
77 // if the struct contains a reference type, it is converted to Auto.
78 // but since all structs here are unmanaged blittable, it's safe.
79 // see also: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.layoutkind?view=netframework-4.8#system-runtime-interopservices-layoutkind-sequential
80 // * StructLayout.Pack depends on CPU word size.
81 // this may be different 4 or 8 on some ARM systems, etc.
82 // this is not safe, and would cause bytes/shorts etc. to be padded.
83 // see also: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute.pack?view=net-6.0
84 // * If we force pack all to '1', they would have no padding which is
85 // great for bandwidth. but on some android systems, CPU can't read
86 // unaligned memory.
87 // see also: https://github.com/vis2k/Mirror/issues/3044
88 // * The only option would be to force explicit layout with multiples
89 // of word size. but this requires lots of weaver checking and is
90 // still questionable (IL2CPP etc.).
91 //
92 // Note: inlining ReadBlittable is enough. don't inline ReadInt etc.
93 // we don't want ReadBlittable to be copied in place everywhere.
94 [MethodImpl(MethodImplOptions.AggressiveInlining)]
95 internal unsafe T ReadBlittable<T>()
96 where T : unmanaged
97 {
98 // check if blittable for safety
99#if UNITY_EDITOR
100 if (!UnsafeUtility.IsBlittable(typeof(T)))
101 {
102 throw new ArgumentException($"{typeof(T)} is not blittable!");
103 }
104#endif
105
106 // calculate size
107 // sizeof(T) gets the managed size at compile time.
108 // Marshal.SizeOf<T> gets the unmanaged size at runtime (slow).
109 // => our 1mio writes benchmark is 6x slower with Marshal.SizeOf<T>
110 // => for blittable types, sizeof(T) is even recommended:
111 // https://docs.microsoft.com/en-us/dotnet/standard/native-interop/best-practices
112 int size = sizeof(T);
113
114 // enough data to read?
115 if (Position + size > buffer.Count)
116 {
117 throw new EndOfStreamException($"ReadBlittable<{typeof(T)}> out of range: {ToString()}");
118 }
119
120 // read blittable
121 T value;
122 fixed (byte* ptr = &buffer.Array[buffer.Offset + Position])
123 {
124#if UNITY_ANDROID
125 // on some android systems, reading *(T*)ptr throws a NRE if
126 // the ptr isn't aligned (i.e. if Position is 1,2,3,5, etc.).
127 // here we have to use memcpy.
128 //
129 // => we can't get a pointer of a struct in C# without
130 // marshalling allocations
131 // => instead, we stack allocate an array of type T and use that
132 // => stackalloc avoids GC and is very fast. it only works for
133 // value types, but all blittable types are anyway.
134 //
135 // this way, we can still support blittable reads on android.
136 // see also: https://github.com/vis2k/Mirror/issues/3044
137 // (solution discovered by AIIO, FakeByte, mischa)
138 T* valueBuffer = stackalloc T[1];
139 UnsafeUtility.MemCpy(valueBuffer, ptr, size);
140 value = valueBuffer[0];
141#else
142 // cast buffer to a T* pointer and then read from it.
143 value = *(T*)ptr;
144#endif
145 }
146 Position += size;
147 return value;
148 }
149
150 // blittable'?' template for code reuse
151 // note: bool isn't blittable. need to read as byte.
152 [MethodImpl(MethodImplOptions.AggressiveInlining)]
153 internal T? ReadBlittableNullable<T>()
154 where T : unmanaged =>
155 ReadByte() != 0 ? ReadBlittable<T>() : default(T?);
156
157 public byte ReadByte() => ReadBlittable<byte>();
158
160 // NOTE: returns byte[] because all reader functions return something.
161 public byte[] ReadBytes(byte[] bytes, int count)
162 {
163 // check if passed byte array is big enough
164 if (count > bytes.Length)
165 {
166 throw new EndOfStreamException($"ReadBytes can't read {count} + bytes because the passed byte[] only has length {bytes.Length}");
167 }
168 // check if within buffer limits
169 if (Position + count > buffer.Count)
170 {
171 throw new EndOfStreamException($"ReadBytesSegment can't read {count} bytes because it would read past the end of the stream. {ToString()}");
172 }
173
174 Array.Copy(buffer.Array, buffer.Offset + Position, bytes, 0, count);
175 Position += count;
176 return bytes;
177 }
178
180 public ArraySegment<byte> ReadBytesSegment(int count)
181 {
182 // check if within buffer limits
183 if (Position + count > buffer.Count)
184 {
185 throw new EndOfStreamException($"ReadBytesSegment can't read {count} bytes because it would read past the end of the stream. {ToString()}");
186 }
187
188 // return the segment
189 ArraySegment<byte> result = new ArraySegment<byte>(buffer.Array, buffer.Offset + Position, count);
190 Position += count;
191 return result;
192 }
193
194 [MethodImpl(MethodImplOptions.AggressiveInlining)]
195 public override string ToString() =>
196 $"NetworkReader pos={Position} len={Length} buffer={BitConverter.ToString(buffer.Array, buffer.Offset, buffer.Count)}";
197
199 [MethodImpl(MethodImplOptions.AggressiveInlining)]
200 public T Read<T>()
201 {
202 Func<NetworkReader, T> readerDelegate = Reader<T>.read;
203 if (readerDelegate == null)
204 {
205 Debug.LogError($"No reader found for {typeof(T)}. Use a type supported by Mirror or define a custom reader");
206 return default;
207 }
208 return readerDelegate(this);
209 }
210 }
211
213 // Note that c# creates a different static variable for each type
214 // -> Weaver.ReaderWriterProcessor.InitializeReaderAndWriters() populates it
215 public static class Reader<T>
216 {
217 public static Func<NetworkReader, T> read;
218 }
219}
Network Reader for most simple types like floats, ints, buffers, structs, etc. Use NetworkReaderPool....
int Length
Total number of bytes to read from buffer
byte[] ReadBytes(byte[] bytes, int count)
Read 'count' bytes into the bytes array
int Position
Next position to read from the buffer
T Read< T >()
Reads any data type that mirror supports. Uses weaver populated Reader(T).read
ArraySegment< byte > ReadBytesSegment(int count)
Read 'count' bytes allocation-free as ArraySegment that points to the internal array.
int Remaining
Remaining bytes that can be read, for convenience.
Helper class that weaver populates with all reader types.