No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FixedStringAppendMethods.cs 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. using Unity.Collections.LowLevel.Unsafe;
  2. namespace Unity.Collections
  3. {
  4. /// <summary>
  5. /// Provides extension methods for FixedString*N*Bytes.
  6. /// </summary>
  7. [GenerateTestsForBurstCompatibility]
  8. public unsafe static partial class FixedStringMethods
  9. {
  10. /// <summary>
  11. /// Appends a Unicode.Rune to this string.
  12. /// </summary>
  13. /// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
  14. /// <param name="fs">A FixedString*N*Bytes.</param>
  15. /// <param name="rune">A Unicode.Rune to append.</param>
  16. /// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
  17. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
  18. public static FormatError Append<T>(ref this T fs, Unicode.Rune rune)
  19. where T : unmanaged, INativeList<byte>, IUTF8Bytes
  20. {
  21. var len = fs.Length;
  22. var runeLen = rune.LengthInUtf8Bytes();
  23. if (!fs.TryResize(len + runeLen, NativeArrayOptions.UninitializedMemory))
  24. return FormatError.Overflow;
  25. return fs.Write(ref len, rune);
  26. }
  27. /// <summary>
  28. /// Appends a char to this string.
  29. /// </summary>
  30. /// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
  31. /// <param name="fs">A FixedString*N*Bytes.</param>
  32. /// <param name="ch">A char to append.</param>
  33. /// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
  34. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
  35. public static FormatError Append<T>(ref this T fs, char ch)
  36. where T : unmanaged, INativeList<byte>, IUTF8Bytes
  37. {
  38. return fs.Append((Unicode.Rune) ch);
  39. }
  40. /// <summary>
  41. /// Appends a byte to this string.
  42. /// </summary>
  43. /// <remarks>
  44. /// No validation is performed: it is your responsibility for the data to be valid UTF-8 when you're done appending bytes.
  45. /// </remarks>
  46. /// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
  47. /// <param name="fs">A FixedString*N*Bytes.</param>
  48. /// <param name="a">A byte to append.</param>
  49. /// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
  50. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
  51. public static FormatError AppendRawByte<T>(ref this T fs, byte a)
  52. where T : unmanaged, INativeList<byte>, IUTF8Bytes
  53. {
  54. var origLength = fs.Length;
  55. if (!fs.TryResize(origLength + 1, NativeArrayOptions.UninitializedMemory))
  56. return FormatError.Overflow;
  57. fs.GetUnsafePtr()[origLength] = a;
  58. return FormatError.None;
  59. }
  60. /// <summary>
  61. /// Appends a Unicode.Rune a number of times to this string.
  62. /// </summary>
  63. /// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
  64. /// <param name="fs">A FixedString*N*Bytes.</param>
  65. /// <param name="rune">A Unicode.Rune to append some number of times.</param>
  66. /// <param name="count">The number of times to append the rune.</param>
  67. /// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
  68. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
  69. public static FormatError Append<T>(ref this T fs, Unicode.Rune rune, int count)
  70. where T : unmanaged, INativeList<byte>, IUTF8Bytes
  71. {
  72. var origLength = fs.Length;
  73. if (!fs.TryResize(origLength + rune.LengthInUtf8Bytes() * count, NativeArrayOptions.UninitializedMemory))
  74. return FormatError.Overflow;
  75. var cap = fs.Capacity;
  76. var b = fs.GetUnsafePtr();
  77. int offset = origLength;
  78. for (int i = 0; i < count; ++i)
  79. {
  80. var error = Unicode.UcsToUtf8(b, ref offset, cap, rune);
  81. if (error != ConversionError.None)
  82. return FormatError.Overflow;
  83. }
  84. return FormatError.None;
  85. }
  86. /// <summary>
  87. /// Appends a number (converted to UTF-8 characters) to this string.
  88. /// </summary>
  89. /// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
  90. /// <param name="fs">A FixedString*N*Bytes.</param>
  91. /// <param name="input">A long integer to append to the string.</param>
  92. /// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
  93. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
  94. public static FormatError Append<T>(ref this T fs, long input)
  95. where T : unmanaged, INativeList<byte>, IUTF8Bytes
  96. {
  97. const int maximumDigits = 20;
  98. var temp = stackalloc byte[maximumDigits];
  99. int offset = maximumDigits;
  100. if (input >= 0)
  101. {
  102. do
  103. {
  104. var digit = (byte)(input % 10);
  105. temp[--offset] = (byte)('0' + digit);
  106. input /= 10;
  107. }
  108. while (input != 0);
  109. }
  110. else
  111. {
  112. do
  113. {
  114. var digit = (byte)(input % 10);
  115. temp[--offset] = (byte)('0' - digit);
  116. input /= 10;
  117. }
  118. while (input != 0);
  119. temp[--offset] = (byte)'-';
  120. }
  121. return fs.Append(temp + offset, maximumDigits - offset);
  122. }
  123. /// <summary>
  124. /// Appends a number (converted to UTF-8 characters) to this string.
  125. /// </summary>
  126. /// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
  127. /// <param name="fs">A FixedString*N*Bytes.</param>
  128. /// <param name="input">An int to append to the string.</param>
  129. /// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
  130. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
  131. public static FormatError Append<T>(ref this T fs, int input)
  132. where T : unmanaged, INativeList<byte>, IUTF8Bytes
  133. {
  134. return fs.Append((long)input);
  135. }
  136. /// <summary>
  137. /// Appends a number (converted to UTF-8 characters) to this string.
  138. /// </summary>
  139. /// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
  140. /// <param name="fs">A FixedString*N*Bytes.</param>
  141. /// <param name="input">A ulong integer to append to the string.</param>
  142. /// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
  143. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
  144. public static FormatError Append<T>(ref this T fs, ulong input)
  145. where T : unmanaged, INativeList<byte>, IUTF8Bytes
  146. {
  147. const int maximumDigits = 20;
  148. var temp = stackalloc byte[maximumDigits];
  149. int offset = maximumDigits;
  150. do
  151. {
  152. var digit = (byte)(input % 10);
  153. temp[--offset] = (byte)('0' + digit);
  154. input /= 10;
  155. }
  156. while (input != 0);
  157. return fs.Append(temp + offset, maximumDigits - offset);
  158. }
  159. /// <summary>
  160. /// Appends a number (converted to UTF-8 characters) to this string.
  161. /// </summary>
  162. /// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
  163. /// <param name="fs">A FixedString*N*Bytes.</param>
  164. /// <param name="input">A uint to append to the string.</param>
  165. /// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
  166. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
  167. public static FormatError Append<T>(ref this T fs, uint input)
  168. where T : unmanaged, INativeList<byte>, IUTF8Bytes
  169. {
  170. return fs.Append((ulong)input);
  171. }
  172. /// <summary>
  173. /// Appends a number (converted to UTF-8 characters) to this string.
  174. /// </summary>
  175. /// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
  176. /// <param name="fs">A FixedString*N*Bytes.</param>
  177. /// <param name="input">A float to append to the string.</param>
  178. /// <param name="decimalSeparator">The character to use as the decimal separator. Defaults to a period ('.').</param>
  179. /// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
  180. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
  181. public static FormatError Append<T>(ref this T fs, float input, char decimalSeparator = '.')
  182. where T : unmanaged, INativeList<byte>, IUTF8Bytes
  183. {
  184. FixedStringUtils.UintFloatUnion ufu = new FixedStringUtils.UintFloatUnion();
  185. ufu.floatValue = input;
  186. var sign = ufu.uintValue >> 31;
  187. ufu.uintValue &= ~(1 << 31);
  188. FormatError error;
  189. if ((ufu.uintValue & 0x7F800000) == 0x7F800000)
  190. {
  191. if (ufu.uintValue == 0x7F800000)
  192. {
  193. if (sign != 0 && ((error = fs.Append('-')) != FormatError.None))
  194. return error;
  195. return fs.Append('I', 'n', 'f', 'i', 'n', 'i', 't', 'y');
  196. }
  197. return fs.Append('N', 'a', 'N');
  198. }
  199. if (sign != 0 && ufu.uintValue != 0) // C# prints -0 as 0
  200. if ((error = fs.Append('-')) != FormatError.None)
  201. return error;
  202. ulong decimalMantissa = 0;
  203. int decimalExponent = 0;
  204. FixedStringUtils.Base2ToBase10(ref decimalMantissa, ref decimalExponent, ufu.floatValue);
  205. var backwards = stackalloc char[9];
  206. int decimalDigits = 0;
  207. do
  208. {
  209. if (decimalDigits >= 9)
  210. return FormatError.Overflow;
  211. var decimalDigit = decimalMantissa % 10;
  212. backwards[8 - decimalDigits++] = (char)('0' + decimalDigit);
  213. decimalMantissa /= 10;
  214. }
  215. while (decimalMantissa > 0);
  216. char *ascii = backwards + 9 - decimalDigits;
  217. var leadingZeroes = -decimalExponent - decimalDigits + 1;
  218. if (leadingZeroes > 0)
  219. {
  220. if (leadingZeroes > 4)
  221. return fs.AppendScientific(ascii, decimalDigits, decimalExponent, decimalSeparator);
  222. if ((error = fs.Append('0', decimalSeparator)) != FormatError.None)
  223. return error;
  224. --leadingZeroes;
  225. while (leadingZeroes > 0)
  226. {
  227. if ((error = fs.Append('0')) != FormatError.None)
  228. return error;
  229. --leadingZeroes;
  230. }
  231. for (var i = 0; i < decimalDigits; ++i)
  232. {
  233. if ((error = fs.Append(ascii[i])) != FormatError.None)
  234. return error;
  235. }
  236. return FormatError.None;
  237. }
  238. var trailingZeroes = decimalExponent;
  239. if (trailingZeroes > 0)
  240. {
  241. if (trailingZeroes > 4)
  242. return fs.AppendScientific(ascii, decimalDigits, decimalExponent, decimalSeparator);
  243. for (var i = 0; i < decimalDigits; ++i)
  244. {
  245. if ((error = fs.Append(ascii[i])) != FormatError.None)
  246. return error;
  247. }
  248. while (trailingZeroes > 0)
  249. {
  250. if ((error = fs.Append('0')) != FormatError.None)
  251. return error;
  252. --trailingZeroes;
  253. }
  254. return FormatError.None;
  255. }
  256. var indexOfSeparator = decimalDigits + decimalExponent;
  257. for (var i = 0; i < decimalDigits; ++i)
  258. {
  259. if (i == indexOfSeparator)
  260. if ((error = fs.Append(decimalSeparator)) != FormatError.None)
  261. return error;
  262. if ((error = fs.Append(ascii[i])) != FormatError.None)
  263. return error;
  264. }
  265. return FormatError.None;
  266. }
  267. /// <summary>
  268. /// Appends another string to this string.
  269. /// </summary>
  270. /// <remarks>
  271. /// When the method returns an error, the destination string is not modified.
  272. /// </remarks>
  273. /// <typeparam name="T">The type of the destination string.</typeparam>
  274. /// <typeparam name="T2">The type of the source string.</typeparam>
  275. /// <param name="fs">The destination string.</param>
  276. /// <param name="input">The source string.</param>
  277. /// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the destination string is exceeded.</returns>
  278. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes), typeof(FixedString128Bytes) })]
  279. public static FormatError Append<T,T2>(ref this T fs, in T2 input)
  280. where T : unmanaged, INativeList<byte>, IUTF8Bytes
  281. where T2 : unmanaged, INativeList<byte>, IUTF8Bytes
  282. {
  283. ref var inputRef = ref UnsafeUtilityExtensions.AsRef(input);
  284. return fs.Append(inputRef.GetUnsafePtr(), inputRef.Length);
  285. }
  286. /// <summary>
  287. /// Copies another string to this string (making the two strings equal).
  288. /// </summary>
  289. /// <remarks>
  290. /// When the method returns an error, the destination string is not modified.
  291. /// </remarks>
  292. /// <typeparam name="T">The type of the destination string.</typeparam>
  293. /// <typeparam name="T2">The type of the source string.</typeparam>
  294. /// <param name="fs">The destination string.</param>
  295. /// <param name="input">The source string.</param>
  296. /// <returns>CopyError.None if successful. Returns CopyError.Truncation if the source string is too large to fit in the destination.</returns>
  297. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes), typeof(FixedString128Bytes) })]
  298. public static CopyError CopyFrom<T, T2>(ref this T fs, in T2 input)
  299. where T : unmanaged, INativeList<byte>, IUTF8Bytes
  300. where T2 : unmanaged, INativeList<byte>, IUTF8Bytes
  301. {
  302. fs.Length = 0;
  303. var fe = Append(ref fs, input);
  304. if (fe != FormatError.None)
  305. return CopyError.Truncation;
  306. return CopyError.None;
  307. }
  308. /// <summary>
  309. /// Appends bytes to this string.
  310. /// </summary>
  311. /// <remarks>
  312. /// When the method returns an error, the destination string is not modified.
  313. ///
  314. /// No validation is performed: it is your responsibility for the destination to contain valid UTF-8 when you're done appending bytes.
  315. /// </remarks>
  316. /// <typeparam name="T">The type of the destination string.</typeparam>
  317. /// <param name="fs">The destination string.</param>
  318. /// <param name="utf8Bytes">The bytes to append.</param>
  319. /// <param name="utf8BytesLength">The number of bytes to append.</param>
  320. /// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the destination string is exceeded.</returns>
  321. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
  322. public unsafe static FormatError Append<T>(ref this T fs, byte* utf8Bytes, int utf8BytesLength)
  323. where T : unmanaged, INativeList<byte>, IUTF8Bytes
  324. {
  325. var origLength = fs.Length;
  326. if (!fs.TryResize(origLength + utf8BytesLength, NativeArrayOptions.UninitializedMemory))
  327. return FormatError.Overflow;
  328. UnsafeUtility.MemCpy(fs.GetUnsafePtr() + origLength, utf8Bytes, utf8BytesLength);
  329. return FormatError.None;
  330. }
  331. /// <summary>
  332. /// Appends another string to this string.
  333. /// </summary>
  334. /// <remarks>
  335. /// When the method returns an error, the destination string is not modified.
  336. /// </remarks>
  337. /// <typeparam name="T">The type of the destination string.</typeparam>
  338. /// <param name="fs">The destination string.</param>
  339. /// <param name="s">The string to append.</param>
  340. /// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the destination string is exceeded.</returns>
  341. [ExcludeFromBurstCompatTesting("Takes managed string")]
  342. public unsafe static FormatError Append<T>(ref this T fs, string s)
  343. where T : unmanaged, INativeList<byte>, IUTF8Bytes
  344. {
  345. // we don't know how big the expansion from UTF16 to UTF8 will be, so we account for worst case.
  346. int worstCaseCapacity = s.Length * 4;
  347. byte* utf8Bytes = stackalloc byte[worstCaseCapacity];
  348. int utf8Len;
  349. fixed (char* chars = s)
  350. {
  351. var err = UTF8ArrayUnsafeUtility.Copy(utf8Bytes, out utf8Len, worstCaseCapacity, chars, s.Length);
  352. if (err != CopyError.None)
  353. {
  354. return FormatError.Overflow;
  355. }
  356. }
  357. return fs.Append(utf8Bytes, utf8Len);
  358. }
  359. /// <summary>
  360. /// Copies another string to this string (making the two strings equal).
  361. /// Replaces any existing content of the FixedString.
  362. /// </summary>
  363. /// <remarks>
  364. /// When the method returns an error, the destination string is not modified.
  365. /// </remarks>
  366. /// <typeparam name="T">The type of the destination string.</typeparam>
  367. /// <param name="fs">The destination string.</param>
  368. /// <param name="s">The source string.</param>
  369. /// <returns>CopyError.None if successful. Returns CopyError.Truncation if the source string is too large to fit in the destination.</returns>
  370. [ExcludeFromBurstCompatTesting("Takes managed string")]
  371. public static CopyError CopyFrom<T>(ref this T fs, string s)
  372. where T : unmanaged, INativeList<byte>, IUTF8Bytes
  373. {
  374. fs.Length = 0;
  375. var fe = Append(ref fs, s);
  376. if (fe != FormatError.None)
  377. return CopyError.Truncation;
  378. return CopyError.None;
  379. }
  380. /// <summary>
  381. /// Copies another string to this string. If the string exceeds the capacity it will be truncated.
  382. /// Replaces any existing content of the FixedString.
  383. /// </summary>
  384. /// <typeparam name="T">The type of the destination string.</typeparam>
  385. /// <param name="fs">The destination string.</param>
  386. /// <param name="s">The source string.</param>
  387. /// <returns>CopyError.None if successful. Returns CopyError.Truncation if the source string is too large to fit in the destination.</returns>
  388. [ExcludeFromBurstCompatTesting("Takes managed string")]
  389. public static CopyError CopyFromTruncated<T>(ref this T fs, string s)
  390. where T : unmanaged, INativeList<byte>, IUTF8Bytes
  391. {
  392. int utf8Len;
  393. fixed (char* chars = s)
  394. {
  395. var error = UTF8ArrayUnsafeUtility.Copy(fs.GetUnsafePtr(), out utf8Len, fs.Capacity, chars, s.Length);
  396. fs.Length = utf8Len;
  397. return error;
  398. }
  399. }
  400. /// <summary>
  401. /// Copies another string to this string. If the string exceeds the capacity it will be truncated.
  402. /// </summary>
  403. /// <remarks>
  404. /// When the method returns an error, the destination string is not modified.
  405. /// </remarks>
  406. /// <typeparam name="T">The type of the destination string.</typeparam>
  407. /// <typeparam name="T2">The type of the source string.</typeparam>
  408. /// <param name="fs">The destination string.</param>
  409. /// <param name="input">The source string.</param>
  410. /// <returns>CopyError.None if successful. Returns CopyError.Truncation if the source string is too large to fit in the destination.</returns>
  411. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes), typeof(FixedString128Bytes) })]
  412. public static CopyError CopyFromTruncated<T, T2>(ref this T fs, in T2 input)
  413. where T : unmanaged, INativeList<byte>, IUTF8Bytes
  414. where T2 : unmanaged, INativeList<byte>, IUTF8Bytes
  415. {
  416. var error = UTF8ArrayUnsafeUtility.Copy(fs.GetUnsafePtr(), out int utf8Len, fs.Capacity, input.GetUnsafePtr(), input.Length);
  417. fs.Length = utf8Len;
  418. return error;
  419. }
  420. }
  421. }