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