NetworkWriter.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Runtime.CompilerServices;
  4. using System.Text;
  5. using UnityEngine;
  6. namespace Mirror
  7. {
  8. /// <summary>Helper class that weaver populates with all writer types.</summary>
  9. // Note that c# creates a different static variable for each type
  10. // -> Weaver.ReaderWriterProcessor.InitializeReaderAndWriters() populates it
  11. public static class Writer<T>
  12. {
  13. public static Action<NetworkWriter, T> write;
  14. }
  15. /// <summary>Network Writer for most simple types like floats, ints, buffers, structs, etc. Use NetworkWriterPool.GetReader() to avoid allocations.</summary>
  16. public class NetworkWriter
  17. {
  18. public const int MaxStringLength = 1024 * 32;
  19. // create writer immediately with it's own buffer so no one can mess with it and so that we can resize it.
  20. // note: BinaryWriter allocates too much, so we only use a MemoryStream
  21. // => 1500 bytes by default because on average, most packets will be <= MTU
  22. byte[] buffer = new byte[1500];
  23. /// <summary>Next position to write to the buffer</summary>
  24. public int Position;
  25. /// <summary>Reset both the position and length of the stream</summary>
  26. // Leaves the capacity the same so that we can reuse this writer without
  27. // extra allocations
  28. public void Reset()
  29. {
  30. Position = 0;
  31. }
  32. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  33. void EnsureCapacity(int value)
  34. {
  35. if (buffer.Length < value)
  36. {
  37. int capacity = Math.Max(value, buffer.Length * 2);
  38. Array.Resize(ref buffer, capacity);
  39. }
  40. }
  41. /// <summary>Copies buffer until 'Position' to a new array.</summary>
  42. public byte[] ToArray()
  43. {
  44. byte[] data = new byte[Position];
  45. Array.ConstrainedCopy(buffer, 0, data, 0, Position);
  46. return data;
  47. }
  48. /// <summary>Returns allocation-free ArraySegment until 'Position'.</summary>
  49. public ArraySegment<byte> ToArraySegment()
  50. {
  51. return new ArraySegment<byte>(buffer, 0, Position);
  52. }
  53. public void WriteByte(byte value)
  54. {
  55. EnsureCapacity(Position + 1);
  56. buffer[Position++] = value;
  57. }
  58. // for byte arrays with consistent size, where the reader knows how many to read
  59. // (like a packet opcode that's always the same)
  60. public void WriteBytes(byte[] buffer, int offset, int count)
  61. {
  62. EnsureCapacity(Position + count);
  63. Array.ConstrainedCopy(buffer, offset, this.buffer, Position, count);
  64. Position += count;
  65. }
  66. /// <summary>Writes any type that mirror supports. Uses weaver populated Writer(T).write.</summary>
  67. public void Write<T>(T value)
  68. {
  69. Action<NetworkWriter, T> writeDelegate = Writer<T>.write;
  70. if (writeDelegate == null)
  71. {
  72. Debug.LogError($"No writer found for {typeof(T)}. Use a type supported by Mirror or define a custom writer");
  73. }
  74. else
  75. {
  76. writeDelegate(this, value);
  77. }
  78. }
  79. }
  80. // Mirror's Weaver automatically detects all NetworkWriter function types,
  81. // but they do all need to be extensions.
  82. public static class NetworkWriterExtensions
  83. {
  84. // cache encoding instead of creating it with BinaryWriter each time
  85. // 1000 readers before: 1MB GC, 30ms
  86. // 1000 readers after: 0.8MB GC, 18ms
  87. static readonly UTF8Encoding encoding = new UTF8Encoding(false, true);
  88. static readonly byte[] stringBuffer = new byte[NetworkWriter.MaxStringLength];
  89. public static void WriteByte(this NetworkWriter writer, byte value) => writer.WriteByte(value);
  90. public static void WriteSByte(this NetworkWriter writer, sbyte value) => writer.WriteByte((byte)value);
  91. public static void WriteChar(this NetworkWriter writer, char value) => writer.WriteUShort(value);
  92. // Deprecated 2021-05-18
  93. [Obsolete("We've cleaned up the API. Use WriteBool instead.")]
  94. public static void WriteBoolean(this NetworkWriter writer, bool value) => writer.WriteBool(value);
  95. public static void WriteBool(this NetworkWriter writer, bool value) => writer.WriteByte((byte)(value ? 1 : 0));
  96. // Deprecated 2021-05-18
  97. [Obsolete("We've cleaned up the API. Use WriteUShort instead.")]
  98. public static void WriteUInt16(this NetworkWriter writer, ushort value) => writer.WriteUShort(value);
  99. public static void WriteUShort(this NetworkWriter writer, ushort value)
  100. {
  101. writer.WriteByte((byte)value);
  102. writer.WriteByte((byte)(value >> 8));
  103. }
  104. // Deprecated 2021-05-18
  105. [Obsolete("We've cleaned up the API. Use WriteShort instead.")]
  106. public static void WriteInt16(this NetworkWriter writer, short value) => writer.WriteShort(value);
  107. public static void WriteShort(this NetworkWriter writer, short value) => writer.WriteUShort((ushort)value);
  108. // Deprecated 2021-05-18
  109. [Obsolete("We've cleaned up the API. Use WriteUInt instead.")]
  110. public static void WriteUInt32(this NetworkWriter writer, uint value) => writer.WriteUInt(value);
  111. public static void WriteUInt(this NetworkWriter writer, uint value)
  112. {
  113. writer.WriteByte((byte)value);
  114. writer.WriteByte((byte)(value >> 8));
  115. writer.WriteByte((byte)(value >> 16));
  116. writer.WriteByte((byte)(value >> 24));
  117. }
  118. // Deprecated 2021-05-18
  119. [Obsolete("We've cleaned up the API. Use WriteInt instead.")]
  120. public static void WriteInt32(this NetworkWriter writer, int value) => writer.WriteInt(value);
  121. public static void WriteInt(this NetworkWriter writer, int value) => writer.WriteUInt((uint)value);
  122. // Deprecated 2021-05-18
  123. [Obsolete("We've cleaned up the API. Use WriteULong instead.")]
  124. public static void WriteUInt64(this NetworkWriter writer, ulong value) => writer.WriteULong(value);
  125. public static void WriteULong(this NetworkWriter writer, ulong value)
  126. {
  127. writer.WriteByte((byte)value);
  128. writer.WriteByte((byte)(value >> 8));
  129. writer.WriteByte((byte)(value >> 16));
  130. writer.WriteByte((byte)(value >> 24));
  131. writer.WriteByte((byte)(value >> 32));
  132. writer.WriteByte((byte)(value >> 40));
  133. writer.WriteByte((byte)(value >> 48));
  134. writer.WriteByte((byte)(value >> 56));
  135. }
  136. // Deprecated 2021-05-18
  137. [Obsolete("We've cleaned up the API. Use WriteLong instead.")]
  138. public static void WriteInt64(this NetworkWriter writer, long value) => writer.WriteLong(value);
  139. public static void WriteLong(this NetworkWriter writer, long value) => writer.WriteULong((ulong)value);
  140. // Deprecated 2021-05-18
  141. [Obsolete("We've cleaned up the API. Use WriteFloat instead.")]
  142. public static void WriteSingle(this NetworkWriter writer, float value) => writer.WriteFloat(value);
  143. public static void WriteFloat(this NetworkWriter writer, float value)
  144. {
  145. UIntFloat converter = new UIntFloat
  146. {
  147. floatValue = value
  148. };
  149. writer.WriteUInt(converter.intValue);
  150. }
  151. public static void WriteDouble(this NetworkWriter writer, double value)
  152. {
  153. UIntDouble converter = new UIntDouble
  154. {
  155. doubleValue = value
  156. };
  157. writer.WriteULong(converter.longValue);
  158. }
  159. public static void WriteDecimal(this NetworkWriter writer, decimal value)
  160. {
  161. // the only way to read it without allocations is to both read and
  162. // write it with the FloatConverter (which is not binary compatible
  163. // to writer.Write(decimal), hence why we use it here too)
  164. UIntDecimal converter = new UIntDecimal
  165. {
  166. decimalValue = value
  167. };
  168. writer.WriteULong(converter.longValue1);
  169. writer.WriteULong(converter.longValue2);
  170. }
  171. public static void WriteString(this NetworkWriter writer, string value)
  172. {
  173. // write 0 for null support, increment real size by 1
  174. // (note: original HLAPI would write "" for null strings, but if a
  175. // string is null on the server then it should also be null
  176. // on the client)
  177. if (value == null)
  178. {
  179. writer.WriteUShort(0);
  180. return;
  181. }
  182. // write string with same method as NetworkReader
  183. // convert to byte[]
  184. int size = encoding.GetBytes(value, 0, value.Length, stringBuffer, 0);
  185. // check if within max size
  186. if (size >= NetworkWriter.MaxStringLength)
  187. {
  188. throw new IndexOutOfRangeException("NetworkWriter.Write(string) too long: " + size + ". Limit: " + NetworkWriter.MaxStringLength);
  189. }
  190. // write size and bytes
  191. writer.WriteUShort(checked((ushort)(size + 1)));
  192. writer.WriteBytes(stringBuffer, 0, size);
  193. }
  194. // for byte arrays with dynamic size, where the reader doesn't know how many will come
  195. // (like an inventory with different items etc.)
  196. public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer, int offset, int count)
  197. {
  198. // null is supported because [SyncVar]s might be structs with null byte[] arrays
  199. // write 0 for null array, increment normal size by 1 to save bandwidth
  200. // (using size=-1 for null would limit max size to 32kb instead of 64kb)
  201. if (buffer == null)
  202. {
  203. writer.WriteUInt(0u);
  204. return;
  205. }
  206. writer.WriteUInt(checked((uint)count) + 1u);
  207. writer.WriteBytes(buffer, offset, count);
  208. }
  209. // Weaver needs a write function with just one byte[] parameter
  210. // (we don't name it .Write(byte[]) because it's really a WriteBytesAndSize since we write size / null info too)
  211. public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer)
  212. {
  213. // buffer might be null, so we can't use .Length in that case
  214. writer.WriteBytesAndSize(buffer, 0, buffer != null ? buffer.Length : 0);
  215. }
  216. public static void WriteBytesAndSizeSegment(this NetworkWriter writer, ArraySegment<byte> buffer)
  217. {
  218. writer.WriteBytesAndSize(buffer.Array, buffer.Offset, buffer.Count);
  219. }
  220. public static void WriteVector2(this NetworkWriter writer, Vector2 value)
  221. {
  222. writer.WriteFloat(value.x);
  223. writer.WriteFloat(value.y);
  224. }
  225. public static void WriteVector3(this NetworkWriter writer, Vector3 value)
  226. {
  227. writer.WriteFloat(value.x);
  228. writer.WriteFloat(value.y);
  229. writer.WriteFloat(value.z);
  230. }
  231. // TODO add nullable support to weaver instead
  232. public static void WriteVector3Nullable(this NetworkWriter writer, Vector3? value)
  233. {
  234. writer.WriteBool(value.HasValue);
  235. if (value.HasValue)
  236. writer.WriteVector3(value.Value);
  237. }
  238. public static void WriteVector4(this NetworkWriter writer, Vector4 value)
  239. {
  240. writer.WriteFloat(value.x);
  241. writer.WriteFloat(value.y);
  242. writer.WriteFloat(value.z);
  243. writer.WriteFloat(value.w);
  244. }
  245. public static void WriteVector2Int(this NetworkWriter writer, Vector2Int value)
  246. {
  247. writer.WriteInt(value.x);
  248. writer.WriteInt(value.y);
  249. }
  250. public static void WriteVector3Int(this NetworkWriter writer, Vector3Int value)
  251. {
  252. writer.WriteInt(value.x);
  253. writer.WriteInt(value.y);
  254. writer.WriteInt(value.z);
  255. }
  256. public static void WriteColor(this NetworkWriter writer, Color value)
  257. {
  258. writer.WriteFloat(value.r);
  259. writer.WriteFloat(value.g);
  260. writer.WriteFloat(value.b);
  261. writer.WriteFloat(value.a);
  262. }
  263. public static void WriteColor32(this NetworkWriter writer, Color32 value)
  264. {
  265. writer.WriteByte(value.r);
  266. writer.WriteByte(value.g);
  267. writer.WriteByte(value.b);
  268. writer.WriteByte(value.a);
  269. }
  270. public static void WriteQuaternion(this NetworkWriter writer, Quaternion value)
  271. {
  272. writer.WriteFloat(value.x);
  273. writer.WriteFloat(value.y);
  274. writer.WriteFloat(value.z);
  275. writer.WriteFloat(value.w);
  276. }
  277. // TODO add nullable support to weaver instead
  278. public static void WriteQuaternionNullable(this NetworkWriter writer, Quaternion? value)
  279. {
  280. writer.WriteBool(value.HasValue);
  281. if (value.HasValue)
  282. writer.WriteQuaternion(value.Value);
  283. }
  284. public static void WriteRect(this NetworkWriter writer, Rect value)
  285. {
  286. writer.WriteFloat(value.xMin);
  287. writer.WriteFloat(value.yMin);
  288. writer.WriteFloat(value.width);
  289. writer.WriteFloat(value.height);
  290. }
  291. public static void WritePlane(this NetworkWriter writer, Plane value)
  292. {
  293. writer.WriteVector3(value.normal);
  294. writer.WriteFloat(value.distance);
  295. }
  296. public static void WriteRay(this NetworkWriter writer, Ray value)
  297. {
  298. writer.WriteVector3(value.origin);
  299. writer.WriteVector3(value.direction);
  300. }
  301. public static void WriteMatrix4x4(this NetworkWriter writer, Matrix4x4 value)
  302. {
  303. writer.WriteFloat(value.m00);
  304. writer.WriteFloat(value.m01);
  305. writer.WriteFloat(value.m02);
  306. writer.WriteFloat(value.m03);
  307. writer.WriteFloat(value.m10);
  308. writer.WriteFloat(value.m11);
  309. writer.WriteFloat(value.m12);
  310. writer.WriteFloat(value.m13);
  311. writer.WriteFloat(value.m20);
  312. writer.WriteFloat(value.m21);
  313. writer.WriteFloat(value.m22);
  314. writer.WriteFloat(value.m23);
  315. writer.WriteFloat(value.m30);
  316. writer.WriteFloat(value.m31);
  317. writer.WriteFloat(value.m32);
  318. writer.WriteFloat(value.m33);
  319. }
  320. public static void WriteGuid(this NetworkWriter writer, Guid value)
  321. {
  322. byte[] data = value.ToByteArray();
  323. writer.WriteBytes(data, 0, data.Length);
  324. }
  325. public static void WriteNetworkIdentity(this NetworkWriter writer, NetworkIdentity value)
  326. {
  327. if (value == null)
  328. {
  329. writer.WriteUInt(0);
  330. return;
  331. }
  332. writer.WriteUInt(value.netId);
  333. }
  334. public static void WriteNetworkBehaviour(this NetworkWriter writer, NetworkBehaviour value)
  335. {
  336. if (value == null)
  337. {
  338. writer.WriteUInt(0);
  339. return;
  340. }
  341. writer.WriteUInt(value.netId);
  342. writer.WriteByte((byte)value.ComponentIndex);
  343. }
  344. public static void WriteTransform(this NetworkWriter writer, Transform value)
  345. {
  346. if (value == null)
  347. {
  348. writer.WriteUInt(0);
  349. return;
  350. }
  351. NetworkIdentity identity = value.GetComponent<NetworkIdentity>();
  352. if (identity != null)
  353. {
  354. writer.WriteUInt(identity.netId);
  355. }
  356. else
  357. {
  358. Debug.LogWarning("NetworkWriter " + value + " has no NetworkIdentity");
  359. writer.WriteUInt(0);
  360. }
  361. }
  362. public static void WriteGameObject(this NetworkWriter writer, GameObject value)
  363. {
  364. if (value == null)
  365. {
  366. writer.WriteUInt(0);
  367. return;
  368. }
  369. NetworkIdentity identity = value.GetComponent<NetworkIdentity>();
  370. if (identity != null)
  371. {
  372. writer.WriteUInt(identity.netId);
  373. }
  374. else
  375. {
  376. Debug.LogWarning("NetworkWriter " + value + " has no NetworkIdentity");
  377. writer.WriteUInt(0);
  378. }
  379. }
  380. public static void WriteUri(this NetworkWriter writer, Uri uri)
  381. {
  382. writer.WriteString(uri?.ToString());
  383. }
  384. public static void WriteList<T>(this NetworkWriter writer, List<T> list)
  385. {
  386. if (list is null)
  387. {
  388. writer.WriteInt(-1);
  389. return;
  390. }
  391. writer.WriteInt(list.Count);
  392. for (int i = 0; i < list.Count; i++)
  393. writer.Write(list[i]);
  394. }
  395. public static void WriteArray<T>(this NetworkWriter writer, T[] array)
  396. {
  397. if (array is null)
  398. {
  399. writer.WriteInt(-1);
  400. return;
  401. }
  402. writer.WriteInt(array.Length);
  403. for (int i = 0; i < array.Length; i++)
  404. writer.Write(array[i]);
  405. }
  406. public static void WriteArraySegment<T>(this NetworkWriter writer, ArraySegment<T> segment)
  407. {
  408. int length = segment.Count;
  409. writer.WriteInt(length);
  410. for (int i = 0; i < length; i++)
  411. {
  412. writer.Write(segment.Array[segment.Offset + i]);
  413. }
  414. }
  415. }
  416. }