using Unity.Collections.LowLevel.Unsafe; namespace Unity.Collections { /// /// Provides extension methods for FixedString*N*Bytes. /// [GenerateTestsForBurstCompatibility] public unsafe static partial class FixedStringMethods { /// /// Appends a Unicode.Rune to this string. /// /// The type of FixedString*N*Bytes. /// A FixedString*N*Bytes. /// A Unicode.Rune to append. /// FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })] public static FormatError Append(ref this T fs, Unicode.Rune rune) where T : unmanaged, INativeList, IUTF8Bytes { var len = fs.Length; var runeLen = rune.LengthInUtf8Bytes(); if (!fs.TryResize(len + runeLen, NativeArrayOptions.UninitializedMemory)) return FormatError.Overflow; return fs.Write(ref len, rune); } /// /// Appends a char to this string. /// /// The type of FixedString*N*Bytes. /// A FixedString*N*Bytes. /// A char to append. /// FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })] public static FormatError Append(ref this T fs, char ch) where T : unmanaged, INativeList, IUTF8Bytes { return fs.Append((Unicode.Rune) ch); } /// /// Appends a byte to this string. /// /// /// No validation is performed: it is your responsibility for the data to be valid UTF-8 when you're done appending bytes. /// /// The type of FixedString*N*Bytes. /// A FixedString*N*Bytes. /// A byte to append. /// FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })] public static FormatError AppendRawByte(ref this T fs, byte a) where T : unmanaged, INativeList, IUTF8Bytes { var origLength = fs.Length; if (!fs.TryResize(origLength + 1, NativeArrayOptions.UninitializedMemory)) return FormatError.Overflow; fs.GetUnsafePtr()[origLength] = a; return FormatError.None; } /// /// Appends a Unicode.Rune a number of times to this string. /// /// The type of FixedString*N*Bytes. /// A FixedString*N*Bytes. /// A Unicode.Rune to append some number of times. /// The number of times to append the rune. /// FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })] public static FormatError Append(ref this T fs, Unicode.Rune rune, int count) where T : unmanaged, INativeList, IUTF8Bytes { var origLength = fs.Length; if (!fs.TryResize(origLength + rune.LengthInUtf8Bytes() * count, NativeArrayOptions.UninitializedMemory)) return FormatError.Overflow; var cap = fs.Capacity; var b = fs.GetUnsafePtr(); int offset = origLength; for (int i = 0; i < count; ++i) { var error = Unicode.UcsToUtf8(b, ref offset, cap, rune); if (error != ConversionError.None) return FormatError.Overflow; } return FormatError.None; } /// /// Appends a number (converted to UTF-8 characters) to this string. /// /// The type of FixedString*N*Bytes. /// A FixedString*N*Bytes. /// A long integer to append to the string. /// FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })] public static FormatError Append(ref this T fs, long input) where T : unmanaged, INativeList, IUTF8Bytes { const int maximumDigits = 20; var temp = stackalloc byte[maximumDigits]; int offset = maximumDigits; if (input >= 0) { do { var digit = (byte)(input % 10); temp[--offset] = (byte)('0' + digit); input /= 10; } while (input != 0); } else { do { var digit = (byte)(input % 10); temp[--offset] = (byte)('0' - digit); input /= 10; } while (input != 0); temp[--offset] = (byte)'-'; } return fs.Append(temp + offset, maximumDigits - offset); } /// /// Appends a number (converted to UTF-8 characters) to this string. /// /// The type of FixedString*N*Bytes. /// A FixedString*N*Bytes. /// An int to append to the string. /// FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })] public static FormatError Append(ref this T fs, int input) where T : unmanaged, INativeList, IUTF8Bytes { return fs.Append((long)input); } /// /// Appends a number (converted to UTF-8 characters) to this string. /// /// The type of FixedString*N*Bytes. /// A FixedString*N*Bytes. /// A ulong integer to append to the string. /// FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })] public static FormatError Append(ref this T fs, ulong input) where T : unmanaged, INativeList, IUTF8Bytes { const int maximumDigits = 20; var temp = stackalloc byte[maximumDigits]; int offset = maximumDigits; do { var digit = (byte)(input % 10); temp[--offset] = (byte)('0' + digit); input /= 10; } while (input != 0); return fs.Append(temp + offset, maximumDigits - offset); } /// /// Appends a number (converted to UTF-8 characters) to this string. /// /// The type of FixedString*N*Bytes. /// A FixedString*N*Bytes. /// A uint to append to the string. /// FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })] public static FormatError Append(ref this T fs, uint input) where T : unmanaged, INativeList, IUTF8Bytes { return fs.Append((ulong)input); } /// /// Appends a number (converted to UTF-8 characters) to this string. /// /// The type of FixedString*N*Bytes. /// A FixedString*N*Bytes. /// A float to append to the string. /// The character to use as the decimal separator. Defaults to a period ('.'). /// FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })] public static FormatError Append(ref this T fs, float input, char decimalSeparator = '.') where T : unmanaged, INativeList, IUTF8Bytes { FixedStringUtils.UintFloatUnion ufu = new FixedStringUtils.UintFloatUnion(); ufu.floatValue = input; var sign = ufu.uintValue >> 31; ufu.uintValue &= ~(1 << 31); FormatError error; if ((ufu.uintValue & 0x7F800000) == 0x7F800000) { if (ufu.uintValue == 0x7F800000) { if (sign != 0 && ((error = fs.Append('-')) != FormatError.None)) return error; return fs.Append('I', 'n', 'f', 'i', 'n', 'i', 't', 'y'); } return fs.Append('N', 'a', 'N'); } if (sign != 0 && ufu.uintValue != 0) // C# prints -0 as 0 if ((error = fs.Append('-')) != FormatError.None) return error; ulong decimalMantissa = 0; int decimalExponent = 0; FixedStringUtils.Base2ToBase10(ref decimalMantissa, ref decimalExponent, ufu.floatValue); var backwards = stackalloc char[9]; int decimalDigits = 0; do { if (decimalDigits >= 9) return FormatError.Overflow; var decimalDigit = decimalMantissa % 10; backwards[8 - decimalDigits++] = (char)('0' + decimalDigit); decimalMantissa /= 10; } while (decimalMantissa > 0); char *ascii = backwards + 9 - decimalDigits; var leadingZeroes = -decimalExponent - decimalDigits + 1; if (leadingZeroes > 0) { if (leadingZeroes > 4) return fs.AppendScientific(ascii, decimalDigits, decimalExponent, decimalSeparator); if ((error = fs.Append('0', decimalSeparator)) != FormatError.None) return error; --leadingZeroes; while (leadingZeroes > 0) { if ((error = fs.Append('0')) != FormatError.None) return error; --leadingZeroes; } for (var i = 0; i < decimalDigits; ++i) { if ((error = fs.Append(ascii[i])) != FormatError.None) return error; } return FormatError.None; } var trailingZeroes = decimalExponent; if (trailingZeroes > 0) { if (trailingZeroes > 4) return fs.AppendScientific(ascii, decimalDigits, decimalExponent, decimalSeparator); for (var i = 0; i < decimalDigits; ++i) { if ((error = fs.Append(ascii[i])) != FormatError.None) return error; } while (trailingZeroes > 0) { if ((error = fs.Append('0')) != FormatError.None) return error; --trailingZeroes; } return FormatError.None; } var indexOfSeparator = decimalDigits + decimalExponent; for (var i = 0; i < decimalDigits; ++i) { if (i == indexOfSeparator) if ((error = fs.Append(decimalSeparator)) != FormatError.None) return error; if ((error = fs.Append(ascii[i])) != FormatError.None) return error; } return FormatError.None; } /// /// Appends another string to this string. /// /// /// When the method returns an error, the destination string is not modified. /// /// The type of the destination string. /// The type of the source string. /// The destination string. /// The source string. /// FormatError.None if successful. Returns FormatError.Overflow if the capacity of the destination string is exceeded. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes), typeof(FixedString128Bytes) })] public static FormatError Append(ref this T fs, in T2 input) where T : unmanaged, INativeList, IUTF8Bytes where T2 : unmanaged, INativeList, IUTF8Bytes { ref var inputRef = ref UnsafeUtilityExtensions.AsRef(input); return fs.Append(inputRef.GetUnsafePtr(), inputRef.Length); } /// /// Copies another string to this string (making the two strings equal). /// /// /// When the method returns an error, the destination string is not modified. /// /// The type of the destination string. /// The type of the source string. /// The destination string. /// The source string. /// CopyError.None if successful. Returns CopyError.Truncation if the source string is too large to fit in the destination. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes), typeof(FixedString128Bytes) })] public static CopyError CopyFrom(ref this T fs, in T2 input) where T : unmanaged, INativeList, IUTF8Bytes where T2 : unmanaged, INativeList, IUTF8Bytes { fs.Length = 0; var fe = Append(ref fs, input); if (fe != FormatError.None) return CopyError.Truncation; return CopyError.None; } /// /// Appends bytes to this string. /// /// /// When the method returns an error, the destination string is not modified. /// /// No validation is performed: it is your responsibility for the destination to contain valid UTF-8 when you're done appending bytes. /// /// The type of the destination string. /// The destination string. /// The bytes to append. /// The number of bytes to append. /// FormatError.None if successful. Returns FormatError.Overflow if the capacity of the destination string is exceeded. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })] public unsafe static FormatError Append(ref this T fs, byte* utf8Bytes, int utf8BytesLength) where T : unmanaged, INativeList, IUTF8Bytes { var origLength = fs.Length; if (!fs.TryResize(origLength + utf8BytesLength, NativeArrayOptions.UninitializedMemory)) return FormatError.Overflow; UnsafeUtility.MemCpy(fs.GetUnsafePtr() + origLength, utf8Bytes, utf8BytesLength); return FormatError.None; } /// /// Appends another string to this string. /// /// /// When the method returns an error, the destination string is not modified. /// /// The type of the destination string. /// The destination string. /// The string to append. /// FormatError.None if successful. Returns FormatError.Overflow if the capacity of the destination string is exceeded. [ExcludeFromBurstCompatTesting("Takes managed string")] public unsafe static FormatError Append(ref this T fs, string s) where T : unmanaged, INativeList, IUTF8Bytes { // we don't know how big the expansion from UTF16 to UTF8 will be, so we account for worst case. int worstCaseCapacity = s.Length * 4; byte* utf8Bytes = stackalloc byte[worstCaseCapacity]; int utf8Len; fixed (char* chars = s) { var err = UTF8ArrayUnsafeUtility.Copy(utf8Bytes, out utf8Len, worstCaseCapacity, chars, s.Length); if (err != CopyError.None) { return FormatError.Overflow; } } return fs.Append(utf8Bytes, utf8Len); } /// /// Copies another string to this string (making the two strings equal). /// Replaces any existing content of the FixedString. /// /// /// When the method returns an error, the destination string is not modified. /// /// The type of the destination string. /// The destination string. /// The source string. /// CopyError.None if successful. Returns CopyError.Truncation if the source string is too large to fit in the destination. [ExcludeFromBurstCompatTesting("Takes managed string")] public static CopyError CopyFrom(ref this T fs, string s) where T : unmanaged, INativeList, IUTF8Bytes { fs.Length = 0; var fe = Append(ref fs, s); if (fe != FormatError.None) return CopyError.Truncation; return CopyError.None; } /// /// Copies another string to this string. If the string exceeds the capacity it will be truncated. /// Replaces any existing content of the FixedString. /// /// The type of the destination string. /// The destination string. /// The source string. /// CopyError.None if successful. Returns CopyError.Truncation if the source string is too large to fit in the destination. [ExcludeFromBurstCompatTesting("Takes managed string")] public static CopyError CopyFromTruncated(ref this T fs, string s) where T : unmanaged, INativeList, IUTF8Bytes { int utf8Len; fixed (char* chars = s) { var error = UTF8ArrayUnsafeUtility.Copy(fs.GetUnsafePtr(), out utf8Len, fs.Capacity, chars, s.Length); fs.Length = utf8Len; return error; } } /// /// Copies another string to this string. If the string exceeds the capacity it will be truncated. /// /// /// When the method returns an error, the destination string is not modified. /// /// The type of the destination string. /// The type of the source string. /// The destination string. /// The source string. /// CopyError.None if successful. Returns CopyError.Truncation if the source string is too large to fit in the destination. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes), typeof(FixedString128Bytes) })] public static CopyError CopyFromTruncated(ref this T fs, in T2 input) where T : unmanaged, INativeList, IUTF8Bytes where T2 : unmanaged, INativeList, IUTF8Bytes { var error = UTF8ArrayUnsafeUtility.Copy(fs.GetUnsafePtr(), out int utf8Len, fs.Capacity, input.GetUnsafePtr(), input.Length); fs.Length = utf8Len; return error; } } }