256 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			256 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | using System.Diagnostics; | ||
|  | using System.Runtime.CompilerServices; | ||
|  | using System.Runtime.InteropServices; | ||
|  | using Unity.Burst; | ||
|  | using Unity.Jobs; | ||
|  | using Unity.Jobs.LowLevel.Unsafe; | ||
|  | using UnityEngine.Assertions; | ||
|  | 
 | ||
|  | namespace Unity.Collections.LowLevel.Unsafe | ||
|  | { | ||
|  |     internal static class UnsafeTextExtensions | ||
|  |     { | ||
|  |         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |         public static ref UnsafeList<byte> AsUnsafeListOfBytes( this ref UnsafeText text ) | ||
|  |         { | ||
|  |             return ref UnsafeUtility.As<UntypedUnsafeList, UnsafeList<byte>>(ref text.m_UntypedListData); | ||
|  |         } | ||
|  | 
 | ||
|  |         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |         public static UnsafeList<byte> AsUnsafeListOfBytesRO(this UnsafeText text) | ||
|  |         { | ||
|  |             return UnsafeUtility.As<UntypedUnsafeList, UnsafeList<byte>>(ref text.m_UntypedListData); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /// <summary> | ||
|  |     /// An unmanaged, mutable, resizable UTF-8 string. | ||
|  |     /// </summary> | ||
|  |     /// <remarks> | ||
|  |     /// The string is always null-terminated, meaning a zero byte always immediately follows the last character. | ||
|  |     /// </remarks> | ||
|  |     [GenerateTestsForBurstCompatibility] | ||
|  |     [DebuggerDisplay("Length = {Length}, Capacity = {Capacity}, IsCreated = {IsCreated}, IsEmpty = {IsEmpty}")] | ||
|  |     [StructLayout(LayoutKind.Sequential)] | ||
|  |     public unsafe struct UnsafeText : INativeDisposable, IUTF8Bytes, INativeList<byte> | ||
|  |     { | ||
|  |         // NOTE! This Length is always > 0, because we have a null terminating byte. | ||
|  |         // We hide this byte from UnsafeText users. | ||
|  |         internal UntypedUnsafeList m_UntypedListData; | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Initializes and returns an instance of UnsafeText. | ||
|  |         /// </summary> | ||
|  |         /// <param name="capacity">The initial capacity, in bytes.</param> | ||
|  |         /// <param name="allocator">The allocator to use.</param> | ||
|  |         public UnsafeText(int capacity, AllocatorManager.AllocatorHandle allocator) | ||
|  |         { | ||
|  |             m_UntypedListData = default; | ||
|  | 
 | ||
|  |             this.AsUnsafeListOfBytes() = new UnsafeList<byte>(capacity + 1, allocator); | ||
|  |             Length = 0; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Whether this string's character buffer has been allocated (and not yet deallocated). | ||
|  |         /// </summary> | ||
|  |         /// <value>Whether this string's character buffer has been allocated (and not yet deallocated).</value> | ||
|  |         public readonly bool IsCreated | ||
|  |         { | ||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |             get => this.AsUnsafeListOfBytesRO().IsCreated; | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static UnsafeText* Alloc(AllocatorManager.AllocatorHandle allocator) | ||
|  |         { | ||
|  |             UnsafeText* data = (UnsafeText*)Memory.Unmanaged.Allocate(sizeof(UnsafeText), UnsafeUtility.AlignOf<UnsafeText>(), allocator); | ||
|  |             return data; | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static void Free(UnsafeText* data) | ||
|  |         { | ||
|  |             if (data == null) | ||
|  |             { | ||
|  |                 throw new InvalidOperationException("UnsafeText has yet to be created or has been destroyed!"); | ||
|  |             } | ||
|  |             var allocator = data->m_UntypedListData.Allocator; | ||
|  |             data->Dispose(); | ||
|  |             Memory.Unmanaged.Free(data, allocator); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Releases all resources (memory). | ||
|  |         /// </summary> | ||
|  |         public void Dispose() | ||
|  |         { | ||
|  |             this.AsUnsafeListOfBytes().Dispose(); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Creates and schedules a job that will dispose this string. | ||
|  |         /// </summary> | ||
|  |         /// <param name="inputDeps">The handle of a job which the new job will depend upon.</param> | ||
|  |         /// <returns>The handle of a new job that will dispose this string. The new job depends upon inputDeps.</returns> | ||
|  |         public JobHandle Dispose(JobHandle inputDeps) | ||
|  |         { | ||
|  |             return this.AsUnsafeListOfBytes().Dispose(inputDeps); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Reports whether container is empty. | ||
|  |         /// </summary> | ||
|  |         /// <value>True if the string is empty or the string has not been constructed.</value> | ||
|  |         public readonly bool IsEmpty | ||
|  |         { | ||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |             get => !IsCreated || Length == 0; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// The byte at an index. | ||
|  |         /// </summary> | ||
|  |         /// <param name="index">A zero-based byte index.</param> | ||
|  |         /// <value>The byte at the index.</value> | ||
|  |         /// <exception cref="IndexOutOfRangeException">Thrown if the index is out of bounds.</exception> | ||
|  |         public byte this[int index] | ||
|  |         { | ||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |             get | ||
|  |             { | ||
|  |                 CheckIndexInRange(index); | ||
|  |                 return UnsafeUtility.ReadArrayElement<byte>(m_UntypedListData.Ptr, index); | ||
|  |             } | ||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |             set | ||
|  |             { | ||
|  |                 CheckIndexInRange(index); | ||
|  |                 UnsafeUtility.WriteArrayElement(m_UntypedListData.Ptr, index, value); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Returns a reference to the byte (not character) at an index. | ||
|  |         /// </summary> | ||
|  |         /// <remarks> | ||
|  |         /// Deallocating or reallocating this string's character buffer makes the reference invalid. | ||
|  |         /// </remarks> | ||
|  |         /// <param name="index">A byte index.</param> | ||
|  |         /// <returns>A reference to the byte at the index.</returns> | ||
|  |         /// <exception cref="IndexOutOfRangeException">Thrown if the index is out of bounds.</exception> | ||
|  |         public ref byte ElementAt(int index) | ||
|  |         { | ||
|  |             CheckIndexInRange(index); | ||
|  |             return ref UnsafeUtility.ArrayElementAsRef<byte>(m_UntypedListData.Ptr, index); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Sets the length to 0. | ||
|  |         /// </summary> | ||
|  |         public void Clear() | ||
|  |         { | ||
|  |             Length = 0; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Returns a pointer to this string's character buffer. | ||
|  |         /// </summary> | ||
|  |         /// <remarks> | ||
|  |         /// The pointer is made invalid by operations that reallocate the character buffer, such as setting <see cref="Capacity"/>. | ||
|  |         /// </remarks> | ||
|  |         /// <returns>A pointer to this string's character buffer.</returns> | ||
|  |         public byte* GetUnsafePtr() | ||
|  |         { | ||
|  |             return (byte*)m_UntypedListData.Ptr; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Attempt to set the length in bytes of this string. | ||
|  |         /// </summary> | ||
|  |         /// <param name="newLength">The new length in bytes of the string.</param> | ||
|  |         /// <param name="clearOptions">Whether any bytes added should be zeroed out.</param> | ||
|  |         /// <returns>Always true.</returns> | ||
|  |         public bool TryResize(int newLength, NativeArrayOptions clearOptions = NativeArrayOptions.ClearMemory) | ||
|  |         { | ||
|  |             // this can't ever fail, because if we can't resize malloc will abort | ||
|  |             this.AsUnsafeListOfBytes().Resize(newLength + 1, clearOptions); | ||
|  |             this.AsUnsafeListOfBytes()[newLength] = 0; | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// The current capacity in bytes of this string. | ||
|  |         /// </summary> | ||
|  |         /// <remarks> | ||
|  |         /// The null-terminator byte is not included in the capacity, so the string's character buffer is `Capacity + 1` in size. | ||
|  |         /// </remarks> | ||
|  |         /// <value>The current capacity in bytes of the string.</value> | ||
|  |         public int Capacity | ||
|  |         { | ||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |             readonly get => this.AsUnsafeListOfBytesRO().Capacity - 1; | ||
|  | 
 | ||
|  |             set | ||
|  |             { | ||
|  |                 CheckCapacityInRange(value + 1, this.AsUnsafeListOfBytes().Length); | ||
|  |                 this.AsUnsafeListOfBytes().SetCapacity(value + 1); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// The current length in bytes of this string. | ||
|  |         /// </summary> | ||
|  |         /// <remarks> | ||
|  |         /// The length does not include the null terminator byte. | ||
|  |         /// </remarks> | ||
|  |         /// <value>The current length in bytes of the UTF-8 encoded string.</value> | ||
|  |         public int Length | ||
|  |         { | ||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |             readonly get => this.AsUnsafeListOfBytesRO().Length - 1; | ||
|  |             set | ||
|  |             { | ||
|  |                 this.AsUnsafeListOfBytes().Resize(value + 1); | ||
|  |                 this.AsUnsafeListOfBytes()[value] = 0; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Returns a managed string copy of this string. | ||
|  |         /// </summary> | ||
|  |         /// <returns>A managed string copy of this string.</returns> | ||
|  |         [ExcludeFromBurstCompatTesting("Returns managed string")] | ||
|  |         public override string ToString() | ||
|  |         { | ||
|  |             if (!IsCreated) | ||
|  |                 return ""; | ||
|  |             return this.ConvertToString(); | ||
|  |         } | ||
|  | 
 | ||
|  |         [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] | ||
|  |         void CheckIndexInRange(int index) | ||
|  |         { | ||
|  |             if (index < 0) | ||
|  |                 throw new IndexOutOfRangeException($"Index {index} must be positive."); | ||
|  |             if (index >= Length) | ||
|  |                 throw new IndexOutOfRangeException($"Index {index} is out of range in UnsafeText of {Length} length."); | ||
|  |         } | ||
|  | 
 | ||
|  |         [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] | ||
|  |         void ThrowCopyError(CopyError error, string source) | ||
|  |         { | ||
|  |             throw new ArgumentException($"UnsafeText: {error} while copying \"{source}\""); | ||
|  |         } | ||
|  | 
 | ||
|  |         [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] | ||
|  |         static void CheckCapacityInRange(int value, int length) | ||
|  |         { | ||
|  |             if (value < 0) | ||
|  |                 throw new ArgumentOutOfRangeException($"Value {value} must be positive."); | ||
|  | 
 | ||
|  |             if ((uint)value < (uint)length) | ||
|  |                 throw new ArgumentOutOfRangeException($"Value {value} is out of range in NativeList of '{length}' Length."); | ||
|  |         } | ||
|  |     } | ||
|  | } |