NetworkWriterExtensions.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Runtime.InteropServices;
  4. using UnityEngine;
  5. namespace Mirror
  6. {
  7. // Mirror's Weaver automatically detects all NetworkWriter function types,
  8. // but they do all need to be extensions.
  9. public static class NetworkWriterExtensions
  10. {
  11. public static void WriteByte(this NetworkWriter writer, byte value) => writer.WriteBlittable(value);
  12. public static void WriteByteNullable(this NetworkWriter writer, byte? value) => writer.WriteBlittableNullable(value);
  13. public static void WriteSByte(this NetworkWriter writer, sbyte value) => writer.WriteBlittable(value);
  14. public static void WriteSByteNullable(this NetworkWriter writer, sbyte? value) => writer.WriteBlittableNullable(value);
  15. // char is not blittable. convert to ushort.
  16. public static void WriteChar(this NetworkWriter writer, char value) => writer.WriteBlittable((ushort)value);
  17. public static void WriteCharNullable(this NetworkWriter writer, char? value) => writer.WriteBlittableNullable((ushort?)value);
  18. // bool is not blittable. convert to byte.
  19. public static void WriteBool(this NetworkWriter writer, bool value) => writer.WriteBlittable((byte)(value ? 1 : 0));
  20. public static void WriteBoolNullable(this NetworkWriter writer, bool? value) => writer.WriteBlittableNullable(value.HasValue ? ((byte)(value.Value ? 1 : 0)) : new byte?());
  21. public static void WriteShort(this NetworkWriter writer, short value) => writer.WriteBlittable(value);
  22. public static void WriteShortNullable(this NetworkWriter writer, short? value) => writer.WriteBlittableNullable(value);
  23. public static void WriteUShort(this NetworkWriter writer, ushort value) => writer.WriteBlittable(value);
  24. public static void WriteUShortNullable(this NetworkWriter writer, ushort? value) => writer.WriteBlittableNullable(value);
  25. public static void WriteInt(this NetworkWriter writer, int value) => writer.WriteBlittable(value);
  26. public static void WriteIntNullable(this NetworkWriter writer, int? value) => writer.WriteBlittableNullable(value);
  27. public static void WriteUInt(this NetworkWriter writer, uint value) => writer.WriteBlittable(value);
  28. public static void WriteUIntNullable(this NetworkWriter writer, uint? value) => writer.WriteBlittableNullable(value);
  29. public static void WriteLong(this NetworkWriter writer, long value) => writer.WriteBlittable(value);
  30. public static void WriteLongNullable(this NetworkWriter writer, long? value) => writer.WriteBlittableNullable(value);
  31. public static void WriteULong(this NetworkWriter writer, ulong value) => writer.WriteBlittable(value);
  32. public static void WriteULongNullable(this NetworkWriter writer, ulong? value) => writer.WriteBlittableNullable(value);
  33. public static void WriteFloat(this NetworkWriter writer, float value) => writer.WriteBlittable(value);
  34. public static void WriteFloatNullable(this NetworkWriter writer, float? value) => writer.WriteBlittableNullable(value);
  35. [StructLayout(LayoutKind.Explicit)]
  36. internal struct UIntDouble
  37. {
  38. [FieldOffset(0)]
  39. public double doubleValue;
  40. [FieldOffset(0)]
  41. public ulong longValue;
  42. }
  43. public static void WriteDouble(this NetworkWriter writer, double value)
  44. {
  45. // DEBUG: try to find the exact value that fails.
  46. //UIntDouble convert = new UIntDouble{doubleValue = value};
  47. //Debug.Log($"=> NetworkWriter.WriteDouble: {value} => 0x{convert.longValue:X8}");
  48. writer.WriteBlittable(value);
  49. }
  50. public static void WriteDoubleNullable(this NetworkWriter writer, double? value) => writer.WriteBlittableNullable(value);
  51. public static void WriteDecimal(this NetworkWriter writer, decimal value) => writer.WriteBlittable(value);
  52. public static void WriteDecimalNullable(this NetworkWriter writer, decimal? value) => writer.WriteBlittableNullable(value);
  53. public static void WriteString(this NetworkWriter writer, string value)
  54. {
  55. // write 0 for null support, increment real size by 1
  56. // (note: original HLAPI would write "" for null strings, but if a
  57. // string is null on the server then it should also be null
  58. // on the client)
  59. if (value == null)
  60. {
  61. writer.WriteUShort(0);
  62. return;
  63. }
  64. // WriteString copies into the buffer manually.
  65. // need to ensure capacity here first, manually.
  66. int maxSize = writer.encoding.GetMaxByteCount(value.Length);
  67. writer.EnsureCapacity(writer.Position + 2 + maxSize); // 2 bytes position + N bytes encoding
  68. // encode it into the buffer first.
  69. // reserve 2 bytes for header after we know how much was written.
  70. int written = writer.encoding.GetBytes(value, 0, value.Length, writer.buffer, writer.Position + 2);
  71. // check if within max size
  72. if (written >= NetworkWriter.MaxStringLength)
  73. throw new IndexOutOfRangeException($"NetworkWriter.Write(string) too long: {written}. Limit: {NetworkWriter.MaxStringLength}");
  74. // .Position is unchanged, so fill in the size header now.
  75. // we already ensured that max size fits into ushort.max-1.
  76. writer.WriteUShort(checked((ushort)(written + 1))); // Position += 2
  77. // now update position by what was written above
  78. writer.Position += written;
  79. }
  80. public static void WriteBytesAndSizeSegment(this NetworkWriter writer, ArraySegment<byte> buffer)
  81. {
  82. writer.WriteBytesAndSize(buffer.Array, buffer.Offset, buffer.Count);
  83. }
  84. // Weaver needs a write function with just one byte[] parameter
  85. // (we don't name it .Write(byte[]) because it's really a WriteBytesAndSize since we write size / null info too)
  86. public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer)
  87. {
  88. // buffer might be null, so we can't use .Length in that case
  89. writer.WriteBytesAndSize(buffer, 0, buffer != null ? buffer.Length : 0);
  90. }
  91. // for byte arrays with dynamic size, where the reader doesn't know how many will come
  92. // (like an inventory with different items etc.)
  93. public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer, int offset, int count)
  94. {
  95. // null is supported because [SyncVar]s might be structs with null byte[] arrays
  96. // write 0 for null array, increment normal size by 1 to save bandwidth
  97. // (using size=-1 for null would limit max size to 32kb instead of 64kb)
  98. if (buffer == null)
  99. {
  100. writer.WriteUInt(0u);
  101. return;
  102. }
  103. writer.WriteUInt(checked((uint)count) + 1u);
  104. writer.WriteBytes(buffer, offset, count);
  105. }
  106. public static void WriteArraySegment<T>(this NetworkWriter writer, ArraySegment<T> segment)
  107. {
  108. int length = segment.Count;
  109. writer.WriteInt(length);
  110. for (int i = 0; i < length; i++)
  111. {
  112. writer.Write(segment.Array[segment.Offset + i]);
  113. }
  114. }
  115. public static void WriteVector2(this NetworkWriter writer, Vector2 value) => writer.WriteBlittable(value);
  116. public static void WriteVector2Nullable(this NetworkWriter writer, Vector2? value) => writer.WriteBlittableNullable(value);
  117. public static void WriteVector3(this NetworkWriter writer, Vector3 value) => writer.WriteBlittable(value);
  118. public static void WriteVector3Nullable(this NetworkWriter writer, Vector3? value) => writer.WriteBlittableNullable(value);
  119. public static void WriteVector4(this NetworkWriter writer, Vector4 value) => writer.WriteBlittable(value);
  120. public static void WriteVector4Nullable(this NetworkWriter writer, Vector4? value) => writer.WriteBlittableNullable(value);
  121. public static void WriteVector2Int(this NetworkWriter writer, Vector2Int value) => writer.WriteBlittable(value);
  122. public static void WriteVector2IntNullable(this NetworkWriter writer, Vector2Int? value) => writer.WriteBlittableNullable(value);
  123. public static void WriteVector3Int(this NetworkWriter writer, Vector3Int value) => writer.WriteBlittable(value);
  124. public static void WriteVector3IntNullable(this NetworkWriter writer, Vector3Int? value) => writer.WriteBlittableNullable(value);
  125. public static void WriteColor(this NetworkWriter writer, Color value) => writer.WriteBlittable(value);
  126. public static void WriteColorNullable(this NetworkWriter writer, Color? value) => writer.WriteBlittableNullable(value);
  127. public static void WriteColor32(this NetworkWriter writer, Color32 value) => writer.WriteBlittable(value);
  128. public static void WriteColor32Nullable(this NetworkWriter writer, Color32? value) => writer.WriteBlittableNullable(value);
  129. public static void WriteQuaternion(this NetworkWriter writer, Quaternion value) => writer.WriteBlittable(value);
  130. public static void WriteQuaternionNullable(this NetworkWriter writer, Quaternion? value) => writer.WriteBlittableNullable(value);
  131. public static void WriteRect(this NetworkWriter writer, Rect value) => writer.WriteBlittable(value);
  132. public static void WriteRectNullable(this NetworkWriter writer, Rect? value) => writer.WriteBlittableNullable(value);
  133. public static void WritePlane(this NetworkWriter writer, Plane value) => writer.WriteBlittable(value);
  134. public static void WritePlaneNullable(this NetworkWriter writer, Plane? value) => writer.WriteBlittableNullable(value);
  135. public static void WriteRay(this NetworkWriter writer, Ray value) => writer.WriteBlittable(value);
  136. public static void WriteRayNullable(this NetworkWriter writer, Ray? value) => writer.WriteBlittableNullable(value);
  137. public static void WriteMatrix4x4(this NetworkWriter writer, Matrix4x4 value) => writer.WriteBlittable(value);
  138. public static void WriteMatrix4x4Nullable(this NetworkWriter writer, Matrix4x4? value) => writer.WriteBlittableNullable(value);
  139. public static void WriteGuid(this NetworkWriter writer, Guid value)
  140. {
  141. #if !UNITY_2021_3_OR_NEWER
  142. // Unity 2019 doesn't have Span yet
  143. byte[] data = value.ToByteArray();
  144. writer.WriteBytes(data, 0, data.Length);
  145. #else
  146. // WriteBlittable(Guid) isn't safe. see WriteBlittable comments.
  147. // Guid is Sequential, but we can't guarantee packing.
  148. // TryWriteBytes is safe and allocation free.
  149. writer.EnsureCapacity(writer.Position + 16);
  150. value.TryWriteBytes(new Span<byte>(writer.buffer, writer.Position, 16));
  151. writer.Position += 16;
  152. #endif
  153. }
  154. public static void WriteGuidNullable(this NetworkWriter writer, Guid? value)
  155. {
  156. writer.WriteBool(value.HasValue);
  157. if (value.HasValue)
  158. writer.WriteGuid(value.Value);
  159. }
  160. public static void WriteNetworkIdentity(this NetworkWriter writer, NetworkIdentity value)
  161. {
  162. if (value == null)
  163. {
  164. writer.WriteUInt(0);
  165. return;
  166. }
  167. // users might try to use unspawned / prefab GameObjects in
  168. // rpcs/cmds/syncvars/messages. they would be null on the other
  169. // end, and it might not be obvious why. let's make it obvious.
  170. // https://github.com/vis2k/Mirror/issues/2060
  171. //
  172. // => warning (instead of exception) because we also use a warning
  173. // if a GameObject doesn't have a NetworkIdentity component etc.
  174. if (value.netId == 0)
  175. 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.");
  176. writer.WriteUInt(value.netId);
  177. }
  178. public static void WriteNetworkBehaviour(this NetworkWriter writer, NetworkBehaviour value)
  179. {
  180. if (value == null)
  181. {
  182. writer.WriteUInt(0);
  183. return;
  184. }
  185. writer.WriteUInt(value.netId);
  186. writer.WriteByte(value.ComponentIndex);
  187. }
  188. public static void WriteTransform(this NetworkWriter writer, Transform value)
  189. {
  190. if (value == null)
  191. {
  192. writer.WriteUInt(0);
  193. return;
  194. }
  195. NetworkIdentity identity = value.GetComponent<NetworkIdentity>();
  196. if (identity != null)
  197. {
  198. writer.WriteUInt(identity.netId);
  199. }
  200. else
  201. {
  202. Debug.LogWarning($"NetworkWriter {value} has no NetworkIdentity");
  203. writer.WriteUInt(0);
  204. }
  205. }
  206. public static void WriteGameObject(this NetworkWriter writer, GameObject value)
  207. {
  208. if (value == null)
  209. {
  210. writer.WriteUInt(0);
  211. return;
  212. }
  213. // warn if the GameObject doesn't have a NetworkIdentity,
  214. NetworkIdentity identity = value.GetComponent<NetworkIdentity>();
  215. if (identity == null)
  216. Debug.LogWarning($"NetworkWriter {value} has no NetworkIdentity");
  217. // serialize the correct amount of data in any case to make sure
  218. // that the other end can read the expected amount of data too.
  219. writer.WriteNetworkIdentity(identity);
  220. }
  221. public static void WriteList<T>(this NetworkWriter writer, List<T> list)
  222. {
  223. if (list is null)
  224. {
  225. writer.WriteInt(-1);
  226. return;
  227. }
  228. writer.WriteInt(list.Count);
  229. for (int i = 0; i < list.Count; i++)
  230. writer.Write(list[i]);
  231. }
  232. public static void WriteArray<T>(this NetworkWriter writer, T[] array)
  233. {
  234. if (array is null)
  235. {
  236. writer.WriteInt(-1);
  237. return;
  238. }
  239. writer.WriteInt(array.Length);
  240. for (int i = 0; i < array.Length; i++)
  241. writer.Write(array[i]);
  242. }
  243. public static void WriteUri(this NetworkWriter writer, Uri uri)
  244. {
  245. writer.WriteString(uri?.ToString());
  246. }
  247. public static void WriteTexture2D(this NetworkWriter writer, Texture2D texture2D)
  248. {
  249. // TODO allocation protection when sending textures to server.
  250. // currently can allocate 32k x 32k x 4 byte = 3.8 GB
  251. // support 'null' textures for [SyncVar]s etc.
  252. // https://github.com/vis2k/Mirror/issues/3144
  253. // simply send -1 for width.
  254. if (texture2D == null)
  255. {
  256. writer.WriteShort(-1);
  257. return;
  258. }
  259. // write dimensions first so reader can create the texture with size
  260. // 32k x 32k short is more than enough
  261. writer.WriteShort((short)texture2D.width);
  262. writer.WriteShort((short)texture2D.height);
  263. writer.WriteArray(texture2D.GetPixels32());
  264. }
  265. public static void WriteSprite(this NetworkWriter writer, Sprite sprite)
  266. {
  267. // support 'null' textures for [SyncVar]s etc.
  268. // https://github.com/vis2k/Mirror/issues/3144
  269. // simply send a 'null' for texture content.
  270. if (sprite == null)
  271. {
  272. writer.WriteTexture2D(null);
  273. return;
  274. }
  275. writer.WriteTexture2D(sprite.texture);
  276. writer.WriteRect(sprite.rect);
  277. writer.WriteVector2(sprite.pivot);
  278. }
  279. }
  280. }