A game about forced loneliness, made by TACStudios
1using System; 2using System.Diagnostics; 3using System.Runtime.CompilerServices; 4using System.Runtime.InteropServices; 5using Unity.Burst; 6using Unity.Jobs; 7using Unity.Jobs.LowLevel.Unsafe; 8using UnityEngine.Assertions; 9 10namespace Unity.Collections.LowLevel.Unsafe 11{ 12 internal static class UnsafeTextExtensions 13 { 14 [MethodImpl(MethodImplOptions.AggressiveInlining)] 15 public static ref UnsafeList<byte> AsUnsafeListOfBytes( this ref UnsafeText text ) 16 { 17 return ref UnsafeUtility.As<UntypedUnsafeList, UnsafeList<byte>>(ref text.m_UntypedListData); 18 } 19 20 [MethodImpl(MethodImplOptions.AggressiveInlining)] 21 public static UnsafeList<byte> AsUnsafeListOfBytesRO(this UnsafeText text) 22 { 23 return UnsafeUtility.As<UntypedUnsafeList, UnsafeList<byte>>(ref text.m_UntypedListData); 24 } 25 } 26 27 /// <summary> 28 /// An unmanaged, mutable, resizable UTF-8 string. 29 /// </summary> 30 /// <remarks> 31 /// The string is always null-terminated, meaning a zero byte always immediately follows the last character. 32 /// </remarks> 33 [GenerateTestsForBurstCompatibility] 34 [DebuggerDisplay("Length = {Length}, Capacity = {Capacity}, IsCreated = {IsCreated}, IsEmpty = {IsEmpty}")] 35 [StructLayout(LayoutKind.Sequential)] 36 public unsafe struct UnsafeText : INativeDisposable, IUTF8Bytes, INativeList<byte> 37 { 38 // NOTE! This Length is always > 0, because we have a null terminating byte. 39 // We hide this byte from UnsafeText users. 40 internal UntypedUnsafeList m_UntypedListData; 41 42 /// <summary> 43 /// Initializes and returns an instance of UnsafeText. 44 /// </summary> 45 /// <param name="capacity">The initial capacity, in bytes.</param> 46 /// <param name="allocator">The allocator to use.</param> 47 public UnsafeText(int capacity, AllocatorManager.AllocatorHandle allocator) 48 { 49 m_UntypedListData = default; 50 51 this.AsUnsafeListOfBytes() = new UnsafeList<byte>(capacity + 1, allocator); 52 Length = 0; 53 } 54 55 /// <summary> 56 /// Whether this string's character buffer has been allocated (and not yet deallocated). 57 /// </summary> 58 /// <value>Whether this string's character buffer has been allocated (and not yet deallocated).</value> 59 public readonly bool IsCreated 60 { 61 [MethodImpl(MethodImplOptions.AggressiveInlining)] 62 get => this.AsUnsafeListOfBytesRO().IsCreated; 63 } 64 65 internal static UnsafeText* Alloc(AllocatorManager.AllocatorHandle allocator) 66 { 67 UnsafeText* data = (UnsafeText*)Memory.Unmanaged.Allocate(sizeof(UnsafeText), UnsafeUtility.AlignOf<UnsafeText>(), allocator); 68 return data; 69 } 70 71 internal static void Free(UnsafeText* data) 72 { 73 if (data == null) 74 { 75 throw new InvalidOperationException("UnsafeText has yet to be created or has been destroyed!"); 76 } 77 var allocator = data->m_UntypedListData.Allocator; 78 data->Dispose(); 79 Memory.Unmanaged.Free(data, allocator); 80 } 81 82 /// <summary> 83 /// Releases all resources (memory). 84 /// </summary> 85 public void Dispose() 86 { 87 this.AsUnsafeListOfBytes().Dispose(); 88 } 89 90 /// <summary> 91 /// Creates and schedules a job that will dispose this string. 92 /// </summary> 93 /// <param name="inputDeps">The handle of a job which the new job will depend upon.</param> 94 /// <returns>The handle of a new job that will dispose this string. The new job depends upon inputDeps.</returns> 95 public JobHandle Dispose(JobHandle inputDeps) 96 { 97 return this.AsUnsafeListOfBytes().Dispose(inputDeps); 98 } 99 100 /// <summary> 101 /// Reports whether container is empty. 102 /// </summary> 103 /// <value>True if the string is empty or the string has not been constructed.</value> 104 public readonly bool IsEmpty 105 { 106 [MethodImpl(MethodImplOptions.AggressiveInlining)] 107 get => !IsCreated || Length == 0; 108 } 109 110 /// <summary> 111 /// The byte at an index. 112 /// </summary> 113 /// <param name="index">A zero-based byte index.</param> 114 /// <value>The byte at the index.</value> 115 /// <exception cref="IndexOutOfRangeException">Thrown if the index is out of bounds.</exception> 116 public byte this[int index] 117 { 118 [MethodImpl(MethodImplOptions.AggressiveInlining)] 119 get 120 { 121 CheckIndexInRange(index); 122 return UnsafeUtility.ReadArrayElement<byte>(m_UntypedListData.Ptr, index); 123 } 124 [MethodImpl(MethodImplOptions.AggressiveInlining)] 125 set 126 { 127 CheckIndexInRange(index); 128 UnsafeUtility.WriteArrayElement(m_UntypedListData.Ptr, index, value); 129 } 130 } 131 132 /// <summary> 133 /// Returns a reference to the byte (not character) at an index. 134 /// </summary> 135 /// <remarks> 136 /// Deallocating or reallocating this string's character buffer makes the reference invalid. 137 /// </remarks> 138 /// <param name="index">A byte index.</param> 139 /// <returns>A reference to the byte at the index.</returns> 140 /// <exception cref="IndexOutOfRangeException">Thrown if the index is out of bounds.</exception> 141 public ref byte ElementAt(int index) 142 { 143 CheckIndexInRange(index); 144 return ref UnsafeUtility.ArrayElementAsRef<byte>(m_UntypedListData.Ptr, index); 145 } 146 147 /// <summary> 148 /// Sets the length to 0. 149 /// </summary> 150 public void Clear() 151 { 152 Length = 0; 153 } 154 155 /// <summary> 156 /// Returns a pointer to this string's character buffer. 157 /// </summary> 158 /// <remarks> 159 /// The pointer is made invalid by operations that reallocate the character buffer, such as setting <see cref="Capacity"/>. 160 /// </remarks> 161 /// <returns>A pointer to this string's character buffer.</returns> 162 public byte* GetUnsafePtr() 163 { 164 return (byte*)m_UntypedListData.Ptr; 165 } 166 167 /// <summary> 168 /// Attempt to set the length in bytes of this string. 169 /// </summary> 170 /// <param name="newLength">The new length in bytes of the string.</param> 171 /// <param name="clearOptions">Whether any bytes added should be zeroed out.</param> 172 /// <returns>Always true.</returns> 173 public bool TryResize(int newLength, NativeArrayOptions clearOptions = NativeArrayOptions.ClearMemory) 174 { 175 // this can't ever fail, because if we can't resize malloc will abort 176 this.AsUnsafeListOfBytes().Resize(newLength + 1, clearOptions); 177 this.AsUnsafeListOfBytes()[newLength] = 0; 178 return true; 179 } 180 181 /// <summary> 182 /// The current capacity in bytes of this string. 183 /// </summary> 184 /// <remarks> 185 /// The null-terminator byte is not included in the capacity, so the string's character buffer is `Capacity + 1` in size. 186 /// </remarks> 187 /// <value>The current capacity in bytes of the string.</value> 188 public int Capacity 189 { 190 [MethodImpl(MethodImplOptions.AggressiveInlining)] 191 readonly get => this.AsUnsafeListOfBytesRO().Capacity - 1; 192 193 set 194 { 195 CheckCapacityInRange(value + 1, this.AsUnsafeListOfBytes().Length); 196 this.AsUnsafeListOfBytes().SetCapacity(value + 1); 197 } 198 } 199 200 /// <summary> 201 /// The current length in bytes of this string. 202 /// </summary> 203 /// <remarks> 204 /// The length does not include the null terminator byte. 205 /// </remarks> 206 /// <value>The current length in bytes of the UTF-8 encoded string.</value> 207 public int Length 208 { 209 [MethodImpl(MethodImplOptions.AggressiveInlining)] 210 readonly get => this.AsUnsafeListOfBytesRO().Length - 1; 211 set 212 { 213 this.AsUnsafeListOfBytes().Resize(value + 1); 214 this.AsUnsafeListOfBytes()[value] = 0; 215 } 216 } 217 218 /// <summary> 219 /// Returns a managed string copy of this string. 220 /// </summary> 221 /// <returns>A managed string copy of this string.</returns> 222 [ExcludeFromBurstCompatTesting("Returns managed string")] 223 public override string ToString() 224 { 225 if (!IsCreated) 226 return ""; 227 return this.ConvertToString(); 228 } 229 230 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 231 void CheckIndexInRange(int index) 232 { 233 if (index < 0) 234 throw new IndexOutOfRangeException($"Index {index} must be positive."); 235 if (index >= Length) 236 throw new IndexOutOfRangeException($"Index {index} is out of range in UnsafeText of {Length} length."); 237 } 238 239 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 240 void ThrowCopyError(CopyError error, string source) 241 { 242 throw new ArgumentException($"UnsafeText: {error} while copying \"{source}\""); 243 } 244 245 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 246 static void CheckCapacityInRange(int value, int length) 247 { 248 if (value < 0) 249 throw new ArgumentOutOfRangeException($"Value {value} must be positive."); 250 251 if ((uint)value < (uint)length) 252 throw new ArgumentOutOfRangeException($"Value {value} is out of range in NativeList of '{length}' Length."); 253 } 254 } 255}