NetworkWriter.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  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)}. 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.");
  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. public static void WriteBool(this NetworkWriter writer, bool value) => writer.WriteByte((byte)(value ? 1 : 0));
  93. public static void WriteUShort(this NetworkWriter writer, ushort value)
  94. {
  95. writer.WriteByte((byte)value);
  96. writer.WriteByte((byte)(value >> 8));
  97. }
  98. public static void WriteShort(this NetworkWriter writer, short value) => writer.WriteUShort((ushort)value);
  99. public static void WriteUInt(this NetworkWriter writer, uint value)
  100. {
  101. writer.WriteByte((byte)value);
  102. writer.WriteByte((byte)(value >> 8));
  103. writer.WriteByte((byte)(value >> 16));
  104. writer.WriteByte((byte)(value >> 24));
  105. }
  106. public static void WriteInt(this NetworkWriter writer, int value) => writer.WriteUInt((uint)value);
  107. public static void WriteULong(this NetworkWriter writer, ulong value)
  108. {
  109. writer.WriteByte((byte)value);
  110. writer.WriteByte((byte)(value >> 8));
  111. writer.WriteByte((byte)(value >> 16));
  112. writer.WriteByte((byte)(value >> 24));
  113. writer.WriteByte((byte)(value >> 32));
  114. writer.WriteByte((byte)(value >> 40));
  115. writer.WriteByte((byte)(value >> 48));
  116. writer.WriteByte((byte)(value >> 56));
  117. }
  118. public static void WriteLong(this NetworkWriter writer, long value) => writer.WriteULong((ulong)value);
  119. public static void WriteFloat(this NetworkWriter writer, float value)
  120. {
  121. UIntFloat converter = new UIntFloat
  122. {
  123. floatValue = value
  124. };
  125. writer.WriteUInt(converter.intValue);
  126. }
  127. public static void WriteDouble(this NetworkWriter writer, double value)
  128. {
  129. UIntDouble converter = new UIntDouble
  130. {
  131. doubleValue = value
  132. };
  133. writer.WriteULong(converter.longValue);
  134. }
  135. public static void WriteDecimal(this NetworkWriter writer, decimal value)
  136. {
  137. // the only way to read it without allocations is to both read and
  138. // write it with the FloatConverter (which is not binary compatible
  139. // to writer.Write(decimal), hence why we use it here too)
  140. UIntDecimal converter = new UIntDecimal
  141. {
  142. decimalValue = value
  143. };
  144. writer.WriteULong(converter.longValue1);
  145. writer.WriteULong(converter.longValue2);
  146. }
  147. public static void WriteString(this NetworkWriter writer, string value)
  148. {
  149. // write 0 for null support, increment real size by 1
  150. // (note: original HLAPI would write "" for null strings, but if a
  151. // string is null on the server then it should also be null
  152. // on the client)
  153. if (value == null)
  154. {
  155. writer.WriteUShort(0);
  156. return;
  157. }
  158. // write string with same method as NetworkReader
  159. // convert to byte[]
  160. int size = encoding.GetBytes(value, 0, value.Length, stringBuffer, 0);
  161. // check if within max size
  162. if (size >= NetworkWriter.MaxStringLength)
  163. {
  164. throw new IndexOutOfRangeException($"NetworkWriter.Write(string) too long: {size}. Limit: {NetworkWriter.MaxStringLength}");
  165. }
  166. // write size and bytes
  167. writer.WriteUShort(checked((ushort)(size + 1)));
  168. writer.WriteBytes(stringBuffer, 0, size);
  169. }
  170. // for byte arrays with dynamic size, where the reader doesn't know how many will come
  171. // (like an inventory with different items etc.)
  172. public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer, int offset, int count)
  173. {
  174. // null is supported because [SyncVar]s might be structs with null byte[] arrays
  175. // write 0 for null array, increment normal size by 1 to save bandwidth
  176. // (using size=-1 for null would limit max size to 32kb instead of 64kb)
  177. if (buffer == null)
  178. {
  179. writer.WriteUInt(0u);
  180. return;
  181. }
  182. writer.WriteUInt(checked((uint)count) + 1u);
  183. writer.WriteBytes(buffer, offset, count);
  184. }
  185. // Weaver needs a write function with just one byte[] parameter
  186. // (we don't name it .Write(byte[]) because it's really a WriteBytesAndSize since we write size / null info too)
  187. public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer)
  188. {
  189. // buffer might be null, so we can't use .Length in that case
  190. writer.WriteBytesAndSize(buffer, 0, buffer != null ? buffer.Length : 0);
  191. }
  192. public static void WriteBytesAndSizeSegment(this NetworkWriter writer, ArraySegment<byte> buffer)
  193. {
  194. writer.WriteBytesAndSize(buffer.Array, buffer.Offset, buffer.Count);
  195. }
  196. public static void WriteVector2(this NetworkWriter writer, Vector2 value)
  197. {
  198. writer.WriteFloat(value.x);
  199. writer.WriteFloat(value.y);
  200. }
  201. public static void WriteVector3(this NetworkWriter writer, Vector3 value)
  202. {
  203. writer.WriteFloat(value.x);
  204. writer.WriteFloat(value.y);
  205. writer.WriteFloat(value.z);
  206. }
  207. // TODO add nullable support to weaver instead
  208. public static void WriteVector3Nullable(this NetworkWriter writer, Vector3? value)
  209. {
  210. writer.WriteBool(value.HasValue);
  211. if (value.HasValue)
  212. writer.WriteVector3(value.Value);
  213. }
  214. public static void WriteVector4(this NetworkWriter writer, Vector4 value)
  215. {
  216. writer.WriteFloat(value.x);
  217. writer.WriteFloat(value.y);
  218. writer.WriteFloat(value.z);
  219. writer.WriteFloat(value.w);
  220. }
  221. public static void WriteVector2Int(this NetworkWriter writer, Vector2Int value)
  222. {
  223. writer.WriteInt(value.x);
  224. writer.WriteInt(value.y);
  225. }
  226. public static void WriteVector3Int(this NetworkWriter writer, Vector3Int value)
  227. {
  228. writer.WriteInt(value.x);
  229. writer.WriteInt(value.y);
  230. writer.WriteInt(value.z);
  231. }
  232. public static void WriteColor(this NetworkWriter writer, Color value)
  233. {
  234. writer.WriteFloat(value.r);
  235. writer.WriteFloat(value.g);
  236. writer.WriteFloat(value.b);
  237. writer.WriteFloat(value.a);
  238. }
  239. // TODO add nullable support to weaver instead
  240. public static void WriteColorNullable(this NetworkWriter writer, Color? value)
  241. {
  242. writer.WriteBool(value.HasValue);
  243. if (value.HasValue)
  244. writer.WriteColor(value.Value);
  245. }
  246. public static void WriteColor32(this NetworkWriter writer, Color32 value)
  247. {
  248. writer.WriteByte(value.r);
  249. writer.WriteByte(value.g);
  250. writer.WriteByte(value.b);
  251. writer.WriteByte(value.a);
  252. }
  253. // TODO add nullable support to weaver instead
  254. public static void WriteColor32Nullable(this NetworkWriter writer, Color32? value)
  255. {
  256. writer.WriteBool(value.HasValue);
  257. if (value.HasValue)
  258. writer.WriteColor32(value.Value);
  259. }
  260. public static void WriteQuaternion(this NetworkWriter writer, Quaternion value)
  261. {
  262. writer.WriteFloat(value.x);
  263. writer.WriteFloat(value.y);
  264. writer.WriteFloat(value.z);
  265. writer.WriteFloat(value.w);
  266. }
  267. // TODO add nullable support to weaver instead
  268. public static void WriteQuaternionNullable(this NetworkWriter writer, Quaternion? value)
  269. {
  270. writer.WriteBool(value.HasValue);
  271. if (value.HasValue)
  272. writer.WriteQuaternion(value.Value);
  273. }
  274. public static void WriteRect(this NetworkWriter writer, Rect value)
  275. {
  276. writer.WriteFloat(value.xMin);
  277. writer.WriteFloat(value.yMin);
  278. writer.WriteFloat(value.width);
  279. writer.WriteFloat(value.height);
  280. }
  281. public static void WritePlane(this NetworkWriter writer, Plane value)
  282. {
  283. writer.WriteVector3(value.normal);
  284. writer.WriteFloat(value.distance);
  285. }
  286. public static void WriteRay(this NetworkWriter writer, Ray value)
  287. {
  288. writer.WriteVector3(value.origin);
  289. writer.WriteVector3(value.direction);
  290. }
  291. public static void WriteMatrix4x4(this NetworkWriter writer, Matrix4x4 value)
  292. {
  293. writer.WriteFloat(value.m00);
  294. writer.WriteFloat(value.m01);
  295. writer.WriteFloat(value.m02);
  296. writer.WriteFloat(value.m03);
  297. writer.WriteFloat(value.m10);
  298. writer.WriteFloat(value.m11);
  299. writer.WriteFloat(value.m12);
  300. writer.WriteFloat(value.m13);
  301. writer.WriteFloat(value.m20);
  302. writer.WriteFloat(value.m21);
  303. writer.WriteFloat(value.m22);
  304. writer.WriteFloat(value.m23);
  305. writer.WriteFloat(value.m30);
  306. writer.WriteFloat(value.m31);
  307. writer.WriteFloat(value.m32);
  308. writer.WriteFloat(value.m33);
  309. }
  310. public static void WriteGuid(this NetworkWriter writer, Guid value)
  311. {
  312. byte[] data = value.ToByteArray();
  313. writer.WriteBytes(data, 0, data.Length);
  314. }
  315. public static void WriteNetworkIdentity(this NetworkWriter writer, NetworkIdentity value)
  316. {
  317. if (value == null)
  318. {
  319. writer.WriteUInt(0);
  320. return;
  321. }
  322. writer.WriteUInt(value.netId);
  323. }
  324. public static void WriteNetworkBehaviour(this NetworkWriter writer, NetworkBehaviour value)
  325. {
  326. if (value == null)
  327. {
  328. writer.WriteUInt(0);
  329. return;
  330. }
  331. writer.WriteUInt(value.netId);
  332. writer.WriteByte((byte)value.ComponentIndex);
  333. }
  334. public static void WriteTransform(this NetworkWriter writer, Transform value)
  335. {
  336. if (value == null)
  337. {
  338. writer.WriteUInt(0);
  339. return;
  340. }
  341. NetworkIdentity identity = value.GetComponent<NetworkIdentity>();
  342. if (identity != null)
  343. {
  344. writer.WriteUInt(identity.netId);
  345. }
  346. else
  347. {
  348. Debug.LogWarning($"NetworkWriter {value} has no NetworkIdentity");
  349. writer.WriteUInt(0);
  350. }
  351. }
  352. public static void WriteGameObject(this NetworkWriter writer, GameObject value)
  353. {
  354. if (value == null)
  355. {
  356. writer.WriteUInt(0);
  357. return;
  358. }
  359. NetworkIdentity identity = value.GetComponent<NetworkIdentity>();
  360. if (identity != null)
  361. {
  362. writer.WriteUInt(identity.netId);
  363. }
  364. else
  365. {
  366. Debug.LogWarning($"NetworkWriter {value} has no NetworkIdentity");
  367. writer.WriteUInt(0);
  368. }
  369. }
  370. public static void WriteUri(this NetworkWriter writer, Uri uri)
  371. {
  372. writer.WriteString(uri?.ToString());
  373. }
  374. public static void WriteList<T>(this NetworkWriter writer, List<T> list)
  375. {
  376. if (list is null)
  377. {
  378. writer.WriteInt(-1);
  379. return;
  380. }
  381. writer.WriteInt(list.Count);
  382. for (int i = 0; i < list.Count; i++)
  383. writer.Write(list[i]);
  384. }
  385. public static void WriteArray<T>(this NetworkWriter writer, T[] array)
  386. {
  387. if (array is null)
  388. {
  389. writer.WriteInt(-1);
  390. return;
  391. }
  392. writer.WriteInt(array.Length);
  393. for (int i = 0; i < array.Length; i++)
  394. writer.Write(array[i]);
  395. }
  396. public static void WriteArraySegment<T>(this NetworkWriter writer, ArraySegment<T> segment)
  397. {
  398. int length = segment.Count;
  399. writer.WriteInt(length);
  400. for (int i = 0; i < length; i++)
  401. {
  402. writer.Write(segment.Array[segment.Offset + i]);
  403. }
  404. }
  405. }
  406. }