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}