From cdab2b3813a7adf8780e90edb329e5778e7d7eaf Mon Sep 17 00:00:00 2001 From: Hertzole Date: Sat, 11 Oct 2025 21:47:18 +0200 Subject: [PATCH 1/5] chore: make asmdef aware of collections package --- src/ZString.Unity/Assets/Scripts/ZString/ZString.asmdef | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ZString.Unity/Assets/Scripts/ZString/ZString.asmdef b/src/ZString.Unity/Assets/Scripts/ZString/ZString.asmdef index 40c88be..65dee81 100644 --- a/src/ZString.Unity/Assets/Scripts/ZString/ZString.asmdef +++ b/src/ZString.Unity/Assets/Scripts/ZString/ZString.asmdef @@ -1,7 +1,8 @@ { "name": "ZString", "references": [ - "Unity.TextMeshPro" + "Unity.TextMeshPro", + "Unity.Collections" ], "includePlatforms": [], "excludePlatforms": [], @@ -20,6 +21,11 @@ "name": "com.unity.ugui", "expression": "2.0.0", "define": "ZSTRING_TEXTMESHPRO_SUPPORT" + }, + { + "name": "com.unity.collections", + "expression": "2.0.0", + "define": "ZSTRING_COLLECTIONS_SUPPORT" } ] } \ No newline at end of file From 13f75788521fd01f66ccab793bd664c1697c1c06 Mon Sep 17 00:00:00 2001 From: Hertzole Date: Sat, 11 Oct 2025 21:47:59 +0200 Subject: [PATCH 2/5] feat: AsFixedStringXBytes to Utf8ValueStringBuilder --- ...Utf8ValueStringBuilder.UnityCollections.cs | 142 ++++++++++++++++++ ...alueStringBuilder.UnityCollections.cs.meta | 3 + 2 files changed, 145 insertions(+) create mode 100644 src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.UnityCollections.cs create mode 100644 src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.UnityCollections.cs.meta diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.UnityCollections.cs b/src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.UnityCollections.cs new file mode 100644 index 0000000..745790c --- /dev/null +++ b/src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.UnityCollections.cs @@ -0,0 +1,142 @@ +#if ZSTRING_COLLECTIONS_SUPPORT +using System; +using Unity.Collections; + +namespace Cysharp.Text +{ + partial struct Utf8ValueStringBuilder + { + /// + /// Get the written buffer data as a . + /// + /// If the of the buffer exceeds 29 bytes. + public FixedString32Bytes AsFixedString32Bytes() + { + if (buffer == null || Length == 0) + { + return new FixedString32Bytes(); + } + + if (Length > FixedString32Bytes.UTF8MaxLengthInBytes) + { + throw new InvalidOperationException( + $"The current length ({Length}) exceeds the maximum length of FixedString32Bytes ({FixedString32Bytes.UTF8MaxLengthInBytes})."); + } + + using (NativeText text = CopyToNativeText(in buffer, in index)) + { + return new FixedString32Bytes(text.AsReadOnly()); + } + } + + /// + /// Get the written buffer data as a . + /// + /// If the of the buffer exceeds 61 bytes. + public FixedString64Bytes AsFixedString64Bytes() + { + if (buffer == null || Length == 0) + { + return new FixedString64Bytes(); + } + + if (Length > FixedString64Bytes.UTF8MaxLengthInBytes) + { + throw new InvalidOperationException( + $"The current length ({Length}) exceeds the maximum length of FixedString64Bytes ({FixedString64Bytes.UTF8MaxLengthInBytes})."); + } + + using (NativeText text = CopyToNativeText(in buffer, in index)) + { + return new FixedString64Bytes(text.AsReadOnly()); + } + } + + /// + /// Get the written buffer data as a . + /// + /// If the of the buffer exceeds 125 bytes. + public FixedString128Bytes AsFixedString128Bytes() + { + if (buffer == null || Length == 0) + { + return new FixedString128Bytes(); + } + + if (Length > FixedString128Bytes.UTF8MaxLengthInBytes) + { + throw new InvalidOperationException( + $"The current length ({Length}) exceeds the maximum length of FixedString128Bytes ({FixedString128Bytes.UTF8MaxLengthInBytes})."); + } + + using (NativeText text = CopyToNativeText(in buffer, in index)) + { + return new FixedString128Bytes(text.AsReadOnly()); + } + } + + /// + /// Get the written buffer data as a . + /// + /// If the of the buffer exceeds 509 bytes. + public FixedString512Bytes AsFixedString512Bytes() + { + if (buffer == null || Length == 0) + { + return new FixedString512Bytes(); + } + + if (Length > FixedString512Bytes.UTF8MaxLengthInBytes) + { + throw new InvalidOperationException( + $"The current length ({Length}) exceeds the maximum length of FixedString512Bytes ({FixedString512Bytes.UTF8MaxLengthInBytes})."); + } + + using (NativeText text = CopyToNativeText(in buffer, in index)) + { + return new FixedString512Bytes(text.AsReadOnly()); + } + } + + /// + /// Get the written buffer data as a . + /// + /// If the of the buffer exceeds 4093 bytes. + public FixedString4096Bytes AsFixedString4096Bytes() + { + if (buffer == null || Length == 0) + { + return new FixedString4096Bytes(); + } + + if (Length > FixedString4096Bytes.UTF8MaxLengthInBytes) + { + throw new InvalidOperationException( + $"The current length ({Length}) exceeds the maximum length of FixedString4096Bytes ({FixedString4096Bytes.UTF8MaxLengthInBytes})."); + } + + using (NativeText text = CopyToNativeText(in buffer, in index)) + { + return new FixedString4096Bytes(text.AsReadOnly()); + } + } + + /// + /// Helper method to copy a array buffer to + /// + /// The current buffer. + /// The length of written elements in the buffer. + /// with the written to it. + private static NativeText CopyToNativeText(in byte[] buffer, in int length) + { + NativeText text = new NativeText(length, Allocator.Temp); + for (int i = 0; i < length; i++) + { + text.Add(buffer[i]); + } + + return text; + } + } +} +#endif diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.UnityCollections.cs.meta b/src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.UnityCollections.cs.meta new file mode 100644 index 0000000..4aa42c8 --- /dev/null +++ b/src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.UnityCollections.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2c1cf0f17b4941be9dec84565c8a644a +timeCreated: 1760210314 \ No newline at end of file From fb6257b7fb72b2875bcaa5d7ce1e774994adc764 Mon Sep 17 00:00:00 2001 From: Hertzole Date: Sat, 11 Oct 2025 22:11:28 +0200 Subject: [PATCH 3/5] feat: AsFixedStringXBytes to Utf16ValueStringBuilder --- ...tf16ValueStringBuilder.UnityCollections.cs | 167 ++++++++++++++++++ ...alueStringBuilder.UnityCollections.cs.meta | 3 + 2 files changed, 170 insertions(+) create mode 100644 src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.UnityCollections.cs create mode 100644 src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.UnityCollections.cs.meta diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.UnityCollections.cs b/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.UnityCollections.cs new file mode 100644 index 0000000..10dc405 --- /dev/null +++ b/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.UnityCollections.cs @@ -0,0 +1,167 @@ +#if ZSTRING_COLLECTIONS_SUPPORT +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Text; +using Unity.Collections; + +namespace Cysharp.Text +{ + partial struct Utf16ValueStringBuilder + { + /// + /// Get the written buffer data as a . + /// + /// If the byte length of the buffer exceeds 29 bytes. + public FixedString32Bytes AsFixedString32Bytes() + { + if (buffer == null || Length == 0) + { + return new FixedString32Bytes(); + } + + int byteCount = GetUtf8ByteCount(in buffer, in index); + + if (byteCount > FixedString32Bytes.UTF8MaxLengthInBytes) + { + throw new InvalidOperationException( + $"The current length ({Length}) exceeds the maximum length of FixedString32Bytes ({FixedString32Bytes.UTF8MaxLengthInBytes})."); + } + + using (NativeText text = CopyToNativeText(in buffer, in byteCount)) + { + return new FixedString32Bytes(text.AsReadOnly()); + } + } + + /// + /// Get the written buffer data as a . + /// + /// If the byte length of the buffer exceeds 61 bytes. + public FixedString64Bytes AsFixedString64Bytes() + { + if (buffer == null || Length == 0) + { + return new FixedString64Bytes(); + } + + int byteCount = GetUtf8ByteCount(in buffer, in index); + + if (byteCount > FixedString64Bytes.UTF8MaxLengthInBytes) + { + throw new InvalidOperationException( + $"The current length ({Length}) exceeds the maximum length of FixedString64Bytes ({FixedString64Bytes.UTF8MaxLengthInBytes})."); + } + + using (NativeText text = CopyToNativeText(in buffer, in byteCount)) + { + return new FixedString64Bytes(text.AsReadOnly()); + } + } + + /// + /// Get the written buffer data as a . + /// + /// If the byte length of the buffer exceeds 125 bytes. + public FixedString128Bytes AsFixedString128Bytes() + { + if (buffer == null || Length == 0) + { + return new FixedString128Bytes(); + } + + int byteCount = GetUtf8ByteCount(in buffer, in index); + + if (byteCount > FixedString128Bytes.UTF8MaxLengthInBytes) + { + throw new InvalidOperationException( + $"The current length ({Length}) exceeds the maximum length of FixedString128Bytes ({FixedString128Bytes.UTF8MaxLengthInBytes})."); + } + + using (NativeText text = CopyToNativeText(in buffer, in byteCount)) + { + return new FixedString128Bytes(text.AsReadOnly()); + } + } + + /// + /// Get the written buffer data as a . + /// + /// If the byte length of the buffer exceeds 509 bytes. + public FixedString512Bytes AsFixedString512Bytes() + { + if (buffer == null || Length == 0) + { + return new FixedString512Bytes(); + } + + int byteCount = GetUtf8ByteCount(in buffer, in index); + + if (byteCount > FixedString512Bytes.UTF8MaxLengthInBytes) + { + throw new InvalidOperationException( + $"The current length ({Length}) exceeds the maximum length of FixedString512Bytes ({FixedString512Bytes.UTF8MaxLengthInBytes})."); + } + + using (NativeText text = CopyToNativeText(in buffer, in byteCount)) + { + return new FixedString512Bytes(text.AsReadOnly()); + } + } + + /// + /// Get the written buffer data as a . + /// + /// If the byte length of the buffer exceeds 4093 bytes. + public FixedString4096Bytes AsFixedString4096Bytes() + { + if (buffer == null || Length == 0) + { + return new FixedString4096Bytes(); + } + + int byteCount = GetUtf8ByteCount(in buffer, in index); + + if (byteCount > FixedString4096Bytes.UTF8MaxLengthInBytes) + { + throw new InvalidOperationException( + $"The current length ({Length}) exceeds the maximum length of FixedString4096Bytes ({FixedString4096Bytes.UTF8MaxLengthInBytes})."); + } + + using (NativeText text = CopyToNativeText(in buffer, in byteCount)) + { + return new FixedString4096Bytes(text.AsReadOnly()); + } + } + + /// + /// Helper method to copy a array buffer to + /// + /// The current buffer. + /// The amount of bytes this buffer requires. + /// with the written to it. + private static NativeText CopyToNativeText(in char[] buffer, in int byteCount) + { + byte[]? destinationArray = ArrayPool.Shared.Rent(byteCount); + Span destination = destinationArray.AsSpan(0, byteCount); + int writtenBytes = Encoding.UTF8.GetBytes(buffer, destination); + + NativeText text = new NativeText(writtenBytes, Allocator.Temp); + for (int i = 0; i < writtenBytes; i++) + { + text.Add(destination[i]); + } + + ArrayPool.Shared.Return(destinationArray); + + return text; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetUtf8ByteCount(in char[] buffer, in int length) + { + return Encoding.UTF8.GetByteCount(buffer, 0, length); + } + } +} +#endif diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.UnityCollections.cs.meta b/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.UnityCollections.cs.meta new file mode 100644 index 0000000..95642f1 --- /dev/null +++ b/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.UnityCollections.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8460f7c414464d7493c8212d7d7520ee +timeCreated: 1760212117 \ No newline at end of file From 31553f1ca618229a455e97f8887a570c6b0f9348 Mon Sep 17 00:00:00 2001 From: Hertzole Date: Mon, 13 Oct 2025 16:28:54 +0200 Subject: [PATCH 4/5] feat: Append FixedString to string builders --- ...tf16ValueStringBuilder.UnityCollections.cs | 76 ++++++++++++++++++- ...Utf8ValueStringBuilder.UnityCollections.cs | 64 +++++++++++++++- 2 files changed, 137 insertions(+), 3 deletions(-) diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.UnityCollections.cs b/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.UnityCollections.cs index 10dc405..c8a9a0e 100644 --- a/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.UnityCollections.cs +++ b/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.UnityCollections.cs @@ -9,6 +9,8 @@ namespace Cysharp.Text { partial struct Utf16ValueStringBuilder { + private static readonly Encoding UTF8NoBom = new UTF8Encoding(false); + /// /// Get the written buffer data as a . /// @@ -144,7 +146,7 @@ private static NativeText CopyToNativeText(in char[] buffer, in int byteCount) { byte[]? destinationArray = ArrayPool.Shared.Rent(byteCount); Span destination = destinationArray.AsSpan(0, byteCount); - int writtenBytes = Encoding.UTF8.GetBytes(buffer, destination); + int writtenBytes = UTF8NoBom.GetBytes(buffer, destination); NativeText text = new NativeText(writtenBytes, Allocator.Temp); for (int i = 0; i < writtenBytes; i++) @@ -160,7 +162,77 @@ private static NativeText CopyToNativeText(in char[] buffer, in int byteCount) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int GetUtf8ByteCount(in char[] buffer, in int length) { - return Encoding.UTF8.GetByteCount(buffer, 0, length); + return UTF8NoBom.GetByteCount(buffer, 0, length); + } + + /// + /// Append a to the builder. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(FixedString32Bytes value) + { + AppendFixedString(value, value.Length); + } + + /// + /// Append a to the builder. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(FixedString64Bytes value) + { + AppendFixedString(value, value.Length); + } + + /// + /// Append a to the builder. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(FixedString128Bytes value) + { + AppendFixedString(value, value.Length); + } + + /// + /// Append a to the builder. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(FixedString512Bytes value) + { + AppendFixedString(value, value.Length); + } + + /// + /// Append a to the builder. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(FixedString4096Bytes value) + { + AppendFixedString(value, value.Length); + } + + /// + /// Helper method to append a fixed string to the builder. + /// + /// The fixed string. + /// The length of the string byte buffer. + /// The type of FixedString. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AppendFixedString(T value, int length) where T : unmanaged, IUTF8Bytes + { + unsafe + { + byte* bytes = value.GetUnsafePtr(); + ReadOnlySpan span = new ReadOnlySpan(bytes, length); + + int charCount = UTF8NoBom.GetCharCount(span); + + char[]? charBuffer = ArrayPool.Shared.Rent(charCount); + int written = UTF8NoBom.GetChars(span, charBuffer); + + Append(charBuffer.AsSpan(0, written)); + + ArrayPool.Shared.Return(charBuffer); + } } } } diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.UnityCollections.cs b/src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.UnityCollections.cs index 745790c..726d83d 100644 --- a/src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.UnityCollections.cs +++ b/src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.UnityCollections.cs @@ -1,5 +1,6 @@ #if ZSTRING_COLLECTIONS_SUPPORT using System; +using System.Runtime.CompilerServices; using Unity.Collections; namespace Cysharp.Text @@ -122,7 +123,7 @@ public FixedString4096Bytes AsFixedString4096Bytes() } /// - /// Helper method to copy a array buffer to + /// Helper method to copy a array buffer to /// /// The current buffer. /// The length of written elements in the buffer. @@ -137,6 +138,67 @@ private static NativeText CopyToNativeText(in byte[] buffer, in int length) return text; } + + /// + /// Append a to the builder. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(FixedString32Bytes value) + { + AppendFixedString(value, value.Length); + } + + /// + /// Append a to the builder. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(FixedString64Bytes value) + { + AppendFixedString(value, value.Length); + } + + /// + /// Append a to the builder. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(FixedString128Bytes value) + { + AppendFixedString(value, value.Length); + } + + /// + /// Append a to the builder. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(FixedString512Bytes value) + { + AppendFixedString(value, value.Length); + } + + /// + /// Append a to the builder. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(FixedString4096Bytes value) + { + AppendFixedString(value, value.Length); + } + + /// + /// Helper method to append a fixed string to the builder. + /// + /// The fixed string. + /// The length of the string byte buffer. + /// The type of FixedString. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AppendFixedString(T value, int length) where T : unmanaged, IUTF8Bytes + { + unsafe + { + byte* bytes = value.GetUnsafePtr(); + AppendLiteral(new ReadOnlySpan(bytes, length)); + } + } } } #endif From 9dde60d78ec5a63b66b4611aefe09151db5ab2c4 Mon Sep 17 00:00:00 2001 From: Hertzole Date: Mon, 13 Oct 2025 16:29:42 +0200 Subject: [PATCH 5/5] chore: move unity collections method to respective utf folder --- .../{ => Utf16}/Utf16ValueStringBuilder.UnityCollections.cs | 0 .../{ => Utf16}/Utf16ValueStringBuilder.UnityCollections.cs.meta | 0 .../ZString/{ => Utf8}/Utf8ValueStringBuilder.UnityCollections.cs | 0 .../{ => Utf8}/Utf8ValueStringBuilder.UnityCollections.cs.meta | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename src/ZString.Unity/Assets/Scripts/ZString/{ => Utf16}/Utf16ValueStringBuilder.UnityCollections.cs (100%) rename src/ZString.Unity/Assets/Scripts/ZString/{ => Utf16}/Utf16ValueStringBuilder.UnityCollections.cs.meta (100%) rename src/ZString.Unity/Assets/Scripts/ZString/{ => Utf8}/Utf8ValueStringBuilder.UnityCollections.cs (100%) rename src/ZString.Unity/Assets/Scripts/ZString/{ => Utf8}/Utf8ValueStringBuilder.UnityCollections.cs.meta (100%) diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.UnityCollections.cs b/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.UnityCollections.cs similarity index 100% rename from src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.UnityCollections.cs rename to src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.UnityCollections.cs diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.UnityCollections.cs.meta b/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.UnityCollections.cs.meta similarity index 100% rename from src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.UnityCollections.cs.meta rename to src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.UnityCollections.cs.meta diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.UnityCollections.cs b/src/ZString.Unity/Assets/Scripts/ZString/Utf8/Utf8ValueStringBuilder.UnityCollections.cs similarity index 100% rename from src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.UnityCollections.cs rename to src/ZString.Unity/Assets/Scripts/ZString/Utf8/Utf8ValueStringBuilder.UnityCollections.cs diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.UnityCollections.cs.meta b/src/ZString.Unity/Assets/Scripts/ZString/Utf8/Utf8ValueStringBuilder.UnityCollections.cs.meta similarity index 100% rename from src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.UnityCollections.cs.meta rename to src/ZString.Unity/Assets/Scripts/ZString/Utf8/Utf8ValueStringBuilder.UnityCollections.cs.meta