NetworkWriterExtensions.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. namespace Mirror
  5. {
  6. // Mirror's Weaver automatically detects all NetworkWriter function types,
  7. // but they do all need to be extensions.
  8. public static class NetworkWriterExtensions
  9. {
  10. public static void WriteByte(this NetworkWriter writer, byte value) => writer.WriteBlittable(value);
  11. public static void WriteByteNullable(this NetworkWriter writer, byte? value) => writer.WriteBlittableNullable(value);
  12. public static void WriteSByte(this NetworkWriter writer, sbyte value) => writer.WriteBlittable(value);
  13. public static void WriteSByteNullable(this NetworkWriter writer, sbyte? value) => writer.WriteBlittableNullable(value);
  14. // char is not blittable. convert to ushort.
  15. public static void WriteChar(this NetworkWriter writer, char value) => writer.WriteBlittable((ushort)value);
  16. public static void WriteCharNullable(this NetworkWriter writer, char? value) => writer.WriteBlittableNullable((ushort?)value);
  17. // bool is not blittable. convert to byte.
  18. public static void WriteBool(this NetworkWriter writer, bool value) => writer.WriteBlittable((byte)(value ? 1 : 0));
  19. public static void WriteBoolNullable(this NetworkWriter writer, bool? value) => writer.WriteBlittableNullable(value.HasValue ? ((byte)(value.Value ? 1 : 0)) : new byte?());
  20. public static void WriteShort(this NetworkWriter writer, short value) => writer.WriteBlittable(value);
  21. public static void WriteShortNullable(this NetworkWriter writer, short? value) => writer.WriteBlittableNullable(value);
  22. public static void WriteUShort(this NetworkWriter writer, ushort value) => writer.WriteBlittable(value);
  23. public static void WriteUShortNullable(this NetworkWriter writer, ushort? value) => writer.WriteBlittableNullable(value);
  24. public static void WriteInt(this NetworkWriter writer, int value) => writer.WriteBlittable(value);
  25. public static void WriteIntNullable(this NetworkWriter writer, int? value) => writer.WriteBlittableNullable(value);
  26. public static void WriteUInt(this NetworkWriter writer, uint value) => writer.WriteBlittable(value);
  27. public static void WriteUIntNullable(this NetworkWriter writer, uint? value) => writer.WriteBlittableNullable(value);
  28. public static void WriteLong(this NetworkWriter writer, long value) => writer.WriteBlittable(value);
  29. public static void WriteLongNullable(this NetworkWriter writer, long? value) => writer.WriteBlittableNullable(value);
  30. public static void WriteULong(this NetworkWriter writer, ulong value) => writer.WriteBlittable(value);
  31. public static void WriteULongNullable(this NetworkWriter writer, ulong? value) => writer.WriteBlittableNullable(value);
  32. public static void WriteFloat(this NetworkWriter writer, float value) => writer.WriteBlittable(value);
  33. public static void WriteFloatNullable(this NetworkWriter writer, float? value) => writer.WriteBlittableNullable(value);
  34. public static void WriteDouble(this NetworkWriter writer, double value) => writer.WriteBlittable(value);
  35. public static void WriteDoubleNullable(this NetworkWriter writer, double? value) => writer.WriteBlittableNullable(value);
  36. public static void WriteDecimal(this NetworkWriter writer, decimal value) => writer.WriteBlittable(value);
  37. public static void WriteDecimalNullable(this NetworkWriter writer, decimal? value) => writer.WriteBlittableNullable(value);
  38. public static void WriteString(this NetworkWriter writer, string value)
  39. {
  40. // write 0 for null support, increment real size by 1
  41. // (note: original HLAPI would write "" for null strings, but if a
  42. // string is null on the server then it should also be null
  43. // on the client)
  44. if (value == null)
  45. {
  46. writer.WriteUShort(0);
  47. return;
  48. }
  49. // WriteString copies into the buffer manually.
  50. // need to ensure capacity here first, manually.
  51. int maxSize = writer.encoding.GetMaxByteCount(value.Length);
  52. writer.EnsureCapacity(writer.Position + 2 + maxSize); // 2 bytes position + N bytes encoding
  53. // encode it into the buffer first.
  54. // reserve 2 bytes for header after we know how much was written.
  55. int written = writer.encoding.GetBytes(value, 0, value.Length, writer.buffer, writer.Position + 2);
  56. // check if within max size, otherwise Reader can't read it.
  57. if (written > NetworkWriter.MaxStringLength)
  58. throw new IndexOutOfRangeException($"NetworkWriter.WriteString - Value too long: {written} bytes. Limit: {NetworkWriter.MaxStringLength} bytes");
  59. // .Position is unchanged, so fill in the size header now.
  60. // we already ensured that max size fits into ushort.max-1.
  61. writer.WriteUShort(checked((ushort)(written + 1))); // Position += 2
  62. // now update position by what was written above
  63. writer.Position += written;
  64. }
  65. // Weaver needs a write function with just one byte[] parameter
  66. // (we don't name it .Write(byte[]) because it's really a WriteBytesAndSize since we write size / null info too)
  67. public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer)
  68. {
  69. // buffer might be null, so we can't use .Length in that case
  70. writer.WriteBytesAndSize(buffer, 0, buffer != null ? buffer.Length : 0);
  71. }
  72. // for byte arrays with dynamic size, where the reader doesn't know how many will come
  73. // (like an inventory with different items etc.)
  74. public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer, int offset, int count)
  75. {
  76. // null is supported because [SyncVar]s might be structs with null byte[] arrays
  77. // write 0 for null array, increment normal size by 1 to save bandwidth
  78. // (using size=-1 for null would limit max size to 32kb instead of 64kb)
  79. if (buffer == null)
  80. {
  81. writer.WriteUInt(0u);
  82. return;
  83. }
  84. writer.WriteUInt(checked((uint)count) + 1u);
  85. writer.WriteBytes(buffer, offset, count);
  86. }
  87. // writes ArraySegment of byte (most common type) and size header
  88. public static void WriteArraySegmentAndSize(this NetworkWriter writer, ArraySegment<byte> segment)
  89. {
  90. writer.WriteBytesAndSize(segment.Array, segment.Offset, segment.Count);
  91. }
  92. // writes ArraySegment of any type, and size header
  93. public static void WriteArraySegment<T>(this NetworkWriter writer, ArraySegment<T> segment)
  94. {
  95. int length = segment.Count;
  96. writer.WriteInt(length);
  97. for (int i = 0; i < length; i++)
  98. {
  99. writer.Write(segment.Array[segment.Offset + i]);
  100. }
  101. }
  102. public static void WriteVector2(this NetworkWriter writer, Vector2 value) => writer.WriteBlittable(value);
  103. public static void WriteVector2Nullable(this NetworkWriter writer, Vector2? value) => writer.WriteBlittableNullable(value);
  104. public static void WriteVector3(this NetworkWriter writer, Vector3 value) => writer.WriteBlittable(value);
  105. public static void WriteVector3Nullable(this NetworkWriter writer, Vector3? value) => writer.WriteBlittableNullable(value);
  106. public static void WriteVector4(this NetworkWriter writer, Vector4 value) => writer.WriteBlittable(value);
  107. public static void WriteVector4Nullable(this NetworkWriter writer, Vector4? value) => writer.WriteBlittableNullable(value);
  108. public static void WriteVector2Int(this NetworkWriter writer, Vector2Int value) => writer.WriteBlittable(value);
  109. public static void WriteVector2IntNullable(this NetworkWriter writer, Vector2Int? value) => writer.WriteBlittableNullable(value);
  110. public static void WriteVector3Int(this NetworkWriter writer, Vector3Int value) => writer.WriteBlittable(value);
  111. public static void WriteVector3IntNullable(this NetworkWriter writer, Vector3Int? value) => writer.WriteBlittableNullable(value);
  112. public static void WriteColor(this NetworkWriter writer, Color value) => writer.WriteBlittable(value);
  113. public static void WriteColorNullable(this NetworkWriter writer, Color? value) => writer.WriteBlittableNullable(value);
  114. public static void WriteColor32(this NetworkWriter writer, Color32 value) => writer.WriteBlittable(value);
  115. public static void WriteColor32Nullable(this NetworkWriter writer, Color32? value) => writer.WriteBlittableNullable(value);
  116. public static void WriteQuaternion(this NetworkWriter writer, Quaternion value) => writer.WriteBlittable(value);
  117. public static void WriteQuaternionNullable(this NetworkWriter writer, Quaternion? value) => writer.WriteBlittableNullable(value);
  118. // Rect is a struct with properties instead of fields
  119. public static void WriteRect(this NetworkWriter writer, Rect value)
  120. {
  121. writer.WriteVector2(value.position);
  122. writer.WriteVector2(value.size);
  123. }
  124. public static void WriteRectNullable(this NetworkWriter writer, Rect? value)
  125. {
  126. writer.WriteBool(value.HasValue);
  127. if (value.HasValue)
  128. writer.WriteRect(value.Value);
  129. }
  130. // Plane is a struct with properties instead of fields
  131. public static void WritePlane(this NetworkWriter writer, Plane value)
  132. {
  133. writer.WriteVector3(value.normal);
  134. writer.WriteFloat(value.distance);
  135. }
  136. public static void WritePlaneNullable(this NetworkWriter writer, Plane? value)
  137. {
  138. writer.WriteBool(value.HasValue);
  139. if (value.HasValue)
  140. writer.WritePlane(value.Value);
  141. }
  142. // Ray is a struct with properties instead of fields
  143. public static void WriteRay(this NetworkWriter writer, Ray value)
  144. {
  145. writer.WriteVector3(value.origin);
  146. writer.WriteVector3(value.direction);
  147. }
  148. public static void WriteRayNullable(this NetworkWriter writer, Ray? value)
  149. {
  150. writer.WriteBool(value.HasValue);
  151. if (value.HasValue)
  152. writer.WriteRay(value.Value);
  153. }
  154. // LayerMask is a struct with properties instead of fields
  155. public static void WriteLayerMask(this NetworkWriter writer, LayerMask layerMask)
  156. {
  157. // 32 layers as a flags enum, max value of 496, we only need a UShort.
  158. writer.WriteUShort((ushort)layerMask.value);
  159. }
  160. public static void WriteLayerMaskNullable(this NetworkWriter writer, LayerMask? layerMask)
  161. {
  162. writer.WriteBool(layerMask.HasValue);
  163. if (layerMask.HasValue)
  164. writer.WriteLayerMask(layerMask.Value);
  165. }
  166. public static void WriteMatrix4x4(this NetworkWriter writer, Matrix4x4 value) => writer.WriteBlittable(value);
  167. public static void WriteMatrix4x4Nullable(this NetworkWriter writer, Matrix4x4? value) => writer.WriteBlittableNullable(value);
  168. public static void WriteGuid(this NetworkWriter writer, Guid value)
  169. {
  170. #if !UNITY_2021_3_OR_NEWER
  171. // Unity 2019 doesn't have Span yet
  172. byte[] data = value.ToByteArray();
  173. writer.WriteBytes(data, 0, data.Length);
  174. #else
  175. // WriteBlittable(Guid) isn't safe. see WriteBlittable comments.
  176. // Guid is Sequential, but we can't guarantee packing.
  177. // TryWriteBytes is safe and allocation free.
  178. writer.EnsureCapacity(writer.Position + 16);
  179. value.TryWriteBytes(new Span<byte>(writer.buffer, writer.Position, 16));
  180. writer.Position += 16;
  181. #endif
  182. }
  183. public static void WriteGuidNullable(this NetworkWriter writer, Guid? value)
  184. {
  185. writer.WriteBool(value.HasValue);
  186. if (value.HasValue)
  187. writer.WriteGuid(value.Value);
  188. }
  189. public static void WriteNetworkIdentity(this NetworkWriter writer, NetworkIdentity value)
  190. {
  191. if (value == null)
  192. {
  193. writer.WriteUInt(0);
  194. return;
  195. }
  196. // users might try to use unspawned / prefab GameObjects in
  197. // rpcs/cmds/syncvars/messages. they would be null on the other
  198. // end, and it might not be obvious why. let's make it obvious.
  199. // https://github.com/vis2k/Mirror/issues/2060
  200. //
  201. // => warning (instead of exception) because we also use a warning
  202. // if a GameObject doesn't have a NetworkIdentity component etc.
  203. if (value.netId == 0)
  204. Debug.LogWarning($"Attempted to serialize unspawned GameObject: {value.name}. Prefabs and unspawned GameObjects would always be null on the other side. Please spawn it before using it in [SyncVar]s/Rpcs/Cmds/NetworkMessages etc.");
  205. writer.WriteUInt(value.netId);
  206. }
  207. public static void WriteNetworkBehaviour(this NetworkWriter writer, NetworkBehaviour value)
  208. {
  209. if (value == null)
  210. {
  211. writer.WriteUInt(0);
  212. return;
  213. }
  214. // users might try to use unspawned / prefab NetworkBehaviours in
  215. // rpcs/cmds/syncvars/messages. they would be null on the other
  216. // end, and it might not be obvious why. let's make it obvious.
  217. // https://github.com/vis2k/Mirror/issues/2060
  218. // and more recently https://github.com/MirrorNetworking/Mirror/issues/3399
  219. //
  220. // => warning (instead of exception) because we also use a warning
  221. // when writing an unspawned NetworkIdentity
  222. if (value.netId == 0)
  223. {
  224. Debug.LogWarning($"Attempted to serialize unspawned NetworkBehaviour: of type {value.GetType()} on GameObject {value.name}. Prefabs and unspawned GameObjects would always be null on the other side. Please spawn it before using it in [SyncVar]s/Rpcs/Cmds/NetworkMessages etc.");
  225. writer.WriteUInt(0);
  226. return;
  227. }
  228. writer.WriteUInt(value.netId);
  229. writer.WriteByte(value.ComponentIndex);
  230. }
  231. public static void WriteTransform(this NetworkWriter writer, Transform value)
  232. {
  233. if (value == null)
  234. {
  235. writer.WriteUInt(0);
  236. return;
  237. }
  238. if (value.TryGetComponent(out NetworkIdentity identity))
  239. {
  240. writer.WriteUInt(identity.netId);
  241. }
  242. else
  243. {
  244. // if users attempt to pass a transform without NetworkIdentity
  245. // to a [Command] or [SyncVar], it should show an obvious warning.
  246. Debug.LogWarning($"Attempted to sync a Transform ({value}) which isn't networked. Transforms without a NetworkIdentity component can't be synced.");
  247. writer.WriteUInt(0);
  248. }
  249. }
  250. public static void WriteGameObject(this NetworkWriter writer, GameObject value)
  251. {
  252. if (value == null)
  253. {
  254. writer.WriteUInt(0);
  255. return;
  256. }
  257. // warn if the GameObject doesn't have a NetworkIdentity,
  258. if (!value.TryGetComponent(out NetworkIdentity identity))
  259. Debug.LogWarning($"Attempted to sync a GameObject ({value}) which isn't networked. GameObject without a NetworkIdentity component can't be synced.");
  260. // serialize the correct amount of data in any case to make sure
  261. // that the other end can read the expected amount of data too.
  262. writer.WriteNetworkIdentity(identity);
  263. }
  264. // while SyncList<T> is recommended for NetworkBehaviours,
  265. // structs may have .List<T> members which weaver needs to be able to
  266. // fully serialize for NetworkMessages etc.
  267. // note that Weaver/Writers/GenerateWriter() handles this manually.
  268. public static void WriteList<T>(this NetworkWriter writer, List<T> list)
  269. {
  270. // 'null' is encoded as '-1'
  271. if (list is null)
  272. {
  273. writer.WriteInt(-1);
  274. return;
  275. }
  276. // check if within max size, otherwise Reader can't read it.
  277. if (list.Count > NetworkReader.AllocationLimit)
  278. throw new IndexOutOfRangeException($"NetworkWriter.WriteList - List<{typeof(T)}> too big: {list.Count} elements. Limit: {NetworkReader.AllocationLimit}");
  279. writer.WriteInt(list.Count);
  280. for (int i = 0; i < list.Count; i++)
  281. writer.Write(list[i]);
  282. }
  283. // while SyncSet<T> is recommended for NetworkBehaviours,
  284. // structs may have .Set<T> members which weaver needs to be able to
  285. // fully serialize for NetworkMessages etc.
  286. // note that Weaver/Writers/GenerateWriter() handles this manually.
  287. // TODO writer not found. need to adjust weaver first. see tests.
  288. /*
  289. public static void WriteHashSet<T>(this NetworkWriter writer, HashSet<T> hashSet)
  290. {
  291. if (hashSet is null)
  292. {
  293. writer.WriteInt(-1);
  294. return;
  295. }
  296. writer.WriteInt(hashSet.Count);
  297. foreach (T item in hashSet)
  298. writer.Write(item);
  299. }
  300. */
  301. public static void WriteArray<T>(this NetworkWriter writer, T[] array)
  302. {
  303. // 'null' is encoded as '-1'
  304. if (array is null)
  305. {
  306. writer.WriteInt(-1);
  307. return;
  308. }
  309. // check if within max size, otherwise Reader can't read it.
  310. if (array.Length > NetworkReader.AllocationLimit)
  311. throw new IndexOutOfRangeException($"NetworkWriter.WriteArray - Array<{typeof(T)}> too big: {array.Length} elements. Limit: {NetworkReader.AllocationLimit}");
  312. writer.WriteInt(array.Length);
  313. for (int i = 0; i < array.Length; i++)
  314. writer.Write(array[i]);
  315. }
  316. public static void WriteUri(this NetworkWriter writer, Uri uri)
  317. {
  318. writer.WriteString(uri?.ToString());
  319. }
  320. public static void WriteTexture2D(this NetworkWriter writer, Texture2D texture2D)
  321. {
  322. // TODO allocation protection when sending textures to server.
  323. // currently can allocate 32k x 32k x 4 byte = 3.8 GB
  324. // support 'null' textures for [SyncVar]s etc.
  325. // https://github.com/vis2k/Mirror/issues/3144
  326. // simply send -1 for width.
  327. if (texture2D == null)
  328. {
  329. writer.WriteShort(-1);
  330. return;
  331. }
  332. // check if within max size, otherwise Reader can't read it.
  333. int totalSize = texture2D.width * texture2D.height;
  334. if (totalSize > NetworkReader.AllocationLimit)
  335. throw new IndexOutOfRangeException($"NetworkWriter.WriteTexture2D - Texture2D total size (width*height) too big: {totalSize}. Limit: {NetworkReader.AllocationLimit}");
  336. // write dimensions first so reader can create the texture with size
  337. // 32k x 32k short is more than enough
  338. writer.WriteShort((short)texture2D.width);
  339. writer.WriteShort((short)texture2D.height);
  340. writer.WriteArray(texture2D.GetPixels32());
  341. }
  342. public static void WriteSprite(this NetworkWriter writer, Sprite sprite)
  343. {
  344. // support 'null' textures for [SyncVar]s etc.
  345. // https://github.com/vis2k/Mirror/issues/3144
  346. // simply send a 'null' for texture content.
  347. if (sprite == null)
  348. {
  349. writer.WriteTexture2D(null);
  350. return;
  351. }
  352. writer.WriteTexture2D(sprite.texture);
  353. writer.WriteRect(sprite.rect);
  354. writer.WriteVector2(sprite.pivot);
  355. }
  356. public static void WriteDateTime(this NetworkWriter writer, DateTime dateTime)
  357. {
  358. writer.WriteDouble(dateTime.ToOADate());
  359. }
  360. public static void WriteDateTimeNullable(this NetworkWriter writer, DateTime? dateTime)
  361. {
  362. writer.WriteBool(dateTime.HasValue);
  363. if (dateTime.HasValue)
  364. writer.WriteDouble(dateTime.Value.ToOADate());
  365. }
  366. }
  367. }