NetworkReader.cs 8.2 KB

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