initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b49443f1e93e4dec9a7a68741679aa52
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,24 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.Burst.Tests")]
[assembly: InternalsVisibleTo("Unity.Collections.BurstCompatibilityTestCodeGen")]
[assembly: InternalsVisibleTo("Unity.Collections.Tests")]
[assembly: InternalsVisibleTo("Unity.Entities")]
[assembly: InternalsVisibleTo("Unity.Entities.CodeGen")]
[assembly: InternalsVisibleTo("Unity.Entities.Tests")]
[assembly: InternalsVisibleTo("Unity.Entities.Editor")]
[assembly: InternalsVisibleTo("Unity.Entities.Editor.Tests")]
[assembly: InternalsVisibleTo("Unity.Rendering.Hybrid")]
[assembly: InternalsVisibleTo("Unity.Runtime")]
[assembly: InternalsVisibleTo("Unity.Runtime.Tests")]
[assembly: InternalsVisibleTo("Unity.Runtime.IO.Tests")]
[assembly: InternalsVisibleTo("Unity.Runtime.UnityInstance")]
[assembly: InternalsVisibleTo("Unity.Tiny.Animation")]
[assembly: InternalsVisibleTo("Unity.Tiny.Core.Tests")]
[assembly: InternalsVisibleTo("Unity.Tiny.GameSave")]
[assembly: InternalsVisibleTo("Unity.Tiny.GameSave.Tests")]
[assembly: InternalsVisibleTo("Unity.Tiny.Rendering.Native")]
[assembly: InternalsVisibleTo("Unity.Scenes")]
[assembly: InternalsVisibleTo("Unity.Scenes.Editor")]
[assembly: InternalsVisibleTo("Samples.GridPath.Tests")]
[assembly: InternalsVisibleTo("Unity.Entities.PerformanceTests")]

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 63bc65d8d2fe144e1b071aeb3b624536
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
using System;
namespace Unity.Collections
{
#if !UNITY_PROPERTIES_EXISTS
class CreatePropertyAttribute : Attribute { }
#endif
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4f31f524e51e467a9dc7691a41622766
timeCreated: 1589493298

View file

@ -0,0 +1,818 @@
using System;
using System.Diagnostics;
using Unity.Mathematics;
namespace Unity.Collections
{
[BurstCompatible]
internal unsafe struct Bitwise
{
internal static int AlignDown(int value, int alignPow2)
{
return value & ~(alignPow2 - 1);
}
internal static int AlignUp(int value, int alignPow2)
{
return AlignDown(value + alignPow2 - 1, alignPow2);
}
internal static int FromBool(bool value)
{
return value ? 1 : 0;
}
// 32-bit uint
internal static uint ExtractBits(uint input, int pos, uint mask)
{
var tmp0 = input >> pos;
return tmp0 & mask;
}
internal static uint ReplaceBits(uint input, int pos, uint mask, uint value)
{
var tmp0 = (value & mask) << pos;
var tmp1 = input & ~(mask << pos);
return tmp0 | tmp1;
}
internal static uint SetBits(uint input, int pos, uint mask, bool value)
{
return ReplaceBits(input, pos, mask, (uint)-FromBool(value));
}
// 64-bit ulong
internal static ulong ExtractBits(ulong input, int pos, ulong mask)
{
var tmp0 = input >> pos;
return tmp0 & mask;
}
internal static ulong ReplaceBits(ulong input, int pos, ulong mask, ulong value)
{
var tmp0 = (value & mask) << pos;
var tmp1 = input & ~(mask << pos);
return tmp0 | tmp1;
}
internal static ulong SetBits(ulong input, int pos, ulong mask, bool value)
{
return ReplaceBits(input, pos, mask, (ulong)-(long)FromBool(value));
}
internal static int lzcnt(byte value)
{
return math.lzcnt((uint)value) - 24;
}
internal static int tzcnt(byte value)
{
return math.min(8, math.tzcnt((uint)value));
}
internal static int lzcnt(ushort value)
{
return math.lzcnt((uint)value) - 16;
}
internal static int tzcnt(ushort value)
{
return math.min(16, math.tzcnt((uint)value));
}
static int FindUlong(ulong* ptr, int beginBit, int endBit, int numBits)
{
var bits = ptr;
var numSteps = (numBits + 63) >> 6;
var numBitsPerStep = 64;
var maxBits = numSteps * numBitsPerStep;
for (int i = beginBit / numBitsPerStep, end = AlignUp(endBit, numBitsPerStep) / numBitsPerStep; i < end; ++i)
{
if (bits[i] != 0)
{
continue;
}
var idx = i * numBitsPerStep;
var num = math.min(idx + numBitsPerStep, endBit) - idx;
if (idx != beginBit)
{
var test = bits[idx / numBitsPerStep - 1];
var newIdx = math.max(idx - math.lzcnt(test), beginBit);
num += idx - newIdx;
idx = newIdx;
}
for (++i; i < end; ++i)
{
if (num >= numBits)
{
return idx;
}
var test = bits[i];
var pos = i * numBitsPerStep;
num += math.min(pos + math.tzcnt(test), endBit) - pos;
if (test != 0)
{
break;
}
}
if (num >= numBits)
{
return idx;
}
}
return endBit;
}
static int FindUint(ulong* ptr, int beginBit, int endBit, int numBits)
{
var bits = (uint*)ptr;
var numSteps = (numBits + 31) >> 5;
var numBitsPerStep = 32;
var maxBits = numSteps * numBitsPerStep;
for (int i = beginBit / numBitsPerStep, end = AlignUp(endBit, numBitsPerStep) / numBitsPerStep; i < end; ++i)
{
if (bits[i] != 0)
{
continue;
}
var idx = i * numBitsPerStep;
var num = math.min(idx + numBitsPerStep, endBit) - idx;
if (idx != beginBit)
{
var test = bits[idx / numBitsPerStep - 1];
var newIdx = math.max(idx - math.lzcnt(test), beginBit);
num += idx - newIdx;
idx = newIdx;
}
for (++i; i < end; ++i)
{
if (num >= numBits)
{
return idx;
}
var test = bits[i];
var pos = i * numBitsPerStep;
num += math.min(pos + math.tzcnt(test), endBit) - pos;
if (test != 0)
{
break;
}
}
if (num >= numBits)
{
return idx;
}
}
return endBit;
}
static int FindUshort(ulong* ptr, int beginBit, int endBit, int numBits)
{
var bits = (ushort*)ptr;
var numSteps = (numBits + 15) >> 4;
var numBitsPerStep = 16;
var maxBits = numSteps * numBitsPerStep;
for (int i = beginBit / numBitsPerStep, end = AlignUp(endBit, numBitsPerStep) / numBitsPerStep; i < end; ++i)
{
if (bits[i] != 0)
{
continue;
}
var idx = i * numBitsPerStep;
var num = math.min(idx + numBitsPerStep, endBit) - idx;
if (idx != beginBit)
{
var test = bits[idx / numBitsPerStep - 1];
var newIdx = math.max(idx - lzcnt(test), beginBit);
num += idx - newIdx;
idx = newIdx;
}
for (++i; i < end; ++i)
{
if (num >= numBits)
{
return idx;
}
var test = bits[i];
var pos = i * numBitsPerStep;
num += math.min(pos + tzcnt(test), endBit) - pos;
if (test != 0)
{
break;
}
}
if (num >= numBits)
{
return idx;
}
}
return endBit;
}
static int FindByte(ulong* ptr, int beginBit, int endBit, int numBits)
{
var bits = (byte*)ptr;
var numSteps = (numBits + 7) >> 3;
var numBitsPerStep = 8;
var maxBits = numSteps * numBitsPerStep;
for (int i = beginBit / numBitsPerStep, end = AlignUp(endBit, numBitsPerStep) / numBitsPerStep; i < end; ++i)
{
if (bits[i] != 0)
{
continue;
}
var idx = i * numBitsPerStep;
var num = math.min(idx + numBitsPerStep, endBit) - idx;
if (idx != beginBit)
{
var test = bits[idx / numBitsPerStep - 1];
var newIdx = math.max(idx - lzcnt(test), beginBit);
num += idx - newIdx;
idx = newIdx;
}
for (++i; i < end; ++i)
{
if (num >= numBits)
{
return idx;
}
var test = bits[i];
var pos = i * numBitsPerStep;
num += math.min(pos + tzcnt(test), endBit) - pos;
if (test != 0)
{
break;
}
}
if (num >= numBits)
{
return idx;
}
}
return endBit;
}
static int FindUpto14bits(ulong* ptr, int beginBit, int endBit, int numBits)
{
var bits = (byte*)ptr;
var bit = (byte)(beginBit & 7);
byte beginMask = (byte)~(0xff << bit);
var lz = 0;
for (int begin = beginBit / 8, end = AlignUp(endBit, 8) / 8, i = begin; i < end; ++i)
{
var test = bits[i];
test |= i == begin ? beginMask : (byte)0;
if (test == 0xff)
{
continue;
}
var pos = i * 8;
var tz = math.min(pos + tzcnt(test), endBit) - pos;
if (lz + tz >= numBits)
{
return pos - lz;
}
lz = lzcnt(test);
var idx = pos + 8;
var newIdx = math.max(idx - lz, beginBit);
lz = math.min(idx, endBit) - newIdx;
if (lz >= numBits)
{
return newIdx;
}
}
return endBit;
}
static int FindUpto6bits(ulong* ptr, int beginBit, int endBit, int numBits)
{
var bits = (byte*)ptr;
byte beginMask = (byte)~(0xff << (beginBit & 7));
byte endMask = (byte)~(0xff >> ((8-(endBit & 7) & 7)));
var mask = 1 << numBits - 1;
for (int begin = beginBit / 8, end = AlignUp(endBit, 8) / 8, i = begin; i < end; ++i)
{
var test = bits[i];
test |= i == begin ? beginMask : (byte)0;
test |= i == end - 1 ? endMask : (byte)0;
if (test == 0xff)
{
continue;
}
for (int pos = i * 8, posEnd = pos + 7; pos < posEnd; ++pos)
{
var tz = tzcnt((byte)(test^0xff));
test >>= tz;
pos += tz;
if ((test & mask) == 0)
{
return pos;
}
test >>= 1;
}
}
return endBit;
}
internal static int FindWithBeginEnd(ulong* ptr, int beginBit, int endBit, int numBits)
{
int idx;
if (numBits >= 127)
{
idx = FindUlong(ptr, beginBit, endBit, numBits);
if (idx != endBit)
{
return idx;
}
}
if (numBits >= 63)
{
idx = FindUint(ptr, beginBit, endBit, numBits);
if (idx != endBit)
{
return idx;
}
}
if (numBits >= 128)
{
// early out - no smaller step will find this gap
return int.MaxValue;
}
if (numBits >= 31)
{
idx = FindUshort(ptr, beginBit, endBit, numBits);
if (idx != endBit)
{
return idx;
}
}
if (numBits >= 64)
{
// early out - no smaller step will find this gap
return int.MaxValue;
}
idx = FindByte(ptr, beginBit, endBit, numBits);
if (idx != endBit)
{
return idx;
}
if (numBits < 15)
{
idx = FindUpto14bits(ptr, beginBit, endBit, numBits);
if (idx != endBit)
{
return idx;
}
if (numBits < 7)
{
// The worst case scenario when every byte boundary bit is set (pattern 0x81),
// and we're looking for 6 or less bits. It will rescan byte-by-byte to find
// any inner byte gap.
idx = FindUpto6bits(ptr, beginBit, endBit, numBits);
if (idx != endBit)
{
return idx;
}
}
}
return int.MaxValue;
}
internal static int Find(ulong* ptr, int pos, int count, int numBits) => FindWithBeginEnd(ptr, pos, pos + count, numBits);
}
/// <summary>
/// A 32-bit array of bits.
/// </summary>
/// <remarks>
/// Stack allocated, so it does not require thread safety checks or disposal.
/// </remarks>
[DebuggerTypeProxy(typeof(BitField32DebugView))]
[BurstCompatible]
public struct BitField32
{
/// <summary>
/// The 32 bits, stored as a uint.
/// </summary>
/// <value>The 32 bits, stored as a uint.</value>
public uint Value;
/// <summary>
/// Initializes and returns an instance of BitField32.
/// </summary>
/// <param name="initialValue">Initial value of the bit field. Default is 0.</param>
public BitField32(uint initialValue = 0u)
{
Value = initialValue;
}
/// <summary>
/// Clears all the bits to 0.
/// </summary>
public void Clear()
{
Value = 0u;
}
/// <summary>
/// Sets a single bit to 1 or 0.
/// </summary>
/// <param name="pos">Position in this bit field to set (must be 0-31).</param>
/// <param name="value">If true, sets the bit to 1. If false, sets the bit to 0.</param>
/// <exception cref="ArgumentException">Thrown if `pos`is out of range.</exception>
public void SetBits(int pos, bool value)
{
CheckArgs(pos, 1);
Value = Bitwise.SetBits(Value, pos, 1, value);
}
/// <summary>
/// Sets one or more contiguous bits to 1 or 0.
/// </summary>
/// <param name="pos">Position in the bit field of the first bit to set (must be 0-31).</param>
/// <param name="value">If true, sets the bits to 1. If false, sets the bits to 0.</param>
/// <param name="numBits">Number of bits to set (must be 1-32).</param>
/// <exception cref="ArgumentException">Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 32.</exception>
public void SetBits(int pos, bool value, int numBits)
{
CheckArgs(pos, numBits);
var mask = 0xffffffffu >> (32 - numBits);
Value = Bitwise.SetBits(Value, pos, mask, value);
}
/// <summary>
/// Returns one or more contiguous bits from the bit field as the lower bits of a uint.
/// </summary>
/// <param name="pos">Position in the bit field of the first bit to get (must be 0-31).</param>
/// <param name="numBits">Number of bits to get (must be 1-32).</param>
/// <exception cref="ArgumentException">Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 32.</exception>
/// <returns>The requested range of bits from the bit field stored in the least-significant bits of a uint. All other bits of the uint will be 0.</returns>
public uint GetBits(int pos, int numBits = 1)
{
CheckArgs(pos, numBits);
var mask = 0xffffffffu >> (32 - numBits);
return Bitwise.ExtractBits(Value, pos, mask);
}
/// <summary>
/// Returns true if the bit at a position is 1.
/// </summary>
/// <param name="pos">Position in the bit field (must be 0-31).</param>
/// <returns>True if the bit at the position is 1.</returns>
public bool IsSet(int pos)
{
return 0 != GetBits(pos);
}
/// <summary>
/// Returns true if none of the bits in a contiguous range are 1.
/// </summary>
/// <param name="pos">Position in the bit field (must be 0-31).</param>
/// <param name="numBits">Number of bits to test (must be 1-32).</param>
/// <exception cref="ArgumentException">Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 32.</exception>
/// <returns>True if none of the bits in the contiguous range are 1.</returns>
public bool TestNone(int pos, int numBits = 1)
{
return 0u == GetBits(pos, numBits);
}
/// <summary>
/// Returns true if any of the bits in a contiguous range are 1.
/// </summary>
/// <param name="pos">Position in the bit field (must be 0-31).</param>
/// <param name="numBits">Number of bits to test (must be 1-32).</param>
/// <exception cref="ArgumentException">Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 32.</exception>
/// <returns>True if at least one bit in the contiguous range is 1.</returns>
public bool TestAny(int pos, int numBits = 1)
{
return 0u != GetBits(pos, numBits);
}
/// <summary>
/// Returns true if all of the bits in a contiguous range are 1.
/// </summary>
/// <param name="pos">Position in the bit field (must be 0-31).</param>
/// <param name="numBits">Number of bits to test (must be 1-32).</param>
/// <exception cref="ArgumentException">Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 32.</exception>
/// <returns>True if all bits in the contiguous range are 1.</returns>
public bool TestAll(int pos, int numBits = 1)
{
CheckArgs(pos, numBits);
var mask = 0xffffffffu >> (32 - numBits);
return mask == Bitwise.ExtractBits(Value, pos, mask);
}
/// <summary>
/// Returns the number of bits that are 1.
/// </summary>
/// <returns>The number of bits that are 1.</returns>
public int CountBits()
{
return math.countbits(Value);
}
/// <summary>
/// Returns the number of leading zeroes.
/// </summary>
/// <returns>The number of leading zeros.</returns>
public int CountLeadingZeros()
{
return math.lzcnt(Value);
}
/// <summary>
/// Returns the number of trailing zeros.
/// </summary>
/// <returns>The number of trailing zeros.</returns>
public int CountTrailingZeros()
{
return math.tzcnt(Value);
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
static void CheckArgs(int pos, int numBits)
{
if (pos > 31
|| numBits == 0
|| numBits > 32
|| pos + numBits > 32)
{
throw new ArgumentException($"BitField32 invalid arguments: pos {pos} (must be 0-31), numBits {numBits} (must be 1-32).");
}
}
}
sealed class BitField32DebugView
{
BitField32 BitField;
public BitField32DebugView(BitField32 bitfield)
{
BitField = bitfield;
}
public bool[] Bits
{
get
{
var array = new bool[32];
for (int i = 0; i < 32; ++i)
{
array[i] = BitField.IsSet(i);
}
return array;
}
}
}
/// <summary>
/// A 64-bit array of bits.
/// </summary>
/// <remarks>
/// Stack allocated, so it does not require thread safety checks or disposal.
/// </remarks>
[DebuggerTypeProxy(typeof(BitField64DebugView))]
[BurstCompatible]
public struct BitField64
{
/// <summary>
/// The 64 bits, stored as a ulong.
/// </summary>
/// <value>The 64 bits, stored as a uint.</value>
public ulong Value;
/// <summary>
/// Initializes and returns an instance of BitField64.
/// </summary>
/// <param name="initialValue">Initial value of the bit field. Default is 0.</param>
public BitField64(ulong initialValue = 0ul)
{
Value = initialValue;
}
/// <summary>
/// Clears all bits to 0.
/// </summary>
public void Clear()
{
Value = 0ul;
}
/// <summary>
/// Sets a single bit to 1 or 0.
/// </summary>
/// <param name="pos">Position in this bit field to set (must be 0-63).</param>
/// <param name="value">If true, sets the bit to 1. If false, sets the bit to 0.</param>
/// <exception cref="ArgumentException">Thrown if `pos`is out of range.</exception>
public void SetBits(int pos, bool value)
{
CheckArgs(pos, 1);
Value = Bitwise.SetBits(Value, pos, 1, value);
}
/// <summary>
/// Sets one or more contiguous bits to 1 or 0.
/// </summary>
/// <param name="pos">Position in the bit field of the first bit to set (must be 0-63).</param>
/// <param name="value">If true, sets the bits to 1. If false, sets the bits to 0.</param>
/// <param name="numBits">Number of bits to set (must be 1-64).</param>
/// <exception cref="ArgumentException">Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 64.</exception>
public void SetBits(int pos, bool value, int numBits = 1)
{
CheckArgs(pos, numBits);
var mask = 0xfffffffffffffffful >> (64 - numBits);
Value = Bitwise.SetBits(Value, pos, mask, value);
}
/// <summary>
/// Returns one or more contiguous bits from the bit field as the lower bits of a ulong.
/// </summary>
/// <param name="pos">Position in the bit field of the first bit to get (must be 0-63).</param>
/// <param name="numBits">Number of bits to get (must be 1-64).</param>
/// <exception cref="ArgumentException">Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 64.</exception>
/// <returns>The requested range of bits from the bit field stored in the least-significant bits of a ulong. All other bits of the ulong will be 0.</returns>
public ulong GetBits(int pos, int numBits = 1)
{
CheckArgs(pos, numBits);
var mask = 0xfffffffffffffffful >> (64 - numBits);
return Bitwise.ExtractBits(Value, pos, mask);
}
/// <summary>
/// Returns true if the bit at a position is 1.
/// </summary>
/// <param name="pos">Position in the bit field (must be 0-63).</param>
/// <returns>True if the bit at the position is 1.</returns>
public bool IsSet(int pos)
{
return 0ul != GetBits(pos);
}
/// <summary>
/// Returns true if none of the bits in a contiguous range are 1.
/// </summary>
/// <param name="pos">Position in the bit field (must be 0-63).</param>
/// <param name="numBits">Number of bits to test (must be 1-64).</param>
/// <exception cref="ArgumentException">Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 64.</exception>
/// <returns>True if none of the bits in the contiguous range are 1.</returns>
public bool TestNone(int pos, int numBits = 1)
{
return 0ul == GetBits(pos, numBits);
}
/// <summary>
/// Returns true if any of the bits in a contiguous range are 1.
/// </summary>
/// <param name="pos">Position in the bit field (must be 0-63).</param>
/// <param name="numBits">Number of bits to test (must be 1-64).</param>
/// <exception cref="ArgumentException">Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 64.</exception>
/// <returns>True if at least one bit in the contiguous range is 1.</returns>
public bool TestAny(int pos, int numBits = 1)
{
return 0ul != GetBits(pos, numBits);
}
/// <summary>
/// Returns true if all of the bits in a contiguous range are 1.
/// </summary>
/// <param name="pos">Position in the bit field (must be 0-63).</param>
/// <param name="numBits">Number of bits to test (must be 1-64).</param>
/// <exception cref="ArgumentException">Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 64.</exception>
/// <returns>True if all bits in the contiguous range are 1.</returns>
public bool TestAll(int pos, int numBits = 1)
{
CheckArgs(pos, numBits);
var mask = 0xfffffffffffffffful >> (64 - numBits);
return mask == Bitwise.ExtractBits(Value, pos, mask);
}
/// <summary>
/// Returns the number of bits that are 1.
/// </summary>
/// <returns>The number of bits that are 1.</returns>
public int CountBits()
{
return math.countbits(Value);
}
/// <summary>
/// Returns the number of leading zeroes.
/// </summary>
/// <returns>The number of leading zeros.</returns>
public int CountLeadingZeros()
{
return math.lzcnt(Value);
}
/// <summary>
/// Returns the number of trailing zeros.
/// </summary>
/// <returns>The number of trailing zeros.</returns>
public int CountTrailingZeros()
{
return math.tzcnt(Value);
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
static void CheckArgs(int pos, int numBits)
{
if (pos > 63
|| numBits == 0
|| numBits > 64
|| pos + numBits > 64)
{
throw new ArgumentException($"BitField32 invalid arguments: pos {pos} (must be 0-63), numBits {numBits} (must be 1-64).");
}
}
}
sealed class BitField64DebugView
{
BitField64 Data;
public BitField64DebugView(BitField64 data)
{
Data = data;
}
public bool[] Bits
{
get
{
var array = new bool[64];
for (int i = 0; i < 64; ++i)
{
array[i] = Data.IsSet(i);
}
return array;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ec1f6f558fc0b9a4fac43b21ec79cd5a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,84 @@
using System;
namespace Unity.Collections
{
/// <summary>
/// Documents and enforces (via generated tests) that the tagged method or property has to stay burst compatible.
/// </summary>
/// <remarks>This attribute cannot be used with private methods or properties.</remarks>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Constructor, AllowMultiple = true)]
public class BurstCompatibleAttribute : Attribute
{
/// <summary>
/// Burst compatible compile target.
/// </summary>
public enum BurstCompatibleCompileTarget
{
/// <summary>
/// Player.
/// </summary>
Player,
/// <summary>
/// Editor.
/// </summary>
Editor,
/// <summary>
/// Player and editor.
/// </summary>
PlayerAndEditor
}
/// <summary>
/// Types to be used for the declared generic type or method.
/// </summary>
/// <remarks>
/// The generic type arguments are tracked separately for types and methods. Say a generic type also contains
/// a generic method, like in the case of Foo&lt;T&gt;.Bar&lt;U&gt;(T baz, U blah). You must specify
/// GenericTypeArguments for Foo and also for Bar to establish the concrete types for T and U. When code
/// generation occurs for the Burst compatibility tests, any time T appears (in the definition of Foo)
/// it will be replaced with the generic type argument you specified for Foo and whenever U appears
/// (in method Bar's body) it will be replaced by whatever generic type argument you specified for the method
/// Bar.
/// </remarks>
public Type[] GenericTypeArguments { get; set; }
/// <summary>
/// Specifies the symbol that must be defined in order for the method to be tested for Burst compatibility.
/// </summary>
public string RequiredUnityDefine = null;
/// <summary>
/// Specifies whether code should be Burst compiled for the player, editor, or both.
/// </summary>
/// <remarks>
/// When set to BurstCompatibleCompileTarget.Editor, the generated Burst compatibility code will be
/// surrounded by #if UNITY_EDITOR to ensure that the Burst compatibility test will only be executed in the
/// editor. The code will be compiled with Burst function pointers. If you have a non-null RequiredUnityDefine,
/// an #if with the RequiredUnityDefine will also be emitted.<para/> <para/>
///
/// When set to BurstCompatibilityCompileTarget.Player, the generated Burst compatibility code will
/// only be surrounded by an #if containing the RequiredUnityDefine (or nothing if RequiredUnityDefine is null).
/// Instead of compiling with Burst function pointers, a player build is started where the Burst AOT compiler
/// will verify the Burst compatibility. This is done to speed up Burst compilation for the compatibility tests
/// since Burst function pointer compilation is not done in parallel.<para/> <para/>
///
/// When set to BurstCompatibilityCompileTarget.PlayerAndEditor, the generated Burst compatibility code will
/// only be surrounded by an #if containing the RequiredUnityDefine (or nothing if RequiredUnityDefine is null).
/// The code will be compiled both by the editor (using Burst function pointers) and with a player build (using
/// Burst AOT).<para/> <para/>
///
/// For best performance of the Burst compatibility tests, prefer to use BurstCompatibilityCompileTarget.Player
/// as much as possible.
/// </remarks>
public BurstCompatibleCompileTarget CompileTarget = BurstCompatibleCompileTarget.Player;
}
/// <summary>
/// Internal attribute to state that a method is not burst compatible even though the containing type is.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Constructor)]
public class NotBurstCompatibleAttribute : Attribute
{
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cf57aae738013234baa585dee44cf5ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,482 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Burst;
using Unity.Burst.CompilerServices;
using Unity.Jobs;
using Unity.Jobs.LowLevel.Unsafe;
using Unity.Mathematics;
#if !NET_DOTS
using System.Reflection;
#endif
namespace Unity.Collections
{
/// <summary>
/// For scheduling release of unmanaged resources.
/// </summary>
public interface INativeDisposable : IDisposable
{
/// <summary>
/// Creates and schedules a job that will release all resources (memory and safety handles) of this collection.
/// </summary>
/// <param name="inputDeps">A job handle which the newly scheduled job will depend upon.</param>
/// <returns>The handle of a new job that will release all resources (memory and safety handles) of this collection.</returns>
JobHandle Dispose(JobHandle inputDeps);
}
/// <summary>
/// Provides helper methods for collections.
/// </summary>
[BurstCompatible]
public static class CollectionHelper
{
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
internal static void CheckAllocator(AllocatorManager.AllocatorHandle allocator)
{
if (!ShouldDeallocate(allocator))
throw new ArgumentException($"Allocator {allocator} must not be None or Invalid");
}
/// <summary>
/// The size in bytes of the current platform's L1 cache lines.
/// </summary>
/// <value>The size in bytes of the current platform's L1 cache lines.</value>
public const int CacheLineSize = JobsUtility.CacheLineSize;
[StructLayout(LayoutKind.Explicit)]
internal struct LongDoubleUnion
{
[FieldOffset(0)]
internal long longValue;
[FieldOffset(0)]
internal double doubleValue;
}
/// <summary>
/// Returns the binary logarithm of the `value`, but the result is rounded down to the nearest integer.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The binary logarithm of the `value`, but the result is rounded down to the nearest integer.</returns>
public static int Log2Floor(int value)
{
return 31 - math.lzcnt((uint)value);
}
/// <summary>
/// Returns the binary logarithm of the `value`, but the result is rounded up to the nearest integer.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The binary logarithm of the `value`, but the result is rounded up to the nearest integer.</returns>
public static int Log2Ceil(int value)
{
return 32 - math.lzcnt((uint)value - 1);
}
/// <summary>
/// Returns an allocation size in bytes that factors in alignment.
/// </summary>
/// <example><code>
/// // 55 aligned to 16 is 64.
/// int size = CollectionHelper.Align(55, 16);
/// </code></example>
/// <param name="size">The size to align.</param>
/// <param name="alignmentPowerOfTwo">A non-zero, positive power of two.</param>
/// <returns>The smallest integer that is greater than or equal to `size` and is a multiple of `alignmentPowerOfTwo`.</returns>
/// <exception cref="ArgumentException">Thrown if `alignmentPowerOfTwo` is not a non-zero, positive power of two.</exception>
public static int Align(int size, int alignmentPowerOfTwo)
{
if (alignmentPowerOfTwo == 0)
return size;
CheckIntPositivePowerOfTwo(alignmentPowerOfTwo);
return (size + alignmentPowerOfTwo - 1) & ~(alignmentPowerOfTwo - 1);
}
/// <summary>
/// Returns an allocation size in bytes that factors in alignment.
/// </summary>
/// <example><code>
/// // 55 aligned to 16 is 64.
/// ulong size = CollectionHelper.Align(55, 16);
/// </code></example>
/// <param name="size">The size to align.</param>
/// <param name="alignmentPowerOfTwo">A non-zero, positive power of two.</param>
/// <returns>The smallest integer that is greater than or equal to `size` and is a multiple of `alignmentPowerOfTwo`.</returns>
/// <exception cref="ArgumentException">Thrown if `alignmentPowerOfTwo` is not a non-zero, positive power of two.</exception>
public static ulong Align(ulong size, ulong alignmentPowerOfTwo)
{
if (alignmentPowerOfTwo == 0)
return size;
CheckUlongPositivePowerOfTwo(alignmentPowerOfTwo);
return (size + alignmentPowerOfTwo - 1) & ~(alignmentPowerOfTwo - 1);
}
/// <summary>
/// Returns true if the address represented by the pointer has a given alignment.
/// </summary>
/// <param name="p">The pointer.</param>
/// <param name="alignmentPowerOfTwo">A non-zero, positive power of two.</param>
/// <returns>True if the address is a multiple of `alignmentPowerOfTwo`.</returns>
/// <exception cref="ArgumentException">Thrown if `alignmentPowerOfTwo` is not a non-zero, positive power of two.</exception>
public static unsafe bool IsAligned(void* p, int alignmentPowerOfTwo)
{
CheckIntPositivePowerOfTwo(alignmentPowerOfTwo);
return ((ulong)p & ((ulong)alignmentPowerOfTwo - 1)) == 0;
}
/// <summary>
/// Returns true if an offset has a given alignment.
/// </summary>
/// <param name="offset">An offset</param>
/// <param name="alignmentPowerOfTwo">A non-zero, positive power of two.</param>
/// <returns>True if the offset is a multiple of `alignmentPowerOfTwo`.</returns>
/// <exception cref="ArgumentException">Thrown if `alignmentPowerOfTwo` is not a non-zero, positive power of two.</exception>
public static bool IsAligned(ulong offset, int alignmentPowerOfTwo)
{
CheckIntPositivePowerOfTwo(alignmentPowerOfTwo);
return (offset & ((ulong)alignmentPowerOfTwo - 1)) == 0;
}
/// <summary>
/// Returns true if a positive value is a non-zero power of two.
/// </summary>
/// <remarks>Result is invalid if `value &lt; 0`.</remarks>
/// <param name="value">A positive value.</param>
/// <returns>True if the value is a non-zero, positive power of two.</returns>
public static bool IsPowerOfTwo(int value)
{
return (value & (value - 1)) == 0;
}
/// <summary>
/// Returns a (non-cryptographic) hash of a memory block.
/// </summary>
/// <remarks>The hash function used is [djb2](http://web.archive.org/web/20190508211657/http://www.cse.yorku.ca/~oz/hash.html).</remarks>
/// <param name="ptr">A buffer.</param>
/// <param name="bytes">The number of bytes to hash.</param>
/// <returns>A hash of the bytes.</returns>
public static unsafe uint Hash(void* ptr, int bytes)
{
// djb2 - Dan Bernstein hash function
// http://web.archive.org/web/20190508211657/http://www.cse.yorku.ca/~oz/hash.html
byte* str = (byte*)ptr;
ulong hash = 5381;
while (bytes > 0)
{
ulong c = str[--bytes];
hash = ((hash << 5) + hash) + c;
}
return (uint)hash;
}
[NotBurstCompatible /* Used only for debugging. */]
internal static void WriteLayout(Type type)
{
#if !NET_DOTS
Console.WriteLine($" Offset | Bytes | Name Layout: {0}", type.Name);
var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var field in fields)
{
Console.WriteLine(" {0, 6} | {1, 6} | {2}"
, Marshal.OffsetOf(type, field.Name)
, Marshal.SizeOf(field.FieldType)
, field.Name
);
}
#else
_ = type;
#endif
}
internal static bool ShouldDeallocate(AllocatorManager.AllocatorHandle allocator)
{
// Allocator.Invalid == container is not initialized.
// Allocator.None == container is initialized, but container doesn't own data.
return allocator.ToAllocator > Allocator.None;
}
/// <summary>
/// Tell Burst that an integer can be assumed to map to an always positive value.
/// </summary>
/// <param name="value">The integer that is always positive.</param>
/// <returns>Returns `x`, but allows the compiler to assume it is always positive.</returns>
[return: AssumeRange(0, int.MaxValue)]
internal static int AssumePositive(int value)
{
return value;
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
[BurstDiscard] // Must use BurstDiscard because UnsafeUtility.IsUnmanaged is not burstable.
[NotBurstCompatible /* Used only for debugging. */]
internal static void CheckIsUnmanaged<T>()
{
if (!UnsafeUtility.IsValidNativeContainerElementType<T>())
{
throw new ArgumentException($"{typeof(T)} used in native collection is not blittable, not primitive, or contains a type tagged as NativeContainer");
}
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
internal static void CheckIntPositivePowerOfTwo(int value)
{
var valid = (value > 0) && ((value & (value - 1)) == 0);
if (!valid)
{
throw new ArgumentException($"Alignment requested: {value} is not a non-zero, positive power of two.");
}
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
internal static void CheckUlongPositivePowerOfTwo(ulong value)
{
var valid = (value > 0) && ((value & (value - 1)) == 0);
if (!valid)
{
throw new ArgumentException($"Alignment requested: {value} is not a non-zero, positive power of two.");
}
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
internal static void CheckIndexInRange(int index, int length)
{
if (index < 0)
throw new IndexOutOfRangeException($"Index {index} must be positive.");
if (index >= length)
throw new IndexOutOfRangeException($"Index {index} is out of range in container of '{length}' Length.");
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
internal static void CheckCapacityInRange(int capacity, int length)
{
if (capacity < 0)
throw new ArgumentOutOfRangeException($"Capacity {capacity} must be positive.");
if (capacity < length)
throw new ArgumentOutOfRangeException($"Capacity {capacity} is out of range in container of '{length}' Length.");
}
/// <summary>
/// Create a NativeArray, using a provided allocator that implements IAllocator.
/// </summary>
/// <param name="length">The number of elements to allocate.</param>
/// <param name="allocator">The allocator to use.</param>
/// <param name="options">Options for allocation, such as whether to clear the memory.</param>
/// <returns>Returns the NativeArray that was created.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(AllocatorManager.AllocatorHandle) })]
public static NativeArray<T> CreateNativeArray<T, U>(int length, ref U allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory)
where T : struct
where U : unmanaged, AllocatorManager.IAllocator
{
NativeArray<T> nativeArray;
if (!allocator.IsCustomAllocator)
{
nativeArray = new NativeArray<T>(length, allocator.ToAllocator, options);
}
else
{
nativeArray = new NativeArray<T>();
nativeArray.Initialize(length, ref allocator, options);
}
return nativeArray;
}
/// <summary>
/// Create a NativeArray, using a provided AllocatorHandle.
/// </summary>
/// <param name="length">The number of elements to allocate.</param>
/// <param name="allocator">The AllocatorHandle to use.</param>
/// <param name="options">Options for allocation, such as whether to clear the memory.</param>
/// <returns>Returns the NativeArray that was created.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(int) })]
public static NativeArray<T> CreateNativeArray<T>(int length, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory)
where T : struct
{
NativeArray<T> nativeArray;
if(!AllocatorManager.IsCustomAllocator(allocator))
{
nativeArray = new NativeArray<T>(length, allocator.ToAllocator, options);
}
else
{
nativeArray = new NativeArray<T>();
nativeArray.Initialize(length, allocator, options);
}
return nativeArray;
}
/// <summary>
/// Create a NativeArray from another NativeArray, using a provided AllocatorHandle.
/// </summary>
/// <param name="array">The NativeArray to make a copy of.</param>
/// <param name="allocator">The AllocatorHandle to use.</param>
/// <returns>Returns the NativeArray that was created.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(int) })]
public static NativeArray<T> CreateNativeArray<T>(NativeArray<T> array, AllocatorManager.AllocatorHandle allocator)
where T : struct
{
NativeArray<T> nativeArray;
if (!AllocatorManager.IsCustomAllocator(allocator))
{
nativeArray = new NativeArray<T>(array, allocator.ToAllocator);
}
else
{
nativeArray = new NativeArray<T>();
nativeArray.Initialize(array.Length, allocator);
nativeArray.CopyFrom(array);
}
return nativeArray;
}
/// <summary>
/// Create a NativeArray from a managed array, using a provided AllocatorHandle.
/// </summary>
/// <param name="array">The managed array to make a copy of.</param>
/// <param name="allocator">The AllocatorHandle to use.</param>
/// <returns>Returns the NativeArray that was created.</returns>
[NotBurstCompatible]
public static NativeArray<T> CreateNativeArray<T>(T[] array, AllocatorManager.AllocatorHandle allocator)
where T : struct
{
NativeArray<T> nativeArray;
if (!AllocatorManager.IsCustomAllocator(allocator))
{
nativeArray = new NativeArray<T>(array, allocator.ToAllocator);
}
else
{
nativeArray = new NativeArray<T>();
nativeArray.Initialize(array.Length, allocator);
nativeArray.CopyFrom(array);
}
return nativeArray;
}
/// <summary>
/// Create a NativeArray from a managed array, using a provided Allocator.
/// </summary>
/// <param name="array">The managed array to make a copy of.</param>
/// <param name="allocator">The Allocator to use.</param>
/// <returns>Returns the NativeArray that was created.</returns>
[NotBurstCompatible]
public static NativeArray<T> CreateNativeArray<T, U>(T[] array, ref U allocator)
where T : struct
where U : unmanaged, AllocatorManager.IAllocator
{
NativeArray<T> nativeArray;
if (!allocator.IsCustomAllocator)
{
nativeArray = new NativeArray<T>(array, allocator.ToAllocator);
}
else
{
nativeArray = new NativeArray<T>();
nativeArray.Initialize(array.Length, ref allocator);
nativeArray.CopyFrom(array);
}
return nativeArray;
}
/// <summary>
/// Create a NativeMultiHashMap from a managed array, using a provided Allocator.
/// </summary>
/// <param name="length">The desired capacity of the NativeMultiHashMap.</param>
/// <param name="allocator">The Allocator to use.</param>
/// <returns>Returns the NativeMultiHashMap that was created.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(int), typeof(AllocatorManager.AllocatorHandle) })]
public static NativeMultiHashMap<TKey, TValue> CreateNativeMultiHashMap<TKey, TValue, U>(int length, ref U allocator)
where TKey : struct, IEquatable<TKey>
where TValue : struct
where U : unmanaged, AllocatorManager.IAllocator
{
var nativeMultiHashMap = new NativeMultiHashMap<TKey, TValue>();
nativeMultiHashMap.Initialize(length, ref allocator);
return nativeMultiHashMap;
}
#if ENABLE_UNITY_COLLECTIONS_CHECKS
[BurstCompatible(RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS")]
internal static AtomicSafetyHandle CreateSafetyHandle(AllocatorManager.AllocatorHandle allocator)
{
if (allocator.IsCustomAllocator)
{
return AtomicSafetyHandle.Create();
}
return (allocator.ToAllocator == Allocator.Temp) ? AtomicSafetyHandle.GetTempMemoryHandle() : AtomicSafetyHandle.Create();
}
[BurstCompatible(RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS")]
internal static void DisposeSafetyHandle(ref AtomicSafetyHandle safety)
{
AtomicSafetyHandle.CheckDeallocateAndThrow(safety);
// If the safety handle is for a temp allocation, create a new safety handle for this instance which can be marked as invalid
// Setting it to new AtomicSafetyHandle is not enough since the handle needs a valid node pointer in order to give the correct errors
if (AtomicSafetyHandle.IsTempMemoryHandle(safety))
{
int staticSafetyId = safety.staticSafetyId;
safety = AtomicSafetyHandle.Create();
safety.staticSafetyId = staticSafetyId;
}
AtomicSafetyHandle.Release(safety);
}
static unsafe void CreateStaticSafetyIdInternal(ref int id, in FixedString512Bytes name)
{
id = AtomicSafetyHandle.NewStaticSafetyId(name.GetUnsafePtr(), name.Length);
}
[BurstDiscard]
static void CreateStaticSafetyIdInternal<T>(ref int id)
{
CreateStaticSafetyIdInternal(ref id, typeof(T).ToString());
}
/// <summary>
/// Returns a static safety id which better identifies resources in safety system messages.
/// </summary>
/// <remarks>This is preferable to AtomicSafetyHandle.NewStaticSafetyId as it is compatible with burst.</remarks>
/// <param name="name">The name of the resource type.</param>
/// <returns>An int representing the static safety id for this resource.</returns>
[BurstCompatible(RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS", GenericTypeArguments = new[] { typeof(NativeArray<int>) })]
public static void SetStaticSafetyId<T>(ref AtomicSafetyHandle handle, ref int sharedStaticId)
{
if (sharedStaticId == 0)
{
// This will eventually either work with burst supporting a subset of typeof()
// or something similar to Burst.BurstRuntime.GetTypeName() will be implemented
// JIRA issue https://jira.unity3d.com/browse/DOTS-5685
CreateStaticSafetyIdInternal<T>(ref sharedStaticId);
}
AtomicSafetyHandle.SetStaticSafetyId(ref handle, sharedStaticId);
}
/// <summary>
/// Returns a static safety id which better identifies resources in safety system messages.
/// </summary>
/// <remarks>This is preferable to AtomicSafetyHandle.NewStaticSafetyId as it is compatible with burst.</remarks>
/// <param name="name">The name of the resource type.</param>
/// <returns>An int representing the static safety id for this resource.</returns>
[BurstCompatible(RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS")]
public static unsafe void SetStaticSafetyId(ref AtomicSafetyHandle handle, ref int sharedStaticId, FixedString512Bytes name)
{
if (sharedStaticId == 0)
{
CreateStaticSafetyIdInternal(ref sharedStaticId, name);
}
AtomicSafetyHandle.SetStaticSafetyId(ref handle, sharedStaticId);
}
#endif
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6901d664ec07a214cab08cbd83ca8deb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,168 @@
using System;
using System.Threading;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
#pragma warning disable 0649
namespace Unity.Collections
{
internal struct Long8
{
internal long f0,f1,f2,f3,f4,f5,f6,f7;
}
internal struct Long64
{
internal Long8 f0,f1,f2,f3,f4,f5,f6,f7;
}
internal struct Long512
{
internal Long64 f0,f1,f2,f3,f4,f5,f6,f7;
}
internal struct Long1024 : IIndexable<long>
{
internal Long512 f0,f1;
public int Length { get { return 1024;} set {} }
public ref long ElementAt(int index)
{
unsafe { fixed(Long512* p = &f0) {
return ref UnsafeUtility.AsRef<long>((long*)p + index);
} }
}
}
internal class ConcurrentMask
{
internal static void longestConsecutiveOnes(long value, out int offset, out int count)
{
count = 0;
var newvalue = value;
while(newvalue != 0)
{
value = newvalue;
newvalue = value & (long)((ulong)value >> 1);
++count;
}
offset = math.tzcnt(value);
}
internal static bool foundAtLeastThisManyConsecutiveOnes(long value, int minimum, out int offset, out int count)
{
if(minimum == 1)
{
offset = math.tzcnt(value); // find offset of first 1 bit
count = 1;
return offset != 64;
}
longestConsecutiveOnes(value, out offset, out count);
return count >= minimum;
}
internal static bool foundAtLeastThisManyConsecutiveZeroes(long value, int minimum, out int offset, out int count)
{
return foundAtLeastThisManyConsecutiveOnes(~value, minimum, out offset, out count);
}
internal const int ErrorFailedToFree = -1;
internal const int ErrorFailedToAllocate = -2;
internal const int EmptyBeforeAllocation = 0;
internal const int EmptyAfterFree = 0;
internal static bool Succeeded(int error)
{
return error >= 0;
}
internal static long MakeMask(int offset, int bits)
{
return (long)(~0UL >> (64-bits)) << offset;
}
internal static int TryAllocate(ref long l, int offset, int bits)
{
var mask = MakeMask(offset, bits);
var readValue = Interlocked.Read(ref l);
long oldReadValue, writtenValue;
do
{
if((readValue & mask) != 0)
return ErrorFailedToAllocate;
writtenValue = readValue | mask;
oldReadValue = readValue;
readValue = Interlocked.CompareExchange(ref l, writtenValue, oldReadValue);
} while(readValue != oldReadValue);
return math.countbits(readValue); // how many bits were set, before i allocated? sometimes if 0, do something special (allocate chunk?)
}
internal static int TryFree(ref long l, int offset, int bits)
{
var mask = MakeMask(offset, bits);
var readValue = Interlocked.Read(ref l);
long oldReadValue, writtenValue;
do
{
if((readValue & mask) != mask)
return ErrorFailedToFree;
writtenValue = readValue & ~mask;
oldReadValue = readValue;
readValue = Interlocked.CompareExchange(ref l, writtenValue, oldReadValue);
} while(readValue != oldReadValue);
return math.countbits(writtenValue); // how many bits are set, after i freed? sometimes if 0, do something special (free chunk?)
}
internal static int TryAllocate(ref long l, out int offset, int bits)
{
var readValue = Interlocked.Read(ref l);
long oldReadValue, writtenValue;
do
{
if(!foundAtLeastThisManyConsecutiveZeroes(readValue, bits, out offset, out int _))
return ErrorFailedToAllocate;
var mask = MakeMask(offset, bits);
writtenValue = readValue | mask;
oldReadValue = readValue;
readValue = Interlocked.CompareExchange(ref l, writtenValue, oldReadValue);
} while(readValue != oldReadValue);
return math.countbits(readValue); // how many bits were set, before i allocated? sometimes if 0, do something special (allocate chunk?)
}
internal static int TryAllocate<T>(ref T t, int offset, int bits) where T : IIndexable<long>
{
var wordOffset = offset >> 6;
var bitOffset = offset & 63;
return TryAllocate(ref t.ElementAt(wordOffset), bitOffset, bits);
}
internal static int TryFree<T>(ref T t, int offset, int bits) where T : IIndexable<long>
{
var wordOffset = offset >> 6;
var bitOffset = offset & 63;
return TryFree(ref t.ElementAt(wordOffset), bitOffset, bits);
}
internal static int TryAllocate<T>(ref T t, out int offset, int begin, int end, int bits) where T : IIndexable<long>
{
for(var wordOffset = begin; wordOffset < end; ++wordOffset)
{
int error, bitOffset;
error = TryAllocate(ref t.ElementAt(wordOffset), out bitOffset, bits);
if(Succeeded(error))
{
offset = wordOffset * 64 + bitOffset;
return error;
}
}
offset = -1;
return ErrorFailedToAllocate;
}
internal static int TryAllocate<T>(ref T t, out int offset, int bits) where T : IIndexable<long>
{
return TryAllocate(ref t, out offset, 0, t.Length, bits);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c6561bdb03b3b415a9725d8af46e62d4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,53 @@
using System;
using System.Collections;
namespace Unity.Collections
{
internal struct Pair<Key, Value>
{
public Key key;
public Value value;
public Pair(Key k, Value v)
{
key = k;
value = v;
}
#if !NET_DOTS
public override string ToString()
{
return $"{key} = {value}";
}
#endif
}
// Tiny does not contains an IList definition (or even ICollection)
#if !NET_DOTS
internal struct ListPair<Key, Value> where Value : IList
{
public Key key;
public Value value;
public ListPair(Key k, Value v)
{
key = k;
value = v;
}
public override string ToString()
{
String result = $"{key} = [";
for (var v = 0; v < value.Count; ++v)
{
result += value[v];
if (v < value.Count - 1)
result += ", ";
}
result += "]";
return result;
}
}
#endif
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 66302e43465db41bc9a7b142b2157ac6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bfb5034a42e3f8f4d97776cf8d2f0e37
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fb24b35aa8286aa45a866596124a241d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f16da17e34bf5f149a196c3f6b32e37f
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ab361814f7174e46989e7f79e89f7c24
timeCreated: 1590896536

View file

@ -0,0 +1,723 @@
<#/*THIS IS A T4 FILE - see t4_text_templating.md for what it is and how to run codegen*/#>
<#@ template debug="True" #>
<#@ output extension=".gen.cs" encoding="utf-8" #>
<#@ assembly name="System.Core" #>
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// TextTransform Samples/Packages/com.unity.collections/Unity.Collections/FixedString.tt
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
using UnityEngine.Internal;
using UnityEngine;
#if UNITY_PROPERTIES_EXISTS
using Unity.Properties;
#endif
namespace Unity.Collections
{
// A temporary copy of a struct is made before it is displayed in a C# debugger.
// However, only the first element of data members with names is copied at this time.
// Therefore, it's important that all data visible in the debugger, has a name
// and includes no 'fixed' array. This is why we name every byte in the following struct.
/// <summary>
/// <undoc /> [FixedBytes will be removed]
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Explicit, Size=16)]
[BurstCompatible]
public struct FixedBytes16
{
<#
for(var i = 0; i < 16; ++i)
{
var offset = i.ToString("D4");
#>
/// <summary>
/// For internal use only.
/// </summary>
[FieldOffset(<#=i#>)] public byte byte<#=offset#>;
<#
}
#>
}
<#
{
var SIZES = new [] {32,64,128,512,4096};
foreach (var BYTES in SIZES) {
// 2 bytes for the ushort length
var MAXLENGTH = BYTES - 2;
var TYPENAME = $"FixedString{BYTES}Bytes";
var OLD_TYPENAME = $"FixedString{BYTES}";
#>
// A temporary copy of a struct is made before it is displayed in a C# debugger.
// However, only the first element of data members with names is copied at this time.
// Therefore, it's important that all data visible in the debugger, has a name
// and includes no 'fixed' array. This is why we name every byte in the following struct.
/// <summary>
/// For internal use only.
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Explicit, Size=<#=MAXLENGTH#>)]
[BurstCompatible]
public struct FixedBytes<#=MAXLENGTH#>
{
<#
for(var i = 0; i < (MAXLENGTH/16)*16; i += 16)
{
var offset = i.ToString("D4");
#>
/// <summary>
/// For internal use only.
/// </summary>
[FieldOffset(<#=i#>)] public FixedBytes16 offset<#=offset#>;
<#
}
for(var i = (MAXLENGTH/16)*16; i < MAXLENGTH; ++i)
{
var offset = i.ToString("D4");
#>
/// <summary>
/// For internal use only.
/// </summary>
[FieldOffset(<#=i#>)] public byte byte<#=offset#>;
<#
}
#>
}
[Obsolete("Renamed to <#=TYPENAME#> (UnityUpgradable) -> <#=TYPENAME#>", true)]
public partial struct <#=OLD_TYPENAME#> {}
/// <summary>
/// An unmanaged UTF-8 string whose content is stored directly in the <#=BYTES#>-byte struct.
/// </summary>
/// <remarks>
/// The binary layout of this string is guaranteed, for now and all time, to be a length (a little-endian two byte integer)
/// followed by the bytes of the characters (with no padding). A zero byte always immediately follows the last character.
/// Effectively, the number of bytes for storing characters is 3 less than <#=BYTES#> (two length bytes and one null byte).
///
/// This layout is identical to a <see cref="FixedList<#=BYTES#>Bytes{T}"/> of bytes, thus allowing reinterpretation between FixedString<#=BYTES#>Bytes and FixedList<#=BYTES#>Bytes.
///
/// By virtue of being an unmanaged, non-allocated struct with no pointers, this string is fully compatible with jobs and Burst compilation.
/// Unlike managed string types, these strings can be put in any unmanaged ECS components, FixedList, or any other unmanaged structs.
/// </remarks>
[Serializable]
[StructLayout(LayoutKind.Sequential, Size=<#=BYTES#>)]
[BurstCompatible]
public partial struct <#=TYPENAME#>
: INativeList<byte>
, IUTF8Bytes
, IComparable<String>
, IEquatable<String>
<#
foreach (var OTHERBYTES in SIZES)
{
#>
, IComparable<FixedString<#=OTHERBYTES#>Bytes>
, IEquatable<FixedString<#=OTHERBYTES#>Bytes>
<#
}
#>
{
internal const ushort utf8MaxLengthInBytes = <#=MAXLENGTH-1#>;
[SerializeField] internal ushort utf8LengthInBytes;
[SerializeField] internal FixedBytes<#=MAXLENGTH#> bytes;
/// <summary>
/// Returns the maximum number of UTF-8 bytes that can be stored in this string.
/// </summary>
/// <returns>
/// The maximum number of UTF-8 bytes that can be stored in this string.
/// </returns>
public static int UTF8MaxLengthInBytes => utf8MaxLengthInBytes;
/// <summary>
/// For internal use only. Use <see cref="ToString"/> instead.
/// </summary>
/// <value>For internal use only. Use <see cref="ToString"/> instead.</value>
[CreateProperty]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[NotBurstCompatible]
public string Value => ToString();
/// <summary>
/// Returns a pointer to the character bytes.
/// </summary>
/// <returns>A pointer to the character bytes.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe byte* GetUnsafePtr()
{
return (byte*) UnsafeUtility.AddressOf(ref bytes);
}
/// <summary>
/// The current length in bytes of this string's content.
/// </summary>
/// <remarks>
/// The length value does not include the null-terminator byte.
/// </remarks>
/// <param name="value">The new length in bytes of the string's content.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown if the new length is out of bounds.</exception>
/// <value>
/// The current length in bytes of this string's content.
/// </value>
public int Length
{
get
{
return utf8LengthInBytes;
}
set
{
CheckLengthInRange(value);
utf8LengthInBytes = (ushort)value;
unsafe
{
GetUnsafePtr()[utf8LengthInBytes] = 0;
}
}
}
/// <summary>
/// The number of bytes this string has for storing UTF-8 characters.
/// </summary>
/// <value>The number of bytes this string has for storing UTF-8 characters.</value>
/// <remarks>
/// Does not include the null-terminator byte.
///
/// A setter is included for conformity with <see cref="INativeList{T}"/>, but <see cref="Capacity"/> is fixed at <#=BYTES-3#>.
/// Setting the value to anything other than <#=BYTES-3#> throws an exception.
///
/// In UTF-8 encoding, each Unicode code point (character) requires 1 to 4 bytes,
/// so the number of characters that can be stored may be less than the capacity.
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">Thrown if attempting to set the capacity to anything other than <#=BYTES-3#>.</exception>
public int Capacity
{
get
{
return utf8MaxLengthInBytes;
}
set
{
CheckCapacityInRange(value);
}
}
/// <summary>
/// Attempts to set the length in bytes. Does nothing if the new length is invalid.
/// </summary>
/// <param name="newLength">The desired length.</param>
/// <param name="clearOptions">Whether added or removed bytes should be cleared (zeroed). (Increasing the length adds bytes; decreasing the length removes bytes.)</param>
/// <returns>True if the new length is valid.</returns>
public bool TryResize(int newLength, NativeArrayOptions clearOptions = NativeArrayOptions.ClearMemory)
{
if (newLength < 0 || newLength > utf8MaxLengthInBytes)
return false;
if (newLength == utf8LengthInBytes)
return true;
unsafe
{
if (clearOptions == NativeArrayOptions.ClearMemory)
{
if (newLength > utf8LengthInBytes)
UnsafeUtility.MemClear(GetUnsafePtr() + utf8LengthInBytes, newLength - utf8LengthInBytes);
else
UnsafeUtility.MemClear(GetUnsafePtr() + newLength, utf8LengthInBytes - newLength);
}
utf8LengthInBytes = (ushort)newLength;
// always null terminate
GetUnsafePtr()[utf8LengthInBytes] = 0;
}
return true;
}
/// <summary>
/// Returns true if this string is empty (has no characters).
/// </summary>
/// <value>True if this string is empty (has no characters).</value>
public bool IsEmpty => utf8LengthInBytes == 0;
/// <summary>
/// Returns the byte (not character) at an index.
/// </summary>
/// <param name="index">A 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]
{
get
{
unsafe
{
CheckIndexInRange(index);
return GetUnsafePtr()[index];
}
}
set
{
unsafe
{
CheckIndexInRange(index);
GetUnsafePtr()[index] = value;
}
}
}
/// <summary>
/// Returns the reference to a byte (not character) at an index.
/// </summary>
/// <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)
{
unsafe
{
CheckIndexInRange(index);
return ref GetUnsafePtr()[index];
}
}
/// <summary>
/// Sets the length to 0.
/// </summary>
public void Clear()
{
Length = 0;
}
/// <summary>
/// Appends a byte.
/// </summary>
/// <remarks>
/// A zero byte will always follow the newly appended byte.
///
/// No validation is performed: it is your responsibility for the bytes of the string to form valid UTF-8 when you're done appending bytes.
/// </remarks>
/// <param name="value">A byte to append.</param>
public void Add(in byte value)
{
this[Length++] = value;
}
/// <summary>
/// An enumerator over the characters (not bytes) of a FixedString<#=BYTES#>Bytes.
/// </summary>
/// <remarks>
/// In an enumerator's initial state, <see cref="Current"/> is not valid to read.
/// The first <see cref="MoveNext"/> call advances the enumerator's index to the first character.
/// </remarks>
public struct Enumerator : IEnumerator
{
FixedString<#=BYTES#>Bytes target;
int offset;
Unicode.Rune current;
/// <summary>
/// Initializes and returns an instance of FixedString<#=BYTES#>Bytes.Enumerator.
/// </summary>
/// <param name="other">A FixeString<#=BYTES#> for which to create an enumerator.</param>
public Enumerator(FixedString<#=BYTES#>Bytes other)
{
target = other;
offset = 0;
current = default;
}
/// <summary>
/// Does nothing.
/// </summary>
public void Dispose()
{
}
/// <summary>
/// Advances the enumerator to the next character.
/// </summary>
/// <returns>True if <see cref="Current"/> is valid to read after the call.</returns>
public bool MoveNext()
{
if (offset >= target.Length)
return false;
unsafe
{
Unicode.Utf8ToUcs(out current, target.GetUnsafePtr(), ref offset, target.Length);
}
return true;
}
/// <summary>
/// Resets the enumerator to its initial state.
/// </summary>
public void Reset()
{
offset = 0;
current = default;
}
/// <summary>
/// The current character.
/// </summary>
/// <remarks>
/// In an enumerator's initial state, <see cref="Current"/> is not valid to read.
/// </remarks>
/// <value>The current character.</value>
public Unicode.Rune Current => current;
object IEnumerator.Current => Current;
}
/// <summary>
/// Returns an enumerator for iterating over the characters of this string.
/// </summary>
/// <returns>An enumerator for iterating over the characters of the FixedString<#=BYTES#>Bytes.</returns>
public Enumerator GetEnumerator()
{
return new Enumerator(this);
}
/// <summary>
/// Returns the lexicographical sort order of this string relative to another.
/// </summary>
/// <param name="other">A `System.String` to compare with.</param>
/// <returns>An integer denoting the lexicographical sort order of this string relative to the other:
///
/// 0 denotes both strings have the same sort position.<br/>
/// -1 denotes that this string should be sorted to precede the other string.<br/>
/// +1 denotes that this string should be sorted to follow the other string.<br/>
/// </returns>
[NotBurstCompatible]
public int CompareTo(String other)
{
return ToString().CompareTo(other);
}
/// <summary>
/// Returns true if this string and another have the same length and all the same characters.
/// </summary>
/// <param name="other">A string to compare for equality.</param>
/// <returns>True if this string and the other have the same length and all the same characters.</returns>
[NotBurstCompatible]
public bool Equals(String other)
{
unsafe {
int alen = utf8LengthInBytes;
int blen = other.Length;
byte* aptr = (byte*) UnsafeUtilityExtensions.AddressOf(bytes);
fixed(char* bptr = other)
{
return UTF8ArrayUnsafeUtility.StrCmp(aptr, alen, bptr, blen) == 0;
}
}
}
/// <summary>
/// Returns a reference to a FixedList<#=BYTES#>Bytes<byte> representation of this string.
/// </summary>
/// <remarks>
/// The referenced FixedListByte<#=BYTES#> is the very same bytes as the original FixedString<#=BYTES#>Bytes,
/// so it is only valid as long as the original FixedString<#=BYTES#>Bytes is valid.
/// </remarks>
/// <returns>A ref to a FixedListByte<#=BYTES#> representation of the FixedString<#=BYTES#>Bytes.</returns>
public unsafe ref FixedList<#=BYTES#>Bytes<byte> AsFixedList()
{
return ref UnsafeUtility.AsRef<FixedList<#=BYTES#>Bytes<byte>>(UnsafeUtility.AddressOf(ref this));
}
/// <summary>
/// Initializes and returns an instance of FixedString<#=BYTES#>Bytes with the characters copied from a string.
/// </summary>
/// <param name="source">The source string to copy.</param>
[NotBurstCompatible]
public FixedString<#=BYTES#>Bytes(String source)
{
this = default;
var error = Initialize(source);
CheckCopyError((CopyError)error, source);
}
/// <summary>
/// Initializes an instance of FixedString<#=BYTES#>Bytes with the characters copied from a string.
/// </summary>
/// <param name="source">The source string to copy.</param>
/// <returns>zero on success, or non-zero on error.</returns>
[NotBurstCompatible]
internal int Initialize(String source)
{
bytes = default;
utf8LengthInBytes = 0;
unsafe
{
fixed (char* sourceptr = source)
{
var error = UTF8ArrayUnsafeUtility.Copy(GetUnsafePtr(), out utf8LengthInBytes, utf8MaxLengthInBytes, sourceptr, source.Length);
if(error != CopyError.None)
return (int)error;
this.Length = utf8LengthInBytes;
}
}
return 0;
}
/// <summary>
/// Initializes and returns an instance of FixedString<#=BYTES#>Bytes with a single character repeatedly appended some number of times.
/// </summary>
/// <param name="rune">The Unicode.Rune to repeat.</param>
/// <param name="count">The number of times to repeat the character. Default is 1.</param>
public FixedString<#=BYTES#>Bytes(Unicode.Rune rune, int count = 1)
{
this = default;
Initialize(rune, count);
}
/// <summary>
/// Initializes an instance of FixedString<#=BYTES#>Bytes with a single character repeatedly appended some number of times.
/// </summary>
/// <param name="rune">The Unicode.Rune to repeat.</param>
/// <param name="count">The number of times to repeat the character. Default is 1.</param>
/// <returns>zero on success, or non-zero on error.</returns>
internal int Initialize(Unicode.Rune rune, int count = 1)
{
this = default;
return (int)this.Append(rune, count);
}
<#
//
// Generate easy conversion and comparison between this and other FixedString types
//
foreach (var OTHERBYTES in SIZES)
{
#>
/// <summary>
/// Returns the lexicographical sort order of this string relative to another.
/// </summary>
/// <param name="other">A string to compare with.</param>
/// <returns>A number denoting the lexicographical sort order of this string relative to the other:
///
/// 0 denotes that both strings have the same sort position.<br/>
/// -1 denotes that this string should be sorted to precede the other.<br/>
/// +1 denotes that this string should be sorted to follow the other.<br/>
/// </returns>
public int CompareTo(FixedString<#=OTHERBYTES#>Bytes other)
{
return FixedStringMethods.CompareTo(ref this, other);
}
/// <summary>
/// Initializes and returns an instance of FixedString<#=BYTES#>Bytes that is a copy of another string.
/// </summary>
/// <param name="other">The string to copy.</param>
/// <exception cref="IndexOutOfRangeException">Thrown if the string to copy's length exceeds the capacity of FixedString<#=BYTES#>Bytes.</exception>
public FixedString<#=BYTES#>Bytes(in FixedString<#=OTHERBYTES#>Bytes other)
{
this = default;
var error = Initialize(other);
CheckFormatError((FormatError)error);
}
/// <summary>
/// Initializes an instance of FixedString<#=BYTES#>Bytes that is a copy of another string.
/// </summary>
/// <param name="other">The string to copy.</param>
/// <returns>zero on success, or non-zero on error.</returns>
internal int Initialize(in FixedString<#=OTHERBYTES#>Bytes other)
{
bytes = default;
utf8LengthInBytes = 0;
unsafe {
int len = 0;
byte* dstBytes = GetUnsafePtr();
byte* srcBytes = (byte*) UnsafeUtilityExtensions.AddressOf(other.bytes);
var srcLength = other.utf8LengthInBytes;
var error = UTF8ArrayUnsafeUtility.AppendUTF8Bytes(dstBytes, ref len, utf8MaxLengthInBytes, srcBytes, srcLength);
if(error != FormatError.None)
return (int)error;
this.Length = len;
}
return 0;
}
/// <summary>
/// Returns true if a FixedString<#=BYTES#>Bytes and another string are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="a">A FixedString<#=BYTES#>Bytes to compare for equality.</param>
/// <param name="b">A FixedString<#=OTHERBYTES#>Bytes to compare for equality.</param>
/// <returns>True if the two strings are equal.</returns>
public static bool operator ==(in FixedString<#=BYTES#>Bytes a, in FixedString<#=OTHERBYTES#>Bytes b)
{
// this must not call any methods on 'a' or 'b'
unsafe {
int alen = a.utf8LengthInBytes;
int blen = b.utf8LengthInBytes;
byte* aptr = (byte*) UnsafeUtilityExtensions.AddressOf(a.bytes);
byte* bptr = (byte*) UnsafeUtilityExtensions.AddressOf(b.bytes);
return UTF8ArrayUnsafeUtility.EqualsUTF8Bytes(aptr, alen, bptr, blen);
}
}
/// <summary>
/// Returns true if a FixedString<#=BYTES#>Bytes and another string are unequal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="a">A FixedString<#=BYTES#>Bytes to compare for inequality.</param>
/// <param name="b">A FixedString<#=OTHERBYTES#>Bytes to compare for inequality.</param>
/// <returns>True if the two strings are unequal.</returns>
public static bool operator !=(in FixedString<#=BYTES#>Bytes a, in FixedString<#=OTHERBYTES#>Bytes b)
{
return !(a == b);
}
/// <summary>
/// Returns true if this string and another string are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="other">A FixedString<#=OTHERBYTES#>Bytes to compare for equality.</param>
/// <returns>True if the two strings are equal.</returns>
public bool Equals(FixedString<#=OTHERBYTES#>Bytes other)
{
return this == other;
}
<#
if (OTHERBYTES > BYTES)
{
// Generate implicit conversions to bigger-sized FixedStrings
#>
/// <summary>
/// Returns a new FixedString<#=OTHERBYTES#>Bytes that is a copy of another string.
/// </summary>
/// <param name="fs">A FixedString<#=BYTES#>Bytes to copy.</param>
/// <returns>A new FixedString<#=OTHERBYTES#>Bytes that is a copy of the other string.</returns>
/// <exception cref="IndexOutOfRangeException">Thrown if the string to copy's length exceeds the capacity of FixedString<#=OTHERBYTES#>Bytes.</exception>
public static implicit operator FixedString<#=OTHERBYTES#>Bytes(in FixedString<#=BYTES#>Bytes fs) => new FixedString<#=OTHERBYTES#>Bytes(in fs);
<#
}
}
#>
/// <summary>
/// Returns a new FixedString<#=BYTES#>Bytes that is a copy of another string.
/// </summary>
/// <param name="b">A string to copy.</param>
/// <returns>A new FixedString<#=BYTES#>Bytes that is a copy of another string.</returns>
/// <exception cref="IndexOutOfRangeException">Thrown if the string to copy's length exceeds the capacity of FixedString<#=BYTES#>Bytes.</exception>
[NotBurstCompatible]
public static implicit operator FixedString<#=BYTES#>Bytes(string b) => new FixedString<#=BYTES#>Bytes(b);
/// <summary>
/// Returns a new managed string that is a copy of this string.
/// </summary>
/// <returns>A new managed string that is a copy of this string.</returns>
[NotBurstCompatible]
public override String ToString()
{
return this.ConvertToString();
}
/// <summary>
/// Returns a hash code of this string.
/// </summary>
/// <remarks>Only the character bytes are included in the hash: any bytes beyond <see cref="Length"/> are not part of the hash.</remarks>
/// <returns>The hash code of this string.</returns>
public override int GetHashCode()
{
return this.ComputeHashCode();
}
/// <summary>
/// Returns true if this string and an object are equal.
/// </summary>
/// <remarks>
/// Returns false if the object is neither a System.String or a FixedString.
///
/// Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="obj">An object to compare for equality.</param>
/// <returns>True if this string and the object are equal.</returns>
[NotBurstCompatible]
public override bool Equals(object obj)
{
if(ReferenceEquals(null, obj)) return false;
if(obj is String aString) return Equals(aString);
<#
foreach(var OTHERBYTES in SIZES)
{
var OTHERTYPENAME = "FixedString" + OTHERBYTES + "Bytes";
WriteLine(" if(obj is {0} a{0}) return Equals(a{0});", OTHERTYPENAME);
}
#>
return false;
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckIndexInRange(int index)
{
if (index < 0)
throw new IndexOutOfRangeException($"Index {index} must be positive.");
if (index >= utf8LengthInBytes)
throw new IndexOutOfRangeException($"Index {index} is out of range in FixedString<#=BYTES#>Bytes of '{utf8LengthInBytes}' Length.");
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckLengthInRange(int length)
{
if (length < 0)
throw new ArgumentOutOfRangeException($"Length {length} must be positive.");
if (length > utf8MaxLengthInBytes)
throw new ArgumentOutOfRangeException($"Length {length} is out of range in FixedString<#=BYTES#>Bytes of '{utf8MaxLengthInBytes}' Capacity.");
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckCapacityInRange(int capacity)
{
if (capacity > utf8MaxLengthInBytes)
throw new ArgumentOutOfRangeException($"Capacity {capacity} must be lower than {utf8MaxLengthInBytes}.");
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
static void CheckCopyError(CopyError error, String source)
{
if (error != CopyError.None)
throw new ArgumentException($"FixedString<#=BYTES#>Bytes: {error} while copying \"{source}\"");
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
static void CheckFormatError(FormatError error)
{
if (error != FormatError.None)
throw new ArgumentException("Source is too long to fit into fixed string of this size");
}
}
<#}}#>
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0a845769eb1dc4f459f64e5c840d07de
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,419 @@
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Collections
{
/// <summary>
/// Provides extension methods for FixedString*N*Bytes.
/// </summary>
[BurstCompatible]
public unsafe static partial class FixedStringMethods
{
/// <summary>
/// Appends a Unicode.Rune to this string.
/// </summary>
/// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
/// <param name="fs">A FixedString*N*Bytes.</param>
/// <param name="rune">A Unicode.Rune to append.</param>
/// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static FormatError Append<T>(ref this T fs, Unicode.Rune rune)
where T : struct, INativeList<byte>, 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);
}
/// <summary>
/// Appends a char to this string.
/// </summary>
/// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
/// <param name="fs">A FixedString*N*Bytes.</param>
/// <param name="ch">A char to append.</param>
/// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static FormatError Append<T>(ref this T fs, char ch)
where T : struct, INativeList<byte>, IUTF8Bytes
{
return fs.Append((Unicode.Rune) ch);
}
/// <summary>
/// Appends a byte to this string.
/// </summary>
/// <remarks>
/// No validation is performed: it is your responsibility for the data to be valid UTF-8 when you're done appending bytes.
/// </remarks>
/// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
/// <param name="fs">A FixedString*N*Bytes.</param>
/// <param name="a">A byte to append.</param>
/// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static FormatError AppendRawByte<T>(ref this T fs, byte a)
where T : struct, INativeList<byte>, IUTF8Bytes
{
var origLength = fs.Length;
if (!fs.TryResize(origLength + 1, NativeArrayOptions.UninitializedMemory))
return FormatError.Overflow;
fs.GetUnsafePtr()[origLength] = a;
return FormatError.None;
}
/// <summary>
/// Appends a Unicode.Rune a number of times to this string.
/// </summary>
/// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
/// <param name="fs">A FixedString*N*Bytes.</param>
/// <param name="rune">A Unicode.Rune to append some number of times.</param>
/// <param name="count">The number of times to append the rune.</param>
/// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static FormatError Append<T>(ref this T fs, Unicode.Rune rune, int count)
where T : struct, INativeList<byte>, 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;
}
/// <summary>
/// Appends a number (converted to UTF-8 characters) to this string.
/// </summary>
/// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
/// <param name="fs">A FixedString*N*Bytes.</param>
/// <param name="input">A long integer to append to the string.</param>
/// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static FormatError Append<T>(ref this T fs, long input)
where T : struct, INativeList<byte>, 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);
}
/// <summary>
/// Appends a number (converted to UTF-8 characters) to this string.
/// </summary>
/// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
/// <param name="fs">A FixedString*N*Bytes.</param>
/// <param name="input">An int to append to the string.</param>
/// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static FormatError Append<T>(ref this T fs, int input)
where T : struct, INativeList<byte>, IUTF8Bytes
{
return fs.Append((long)input);
}
/// <summary>
/// Appends a number (converted to UTF-8 characters) to this string.
/// </summary>
/// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
/// <param name="fs">A FixedString*N*Bytes.</param>
/// <param name="input">A ulong integer to append to the string.</param>
/// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static FormatError Append<T>(ref this T fs, ulong input)
where T : struct, INativeList<byte>, 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);
}
/// <summary>
/// Appends a number (converted to UTF-8 characters) to this string.
/// </summary>
/// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
/// <param name="fs">A FixedString*N*Bytes.</param>
/// <param name="input">A uint to append to the string.</param>
/// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static FormatError Append<T>(ref this T fs, uint input)
where T : struct, INativeList<byte>, IUTF8Bytes
{
return fs.Append((ulong)input);
}
/// <summary>
/// Appends a number (converted to UTF-8 characters) to this string.
/// </summary>
/// <typeparam name="T">The type of FixedString*N*Bytes.</typeparam>
/// <param name="fs">A FixedString*N*Bytes.</param>
/// <param name="input">A float to append to the string.</param>
/// <param name="decimalSeparator">The character to use as the decimal separator. Defaults to a period ('.').</param>
/// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the string is exceeded.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static FormatError Append<T>(ref this T fs, float input, char decimalSeparator = '.')
where T : struct, INativeList<byte>, 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;
}
/// <summary>
/// Appends another string to this string.
/// </summary>
/// <remarks>
/// When the method returns an error, the destination string is not modified.
/// </remarks>
/// <typeparam name="T">The type of the destination string.</typeparam>
/// <typeparam name="T2">The type of the source string.</typeparam>
/// <param name="fs">The destination string.</param>
/// <param name="input">The source string.</param>
/// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the destination string is exceeded.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes), typeof(FixedString128Bytes) })]
public static FormatError Append<T,T2>(ref this T fs, in T2 input)
where T : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
{
ref var inputRef = ref UnsafeUtilityExtensions.AsRef(input);
return fs.Append(inputRef.GetUnsafePtr(), inputRef.Length);
}
/// <summary>
/// Copies another string to this string (making the two strings equal).
/// </summary>
/// <remarks>
/// When the method returns an error, the destination string is not modified.
/// </remarks>
/// <typeparam name="T">The type of the destination string.</typeparam>
/// <typeparam name="T2">The type of the source string.</typeparam>
/// <param name="fs">The destination string.</param>
/// <param name="input">The source string.</param>
/// <returns>CopyError.None if successful. Returns CopyError.Truncation if the source string is too large to fit in the destination.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes), typeof(FixedString128Bytes) })]
public static CopyError CopyFrom<T, T2>(ref this T fs, in T2 input)
where T : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
{
fs.Length = 0;
var fe = Append(ref fs, input);
if (fe != FormatError.None)
return CopyError.Truncation;
return CopyError.None;
}
/// <summary>
/// Appends bytes to this string.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <typeparam name="T">The type of the destination string.</typeparam>
/// <param name="fs">The destination string.</param>
/// <param name="utf8Bytes">The bytes to append.</param>
/// <param name="utf8BytesLength">The number of bytes to append.</param>
/// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the destination string is exceeded.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public unsafe static FormatError Append<T>(ref this T fs, byte* utf8Bytes, int utf8BytesLength)
where T : struct, INativeList<byte>, 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;
}
/// <summary>
/// Appends another string to this string.
/// </summary>
/// <remarks>
/// When the method returns an error, the destination string is not modified.
/// </remarks>
/// <typeparam name="T">The type of the destination string.</typeparam>
/// <param name="fs">The destination string.</param>
/// <param name="s">The string to append.</param>
/// <returns>FormatError.None if successful. Returns FormatError.Overflow if the capacity of the destination string is exceeded.</returns>
[NotBurstCompatible]
public unsafe static FormatError Append<T>(ref this T fs, string s)
where T : struct, INativeList<byte>, 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);
}
/// <summary>
/// Copies another string to this string (making the two strings equal).
/// </summary>
/// <remarks>
/// When the method returns an error, the destination string is not modified.
/// </remarks>
/// <typeparam name="T">The type of the destination string.</typeparam>
/// <param name="fs">The destination string.</param>
/// <param name="s">The source string.</param>
/// <returns>CopyError.None if successful. Returns CopyError.Truncation if the source string is too large to fit in the destination.</returns>
[NotBurstCompatible]
public static CopyError CopyFrom<T>(ref this T fs, string s)
where T : struct, INativeList<byte>, IUTF8Bytes
{
fs.Length = 0;
var fe = Append(ref fs, s);
if (fe != FormatError.None)
return CopyError.Truncation;
return CopyError.None;
}
/// <summary>
/// Copies another string to this string. If the string exceeds the capacity it will be truncated.
/// </summary>
/// <typeparam name="T">The type of the destination string.</typeparam>
/// <param name="fs">The destination string.</param>
/// <param name="s">The source string.</param>
[NotBurstCompatible]
public static void CopyFromTruncated<T>(ref this T fs, string s)
where T : struct, INativeList<byte>, IUTF8Bytes
{
int utf8Len;
fixed (char* chars = s)
{
UTF8ArrayUnsafeUtility.Copy(fs.GetUnsafePtr(), out utf8Len, fs.Capacity, chars, s.Length);
fs.Length = utf8Len;
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b3d1c84db8ef4e288ba9089a616a3523
timeCreated: 1591061243

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 66f7a4570af244dfacfded8cc0c60aec
timeCreated: 1593669547

View file

@ -0,0 +1,168 @@
<#/*THIS IS A T4 FILE - see t4_text_templating.md for what it is and how to run codegen*/#>
<#@ template debug="True" #>
<#@ output extension=".gen.cs" encoding="utf-8" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Reflection" #>
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// TextTransform Samples/Packages/com.unity.collections/Unity.Collections/FixedStringFormat.tt
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Runtime.InteropServices;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.Internal;
<#
List<List<T>> AllCombinationsOf<T>(List<List<T>> sets)
{
// need array bounds checking etc for production
var combinations = new List<List<T>>();
// prime the data
foreach (var value in sets[0])
combinations.Add(new List<T> { value });
foreach (var set in sets.Skip(1))
combinations = AddExtraSet(combinations, set);
return combinations;
}
List<List<T>> AddExtraSet<T>(List<List<T>> combinations, List<T> set)
{
var newCombinations = from value in set
from combination in combinations
select new List<T>(combination) { value };
return newCombinations.ToList();
}
string WithCommas(IEnumerable<string> input)
{
return string.Join(", ", input);
}
var ARGTYPES = new[] { "int", "float", "string", "FixedStringN", null };
var ARGCOUNT = 4;
var ARGSETS = new List<List<string>>();
for (int i = 0; i < ARGCOUNT; ++i)
ARGSETS.Add(ARGTYPES.ToList());
var ARGCOMBOS = AllCombinationsOf(ARGSETS);
#>
namespace Unity.Collections
{
/// <summary>
/// Provides formatting methods for FixedString*N*.
/// </summary>
[BurstCompatible]
public static class FixedString
{
<#
foreach (var COMBO in ARGCOMBOS)
{
while (COMBO.Count != 0 && COMBO.Last() == null)
COMBO.RemoveAt(COMBO.Count - 1);
if (COMBO.Count == 0 || COMBO.IndexOf(null) != -1)
continue;
var numFixedStringN = COMBO.Count((s) => s == "FixedStringN");
// turn FixedStringN into T1..Tn
var GENERICSPARAM = "";
var BCOMPAT = "";
if (COMBO.Contains("string"))
{
BCOMPAT = "[NotBurstCompatible]";
}
var GENERICSCONSTRAINT = new StringBuilder();
var TxDOCS = new StringBuilder();
if (numFixedStringN > 0)
{
GENERICSPARAM = $"<{string.Join(",", Enumerable.Range(1, numFixedStringN).Select((n) => $"T{n}"))}>";
if (!COMBO.Contains("string"))
{
BCOMPAT = $"[BurstCompatible(GenericTypeArguments = new[] {{ {string.Join(", ", Enumerable.Range(1, numFixedStringN).Select((n) => $"typeof(FixedString32Bytes /*$T{n}*/)"))} }} )]";
}
for (int i = 0; i < numFixedStringN; ++i)
{
var index = COMBO.IndexOf("FixedStringN");
COMBO[index] = $"T{i + 1}";
GENERICSCONSTRAINT.Append($" where T{i + 1} : struct, INativeList<byte>, IUTF8Bytes\n");
TxDOCS.Append($" /// <typeparam name=\"T{i + 1}\"><undoc /></typeparam>\r\n");
}
}
var ARGS = Enumerable.Range(0, COMBO.Count).Select((n) => $"{COMBO[n]} arg{n}");
var ARGNAMES = Enumerable.Range(0, COMBO.Count).Select((n) => $"arg{n}").ToList();
var CONVERSIONS = new StringBuilder();
for (int i = 0; i < COMBO.Count; ++i)
{
if (COMBO[i].StartsWith("T"))
continue;
CONVERSIONS.Append($" FixedString32Bytes carg{i} = default; carg{i}.Append(arg{i});\n");
ARGNAMES[i] = $"carg{i}";
}
var RETURNTYPE = "FixedString128Bytes";
if (COMBO.Count > 3)
RETURNTYPE = "FixedString512Bytes";
var ARGxDOCS = String.Join("\r\n /// ", Enumerable.Range(0, COMBO.Count).Select(n => $"<param name=\"arg{n}\">Value to interpolate into the format string.</param>"));
#>
/// <summary>
/// Returns a new string produced by interpolating a format string.
/// </summary>
/// <remarks>
/// Similar to StringBuilder.AppendFormat but with significant limitations:
/// - Only supports numeric format placeholders of the form `{0}` .. `{N}`.
/// - No format modifiers (*e.g.* `{0:x}`) are supported.
///
/// The various overloads of this method take up to four values for interpolation. The allowed argument types are:
/// - FixedString*N*Bytes
/// - string
/// - int
/// - float
/// - structs implementing INativeList&lt;byte&gt; and IUTF8Bytes
///
/// <seealso cref="FixedStringMethods.AppendFormat"/>
/// </remarks>
/// <param name="formatString">The format string.</param>
<#=TxDOCS#>
/// <#=ARGxDOCS#>
/// <returns>A new string produced by interpolating the format string.</returns>
<#=BCOMPAT#>
public static <#=RETURNTYPE#> Format<#=GENERICSPARAM#>(<#=RETURNTYPE#> formatString, <#=WithCommas(ARGS)#>)
<#=GENERICSCONSTRAINT#>
{
<#=RETURNTYPE#> result = default;
<#=CONVERSIONS#>
result.AppendFormat(formatString, <#=WithCommas(ARGNAMES)#>);
return result;
}
<#
}
#>
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 059659011adc4f67a43b9d9bcc5dfd01
timeCreated: 1593668361

View file

@ -0,0 +1,675 @@

//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// TextTransform Samples/Packages/com.unity.collections/Unity.Collections/FixedStringFormatMethods.tt
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Collections
{
/// <summary>
/// Provides extension methods for FixedString*N*Bytes.
/// </summary>
public unsafe static partial class FixedStringMethods
{
/// <summary>
/// Interpolates strings into a format string and appends the result to this string.
/// </summary>
/// <remarks>
/// Similar to `StringBuilder.AppendFormat` but with significant limitations:
/// - Only supports FixedString*N*Bytes arguments. To use other string types, convert them to FixedString*N*Bytes first.
/// - Only supports numeric format placeholders of the form `{0}` .. `{N}`.
/// - No format modifiers (*e.g.* `{0:x}`) are supported.
///
/// The overloads of this method take up to ten strings to interpolate into the format string.
/// </remarks>
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="U">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="T0">The type of value to interpolate into the format string.</typeparam>
/// <param name="dest">The string to append to.</param>d
/// <param name="format">A string to be interpolated and appended.</param>
/// <param name="arg0">A FixedString*N*Bytes to interpolate into the format string.</param>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes /*T*/), typeof(FixedString128Bytes /*U*/), typeof(FixedString128Bytes /*T0*/) })]
public static unsafe void AppendFormat<T, U, T0>(ref this T dest, in U format, in T0 arg0)
where T : struct, INativeList<byte>, IUTF8Bytes
where U : struct, INativeList<byte>, IUTF8Bytes
where T0 : struct, INativeList<byte>, IUTF8Bytes
{
ref var formatRef = ref UnsafeUtilityExtensions.AsRef(in format);
int formatLength = formatRef.Length;
byte* formatBytes = formatRef.GetUnsafePtr();
for (var i = 0; i < formatLength; ++i)
{
if (formatBytes[i] == (byte)'{')
{
if (formatLength - i >= 3 && formatBytes[i + 1] != (byte)'{')
{
var index = formatBytes[i + 1] - (byte)'0';
switch (index)
{
case 0: dest.Append(in arg0); i+=2; break;
default:
dest.AppendRawByte(formatBytes[i]);
break;
}
}
}
else
dest.AppendRawByte(formatBytes[i]);
}
}
/// <summary>
/// Interpolates strings into a format string and appends the result to this string.
/// </summary>
/// <remarks>
/// Similar to `StringBuilder.AppendFormat` but with significant limitations:
/// - Only supports FixedString*N*Bytes arguments. To use other string types, convert them to FixedString*N*Bytes first.
/// - Only supports numeric format placeholders of the form `{0}` .. `{N}`.
/// - No format modifiers (*e.g.* `{0:x}`) are supported.
///
/// The overloads of this method take up to ten strings to interpolate into the format string.
/// </remarks>
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="U">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="T0">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T1">The type of value to interpolate into the format string.</typeparam>
/// <param name="dest">The string to append to.</param>d
/// <param name="format">A string to be interpolated and appended.</param>
/// <param name="arg0">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg1">A FixedString*N*Bytes to interpolate into the format string.</param>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes /*T*/), typeof(FixedString128Bytes /*U*/), typeof(FixedString128Bytes /*T0*/), typeof(FixedString128Bytes /*T1*/) })]
public static unsafe void AppendFormat<T, U, T0, T1>(ref this T dest, in U format, in T0 arg0, in T1 arg1)
where T : struct, INativeList<byte>, IUTF8Bytes
where U : struct, INativeList<byte>, IUTF8Bytes
where T0 : struct, INativeList<byte>, IUTF8Bytes
where T1 : struct, INativeList<byte>, IUTF8Bytes
{
ref var formatRef = ref UnsafeUtilityExtensions.AsRef(in format);
int formatLength = formatRef.Length;
byte* formatBytes = formatRef.GetUnsafePtr();
for (var i = 0; i < formatLength; ++i)
{
if (formatBytes[i] == (byte)'{')
{
if (formatLength - i >= 3 && formatBytes[i + 1] != (byte)'{')
{
var index = formatBytes[i + 1] - (byte)'0';
switch (index)
{
case 0: dest.Append(in arg0); i+=2; break;
case 1: dest.Append(in arg1); i+=2; break;
default:
dest.AppendRawByte(formatBytes[i]);
break;
}
}
}
else
dest.AppendRawByte(formatBytes[i]);
}
}
/// <summary>
/// Interpolates strings into a format string and appends the result to this string.
/// </summary>
/// <remarks>
/// Similar to `StringBuilder.AppendFormat` but with significant limitations:
/// - Only supports FixedString*N*Bytes arguments. To use other string types, convert them to FixedString*N*Bytes first.
/// - Only supports numeric format placeholders of the form `{0}` .. `{N}`.
/// - No format modifiers (*e.g.* `{0:x}`) are supported.
///
/// The overloads of this method take up to ten strings to interpolate into the format string.
/// </remarks>
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="U">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="T0">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T1">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T2">The type of value to interpolate into the format string.</typeparam>
/// <param name="dest">The string to append to.</param>d
/// <param name="format">A string to be interpolated and appended.</param>
/// <param name="arg0">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg1">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg2">A FixedString*N*Bytes to interpolate into the format string.</param>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes /*T*/), typeof(FixedString128Bytes /*U*/), typeof(FixedString128Bytes /*T0*/), typeof(FixedString128Bytes /*T1*/), typeof(FixedString128Bytes /*T2*/) })]
public static unsafe void AppendFormat<T, U, T0, T1, T2>(ref this T dest, in U format, in T0 arg0, in T1 arg1, in T2 arg2)
where T : struct, INativeList<byte>, IUTF8Bytes
where U : struct, INativeList<byte>, IUTF8Bytes
where T0 : struct, INativeList<byte>, IUTF8Bytes
where T1 : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
{
ref var formatRef = ref UnsafeUtilityExtensions.AsRef(in format);
int formatLength = formatRef.Length;
byte* formatBytes = formatRef.GetUnsafePtr();
for (var i = 0; i < formatLength; ++i)
{
if (formatBytes[i] == (byte)'{')
{
if (formatLength - i >= 3 && formatBytes[i + 1] != (byte)'{')
{
var index = formatBytes[i + 1] - (byte)'0';
switch (index)
{
case 0: dest.Append(in arg0); i+=2; break;
case 1: dest.Append(in arg1); i+=2; break;
case 2: dest.Append(in arg2); i+=2; break;
default:
dest.AppendRawByte(formatBytes[i]);
break;
}
}
}
else
dest.AppendRawByte(formatBytes[i]);
}
}
/// <summary>
/// Interpolates strings into a format string and appends the result to this string.
/// </summary>
/// <remarks>
/// Similar to `StringBuilder.AppendFormat` but with significant limitations:
/// - Only supports FixedString*N*Bytes arguments. To use other string types, convert them to FixedString*N*Bytes first.
/// - Only supports numeric format placeholders of the form `{0}` .. `{N}`.
/// - No format modifiers (*e.g.* `{0:x}`) are supported.
///
/// The overloads of this method take up to ten strings to interpolate into the format string.
/// </remarks>
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="U">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="T0">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T1">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T2">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T3">The type of value to interpolate into the format string.</typeparam>
/// <param name="dest">The string to append to.</param>d
/// <param name="format">A string to be interpolated and appended.</param>
/// <param name="arg0">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg1">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg2">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg3">A FixedString*N*Bytes to interpolate into the format string.</param>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes /*T*/), typeof(FixedString128Bytes /*U*/), typeof(FixedString128Bytes /*T0*/), typeof(FixedString128Bytes /*T1*/), typeof(FixedString128Bytes /*T2*/), typeof(FixedString128Bytes /*T3*/) })]
public static unsafe void AppendFormat<T, U, T0, T1, T2, T3>(ref this T dest, in U format, in T0 arg0, in T1 arg1, in T2 arg2, in T3 arg3)
where T : struct, INativeList<byte>, IUTF8Bytes
where U : struct, INativeList<byte>, IUTF8Bytes
where T0 : struct, INativeList<byte>, IUTF8Bytes
where T1 : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
where T3 : struct, INativeList<byte>, IUTF8Bytes
{
ref var formatRef = ref UnsafeUtilityExtensions.AsRef(in format);
int formatLength = formatRef.Length;
byte* formatBytes = formatRef.GetUnsafePtr();
for (var i = 0; i < formatLength; ++i)
{
if (formatBytes[i] == (byte)'{')
{
if (formatLength - i >= 3 && formatBytes[i + 1] != (byte)'{')
{
var index = formatBytes[i + 1] - (byte)'0';
switch (index)
{
case 0: dest.Append(in arg0); i+=2; break;
case 1: dest.Append(in arg1); i+=2; break;
case 2: dest.Append(in arg2); i+=2; break;
case 3: dest.Append(in arg3); i+=2; break;
default:
dest.AppendRawByte(formatBytes[i]);
break;
}
}
}
else
dest.AppendRawByte(formatBytes[i]);
}
}
/// <summary>
/// Interpolates strings into a format string and appends the result to this string.
/// </summary>
/// <remarks>
/// Similar to `StringBuilder.AppendFormat` but with significant limitations:
/// - Only supports FixedString*N*Bytes arguments. To use other string types, convert them to FixedString*N*Bytes first.
/// - Only supports numeric format placeholders of the form `{0}` .. `{N}`.
/// - No format modifiers (*e.g.* `{0:x}`) are supported.
///
/// The overloads of this method take up to ten strings to interpolate into the format string.
/// </remarks>
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="U">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="T0">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T1">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T2">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T3">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T4">The type of value to interpolate into the format string.</typeparam>
/// <param name="dest">The string to append to.</param>d
/// <param name="format">A string to be interpolated and appended.</param>
/// <param name="arg0">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg1">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg2">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg3">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg4">A FixedString*N*Bytes to interpolate into the format string.</param>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes /*T*/), typeof(FixedString128Bytes /*U*/), typeof(FixedString128Bytes /*T0*/), typeof(FixedString128Bytes /*T1*/), typeof(FixedString128Bytes /*T2*/), typeof(FixedString128Bytes /*T3*/), typeof(FixedString128Bytes /*T4*/) })]
public static unsafe void AppendFormat<T, U, T0, T1, T2, T3, T4>(ref this T dest, in U format, in T0 arg0, in T1 arg1, in T2 arg2, in T3 arg3, in T4 arg4)
where T : struct, INativeList<byte>, IUTF8Bytes
where U : struct, INativeList<byte>, IUTF8Bytes
where T0 : struct, INativeList<byte>, IUTF8Bytes
where T1 : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
where T3 : struct, INativeList<byte>, IUTF8Bytes
where T4 : struct, INativeList<byte>, IUTF8Bytes
{
ref var formatRef = ref UnsafeUtilityExtensions.AsRef(in format);
int formatLength = formatRef.Length;
byte* formatBytes = formatRef.GetUnsafePtr();
for (var i = 0; i < formatLength; ++i)
{
if (formatBytes[i] == (byte)'{')
{
if (formatLength - i >= 3 && formatBytes[i + 1] != (byte)'{')
{
var index = formatBytes[i + 1] - (byte)'0';
switch (index)
{
case 0: dest.Append(in arg0); i+=2; break;
case 1: dest.Append(in arg1); i+=2; break;
case 2: dest.Append(in arg2); i+=2; break;
case 3: dest.Append(in arg3); i+=2; break;
case 4: dest.Append(in arg4); i+=2; break;
default:
dest.AppendRawByte(formatBytes[i]);
break;
}
}
}
else
dest.AppendRawByte(formatBytes[i]);
}
}
/// <summary>
/// Interpolates strings into a format string and appends the result to this string.
/// </summary>
/// <remarks>
/// Similar to `StringBuilder.AppendFormat` but with significant limitations:
/// - Only supports FixedString*N*Bytes arguments. To use other string types, convert them to FixedString*N*Bytes first.
/// - Only supports numeric format placeholders of the form `{0}` .. `{N}`.
/// - No format modifiers (*e.g.* `{0:x}`) are supported.
///
/// The overloads of this method take up to ten strings to interpolate into the format string.
/// </remarks>
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="U">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="T0">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T1">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T2">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T3">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T4">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T5">The type of value to interpolate into the format string.</typeparam>
/// <param name="dest">The string to append to.</param>d
/// <param name="format">A string to be interpolated and appended.</param>
/// <param name="arg0">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg1">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg2">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg3">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg4">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg5">A FixedString*N*Bytes to interpolate into the format string.</param>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes /*T*/), typeof(FixedString128Bytes /*U*/), typeof(FixedString128Bytes /*T0*/), typeof(FixedString128Bytes /*T1*/), typeof(FixedString128Bytes /*T2*/), typeof(FixedString128Bytes /*T3*/), typeof(FixedString128Bytes /*T4*/), typeof(FixedString128Bytes /*T5*/) })]
public static unsafe void AppendFormat<T, U, T0, T1, T2, T3, T4, T5>(ref this T dest, in U format, in T0 arg0, in T1 arg1, in T2 arg2, in T3 arg3, in T4 arg4, in T5 arg5)
where T : struct, INativeList<byte>, IUTF8Bytes
where U : struct, INativeList<byte>, IUTF8Bytes
where T0 : struct, INativeList<byte>, IUTF8Bytes
where T1 : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
where T3 : struct, INativeList<byte>, IUTF8Bytes
where T4 : struct, INativeList<byte>, IUTF8Bytes
where T5 : struct, INativeList<byte>, IUTF8Bytes
{
ref var formatRef = ref UnsafeUtilityExtensions.AsRef(in format);
int formatLength = formatRef.Length;
byte* formatBytes = formatRef.GetUnsafePtr();
for (var i = 0; i < formatLength; ++i)
{
if (formatBytes[i] == (byte)'{')
{
if (formatLength - i >= 3 && formatBytes[i + 1] != (byte)'{')
{
var index = formatBytes[i + 1] - (byte)'0';
switch (index)
{
case 0: dest.Append(in arg0); i+=2; break;
case 1: dest.Append(in arg1); i+=2; break;
case 2: dest.Append(in arg2); i+=2; break;
case 3: dest.Append(in arg3); i+=2; break;
case 4: dest.Append(in arg4); i+=2; break;
case 5: dest.Append(in arg5); i+=2; break;
default:
dest.AppendRawByte(formatBytes[i]);
break;
}
}
}
else
dest.AppendRawByte(formatBytes[i]);
}
}
/// <summary>
/// Interpolates strings into a format string and appends the result to this string.
/// </summary>
/// <remarks>
/// Similar to `StringBuilder.AppendFormat` but with significant limitations:
/// - Only supports FixedString*N*Bytes arguments. To use other string types, convert them to FixedString*N*Bytes first.
/// - Only supports numeric format placeholders of the form `{0}` .. `{N}`.
/// - No format modifiers (*e.g.* `{0:x}`) are supported.
///
/// The overloads of this method take up to ten strings to interpolate into the format string.
/// </remarks>
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="U">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="T0">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T1">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T2">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T3">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T4">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T5">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T6">The type of value to interpolate into the format string.</typeparam>
/// <param name="dest">The string to append to.</param>d
/// <param name="format">A string to be interpolated and appended.</param>
/// <param name="arg0">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg1">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg2">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg3">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg4">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg5">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg6">A FixedString*N*Bytes to interpolate into the format string.</param>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes /*T*/), typeof(FixedString128Bytes /*U*/), typeof(FixedString128Bytes /*T0*/), typeof(FixedString128Bytes /*T1*/), typeof(FixedString128Bytes /*T2*/), typeof(FixedString128Bytes /*T3*/), typeof(FixedString128Bytes /*T4*/), typeof(FixedString128Bytes /*T5*/), typeof(FixedString128Bytes /*T6*/) })]
public static unsafe void AppendFormat<T, U, T0, T1, T2, T3, T4, T5, T6>(ref this T dest, in U format, in T0 arg0, in T1 arg1, in T2 arg2, in T3 arg3, in T4 arg4, in T5 arg5, in T6 arg6)
where T : struct, INativeList<byte>, IUTF8Bytes
where U : struct, INativeList<byte>, IUTF8Bytes
where T0 : struct, INativeList<byte>, IUTF8Bytes
where T1 : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
where T3 : struct, INativeList<byte>, IUTF8Bytes
where T4 : struct, INativeList<byte>, IUTF8Bytes
where T5 : struct, INativeList<byte>, IUTF8Bytes
where T6 : struct, INativeList<byte>, IUTF8Bytes
{
ref var formatRef = ref UnsafeUtilityExtensions.AsRef(in format);
int formatLength = formatRef.Length;
byte* formatBytes = formatRef.GetUnsafePtr();
for (var i = 0; i < formatLength; ++i)
{
if (formatBytes[i] == (byte)'{')
{
if (formatLength - i >= 3 && formatBytes[i + 1] != (byte)'{')
{
var index = formatBytes[i + 1] - (byte)'0';
switch (index)
{
case 0: dest.Append(in arg0); i+=2; break;
case 1: dest.Append(in arg1); i+=2; break;
case 2: dest.Append(in arg2); i+=2; break;
case 3: dest.Append(in arg3); i+=2; break;
case 4: dest.Append(in arg4); i+=2; break;
case 5: dest.Append(in arg5); i+=2; break;
case 6: dest.Append(in arg6); i+=2; break;
default:
dest.AppendRawByte(formatBytes[i]);
break;
}
}
}
else
dest.AppendRawByte(formatBytes[i]);
}
}
/// <summary>
/// Interpolates strings into a format string and appends the result to this string.
/// </summary>
/// <remarks>
/// Similar to `StringBuilder.AppendFormat` but with significant limitations:
/// - Only supports FixedString*N*Bytes arguments. To use other string types, convert them to FixedString*N*Bytes first.
/// - Only supports numeric format placeholders of the form `{0}` .. `{N}`.
/// - No format modifiers (*e.g.* `{0:x}`) are supported.
///
/// The overloads of this method take up to ten strings to interpolate into the format string.
/// </remarks>
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="U">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="T0">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T1">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T2">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T3">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T4">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T5">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T6">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T7">The type of value to interpolate into the format string.</typeparam>
/// <param name="dest">The string to append to.</param>d
/// <param name="format">A string to be interpolated and appended.</param>
/// <param name="arg0">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg1">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg2">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg3">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg4">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg5">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg6">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg7">A FixedString*N*Bytes to interpolate into the format string.</param>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes /*T*/), typeof(FixedString128Bytes /*U*/), typeof(FixedString128Bytes /*T0*/), typeof(FixedString128Bytes /*T1*/), typeof(FixedString128Bytes /*T2*/), typeof(FixedString128Bytes /*T3*/), typeof(FixedString128Bytes /*T4*/), typeof(FixedString128Bytes /*T5*/), typeof(FixedString128Bytes /*T6*/), typeof(FixedString128Bytes /*T7*/) })]
public static unsafe void AppendFormat<T, U, T0, T1, T2, T3, T4, T5, T6, T7>(ref this T dest, in U format, in T0 arg0, in T1 arg1, in T2 arg2, in T3 arg3, in T4 arg4, in T5 arg5, in T6 arg6, in T7 arg7)
where T : struct, INativeList<byte>, IUTF8Bytes
where U : struct, INativeList<byte>, IUTF8Bytes
where T0 : struct, INativeList<byte>, IUTF8Bytes
where T1 : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
where T3 : struct, INativeList<byte>, IUTF8Bytes
where T4 : struct, INativeList<byte>, IUTF8Bytes
where T5 : struct, INativeList<byte>, IUTF8Bytes
where T6 : struct, INativeList<byte>, IUTF8Bytes
where T7 : struct, INativeList<byte>, IUTF8Bytes
{
ref var formatRef = ref UnsafeUtilityExtensions.AsRef(in format);
int formatLength = formatRef.Length;
byte* formatBytes = formatRef.GetUnsafePtr();
for (var i = 0; i < formatLength; ++i)
{
if (formatBytes[i] == (byte)'{')
{
if (formatLength - i >= 3 && formatBytes[i + 1] != (byte)'{')
{
var index = formatBytes[i + 1] - (byte)'0';
switch (index)
{
case 0: dest.Append(in arg0); i+=2; break;
case 1: dest.Append(in arg1); i+=2; break;
case 2: dest.Append(in arg2); i+=2; break;
case 3: dest.Append(in arg3); i+=2; break;
case 4: dest.Append(in arg4); i+=2; break;
case 5: dest.Append(in arg5); i+=2; break;
case 6: dest.Append(in arg6); i+=2; break;
case 7: dest.Append(in arg7); i+=2; break;
default:
dest.AppendRawByte(formatBytes[i]);
break;
}
}
}
else
dest.AppendRawByte(formatBytes[i]);
}
}
/// <summary>
/// Interpolates strings into a format string and appends the result to this string.
/// </summary>
/// <remarks>
/// Similar to `StringBuilder.AppendFormat` but with significant limitations:
/// - Only supports FixedString*N*Bytes arguments. To use other string types, convert them to FixedString*N*Bytes first.
/// - Only supports numeric format placeholders of the form `{0}` .. `{N}`.
/// - No format modifiers (*e.g.* `{0:x}`) are supported.
///
/// The overloads of this method take up to ten strings to interpolate into the format string.
/// </remarks>
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="U">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="T0">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T1">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T2">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T3">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T4">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T5">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T6">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T7">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T8">The type of value to interpolate into the format string.</typeparam>
/// <param name="dest">The string to append to.</param>d
/// <param name="format">A string to be interpolated and appended.</param>
/// <param name="arg0">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg1">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg2">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg3">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg4">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg5">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg6">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg7">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg8">A FixedString*N*Bytes to interpolate into the format string.</param>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes /*T*/), typeof(FixedString128Bytes /*U*/), typeof(FixedString128Bytes /*T0*/), typeof(FixedString128Bytes /*T1*/), typeof(FixedString128Bytes /*T2*/), typeof(FixedString128Bytes /*T3*/), typeof(FixedString128Bytes /*T4*/), typeof(FixedString128Bytes /*T5*/), typeof(FixedString128Bytes /*T6*/), typeof(FixedString128Bytes /*T7*/), typeof(FixedString128Bytes /*T8*/) })]
public static unsafe void AppendFormat<T, U, T0, T1, T2, T3, T4, T5, T6, T7, T8>(ref this T dest, in U format, in T0 arg0, in T1 arg1, in T2 arg2, in T3 arg3, in T4 arg4, in T5 arg5, in T6 arg6, in T7 arg7, in T8 arg8)
where T : struct, INativeList<byte>, IUTF8Bytes
where U : struct, INativeList<byte>, IUTF8Bytes
where T0 : struct, INativeList<byte>, IUTF8Bytes
where T1 : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
where T3 : struct, INativeList<byte>, IUTF8Bytes
where T4 : struct, INativeList<byte>, IUTF8Bytes
where T5 : struct, INativeList<byte>, IUTF8Bytes
where T6 : struct, INativeList<byte>, IUTF8Bytes
where T7 : struct, INativeList<byte>, IUTF8Bytes
where T8 : struct, INativeList<byte>, IUTF8Bytes
{
ref var formatRef = ref UnsafeUtilityExtensions.AsRef(in format);
int formatLength = formatRef.Length;
byte* formatBytes = formatRef.GetUnsafePtr();
for (var i = 0; i < formatLength; ++i)
{
if (formatBytes[i] == (byte)'{')
{
if (formatLength - i >= 3 && formatBytes[i + 1] != (byte)'{')
{
var index = formatBytes[i + 1] - (byte)'0';
switch (index)
{
case 0: dest.Append(in arg0); i+=2; break;
case 1: dest.Append(in arg1); i+=2; break;
case 2: dest.Append(in arg2); i+=2; break;
case 3: dest.Append(in arg3); i+=2; break;
case 4: dest.Append(in arg4); i+=2; break;
case 5: dest.Append(in arg5); i+=2; break;
case 6: dest.Append(in arg6); i+=2; break;
case 7: dest.Append(in arg7); i+=2; break;
case 8: dest.Append(in arg8); i+=2; break;
default:
dest.AppendRawByte(formatBytes[i]);
break;
}
}
}
else
dest.AppendRawByte(formatBytes[i]);
}
}
/// <summary>
/// Interpolates strings into a format string and appends the result to this string.
/// </summary>
/// <remarks>
/// Similar to `StringBuilder.AppendFormat` but with significant limitations:
/// - Only supports FixedString*N*Bytes arguments. To use other string types, convert them to FixedString*N*Bytes first.
/// - Only supports numeric format placeholders of the form `{0}` .. `{N}`.
/// - No format modifiers (*e.g.* `{0:x}`) are supported.
///
/// The overloads of this method take up to ten strings to interpolate into the format string.
/// </remarks>
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="U">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="T0">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T1">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T2">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T3">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T4">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T5">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T6">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T7">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T8">The type of value to interpolate into the format string.</typeparam>
/// <typeparam name="T9">The type of value to interpolate into the format string.</typeparam>
/// <param name="dest">The string to append to.</param>d
/// <param name="format">A string to be interpolated and appended.</param>
/// <param name="arg0">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg1">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg2">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg3">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg4">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg5">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg6">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg7">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg8">A FixedString*N*Bytes to interpolate into the format string.</param>
/// <param name="arg9">A FixedString*N*Bytes to interpolate into the format string.</param>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes /*T*/), typeof(FixedString128Bytes /*U*/), typeof(FixedString128Bytes /*T0*/), typeof(FixedString128Bytes /*T1*/), typeof(FixedString128Bytes /*T2*/), typeof(FixedString128Bytes /*T3*/), typeof(FixedString128Bytes /*T4*/), typeof(FixedString128Bytes /*T5*/), typeof(FixedString128Bytes /*T6*/), typeof(FixedString128Bytes /*T7*/), typeof(FixedString128Bytes /*T8*/), typeof(FixedString128Bytes /*T9*/) })]
public static unsafe void AppendFormat<T, U, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>(ref this T dest, in U format, in T0 arg0, in T1 arg1, in T2 arg2, in T3 arg3, in T4 arg4, in T5 arg5, in T6 arg6, in T7 arg7, in T8 arg8, in T9 arg9)
where T : struct, INativeList<byte>, IUTF8Bytes
where U : struct, INativeList<byte>, IUTF8Bytes
where T0 : struct, INativeList<byte>, IUTF8Bytes
where T1 : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
where T3 : struct, INativeList<byte>, IUTF8Bytes
where T4 : struct, INativeList<byte>, IUTF8Bytes
where T5 : struct, INativeList<byte>, IUTF8Bytes
where T6 : struct, INativeList<byte>, IUTF8Bytes
where T7 : struct, INativeList<byte>, IUTF8Bytes
where T8 : struct, INativeList<byte>, IUTF8Bytes
where T9 : struct, INativeList<byte>, IUTF8Bytes
{
ref var formatRef = ref UnsafeUtilityExtensions.AsRef(in format);
int formatLength = formatRef.Length;
byte* formatBytes = formatRef.GetUnsafePtr();
for (var i = 0; i < formatLength; ++i)
{
if (formatBytes[i] == (byte)'{')
{
if (formatLength - i >= 3 && formatBytes[i + 1] != (byte)'{')
{
var index = formatBytes[i + 1] - (byte)'0';
switch (index)
{
case 0: dest.Append(in arg0); i+=2; break;
case 1: dest.Append(in arg1); i+=2; break;
case 2: dest.Append(in arg2); i+=2; break;
case 3: dest.Append(in arg3); i+=2; break;
case 4: dest.Append(in arg4); i+=2; break;
case 5: dest.Append(in arg5); i+=2; break;
case 6: dest.Append(in arg6); i+=2; break;
case 7: dest.Append(in arg7); i+=2; break;
case 8: dest.Append(in arg8); i+=2; break;
case 9: dest.Append(in arg9); i+=2; break;
default:
dest.AppendRawByte(formatBytes[i]);
break;
}
}
}
else
dest.AppendRawByte(formatBytes[i]);
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 89f65fb005504fceb04727480e65f16c
timeCreated: 1591061827

View file

@ -0,0 +1,98 @@
<#/*THIS IS A T4 FILE - see t4_text_templating.md for what it is and how to run codegen*/#>
<#@ template debug="True" #>
<#@ output extension=".gen.cs" encoding="utf-8" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// TextTransform Samples/Packages/com.unity.collections/Unity.Collections/FixedStringFormatMethods.tt
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Collections
{
/// <summary>
/// Provides extension methods for FixedString*N*Bytes.
/// </summary>
public unsafe static partial class FixedStringMethods
{
<#
for (var ARGS = 1; ARGS <= 10; ++ARGS)
{
var TYPES = String.Join(", ", Enumerable.Range(0, ARGS).Select(n => $"T{n}"));
var PARAMS = String.Join(", ", Enumerable.Range(0, ARGS).Select(n => $"in T{n} arg{n}"));
var ARGNAMES = String.Join(", ", Enumerable.Range(0, ARGS).Select(n => $"arg{n}"));
var TxDOCS = String.Join("\r\n /// ", Enumerable.Range(0, ARGS).Select(n => $"<typeparam name=\"T{n}\">The type of value to interpolate into the format string.</typeparam>"));
var ARGxDOCS = String.Join("\r\n /// ", Enumerable.Range(0, ARGS).Select(n => $"<param name=\"arg{n}\">A FixedString*N*Bytes to interpolate into the format string.</param>"));
var BCOMPAT = String.Join(", ", Enumerable.Range(0, ARGS).Select(n => $"typeof(FixedString128Bytes /*T{n}*/)"));
#>
/// <summary>
/// Interpolates strings into a format string and appends the result to this string.
/// </summary>
/// <remarks>
/// Similar to `StringBuilder.AppendFormat` but with significant limitations:
/// - Only supports FixedString*N*Bytes arguments. To use other string types, convert them to FixedString*N*Bytes first.
/// - Only supports numeric format placeholders of the form `{0}` .. `{N}`.
/// - No format modifiers (*e.g.* `{0:x}`) are supported.
///
/// The overloads of this method take up to ten strings to interpolate into the format string.
/// </remarks>
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
/// <typeparam name="U">A FixedString*N*Bytes type.</typeparam>
/// <#=TxDOCS#>
/// <param name="dest">The string to append to.</param>d
/// <param name="format">A string to be interpolated and appended.</param>
/// <#=ARGxDOCS#>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes /*T*/), typeof(FixedString128Bytes /*U*/), <#=BCOMPAT#> })]
public static unsafe void AppendFormat<T, U, <#=TYPES#>>(ref this T dest, in U format, <#=PARAMS#>)
where T : struct, INativeList<byte>, IUTF8Bytes
where U : struct, INativeList<byte>, IUTF8Bytes
<#
for (var a = 0; a < ARGS; ++a)
WriteLine(" where T{0} : struct, INativeList<byte>, IUTF8Bytes", a);
#>
{
ref var formatRef = ref UnsafeUtilityExtensions.AsRef(in format);
int formatLength = formatRef.Length;
byte* formatBytes = formatRef.GetUnsafePtr();
for (var i = 0; i < formatLength; ++i)
{
if (formatBytes[i] == (byte)'{')
{
if (formatLength - i >= 3 && formatBytes[i + 1] != (byte)'{')
{
var index = formatBytes[i + 1] - (byte)'0';
switch (index)
{
<#
for(var a = 0; a < ARGS; ++a)
{
WriteLine($" case {a}: dest.Append(in arg{a}); i+=2; break;");
}
#>
default:
dest.AppendRawByte(formatBytes[i]);
break;
}
}
}
else
dest.AppendRawByte(formatBytes[i]);
}
}
<#
}
#>
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0cdef7b1946241588e9a3b52426940e4
timeCreated: 1584994860

View file

@ -0,0 +1,158 @@
using System;
namespace Unity.Collections
{
[BurstCompatible]
public unsafe static partial class FixedStringMethods
{
/// <summary>
/// Append two characters to this IUTF8Bytes. This is used as a helper for internal formatting.
/// </summary>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
internal static FormatError Append<T>(ref this T fs, char a, char b)
where T : struct, INativeList<byte>, IUTF8Bytes
{
FormatError err = FormatError.None;
err |= fs.Append((Unicode.Rune) a);
err |= fs.Append((Unicode.Rune) b);
if (err != FormatError.None)
return FormatError.Overflow;
return FormatError.None;
}
/// <summary>
/// Append three characters to this IUTF8Bytes. This is used as a helper for internal formatting.
/// </summary>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
internal static FormatError Append<T>(ref this T fs, char a, char b, char c)
where T : struct, INativeList<byte>, IUTF8Bytes
{
FormatError err = FormatError.None;
err |= fs.Append((Unicode.Rune) a);
err |= fs.Append((Unicode.Rune) b);
err |= fs.Append((Unicode.Rune) c);
if (err != FormatError.None)
return FormatError.Overflow;
return FormatError.None;
}
/// <summary>
/// Append 'I' 'n' 'f' 'i' 'n' 'i' 't' 'y' characters to this IUTF8Bytes. This is used as a helper for internal formatting.
/// </summary>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
internal static FormatError Append<T>(ref this T fs, char a, char b, char c, char d, char e, char f, char g, char h)
where T : struct, INativeList<byte>, IUTF8Bytes
{
FormatError err = FormatError.None;
err |= fs.Append((Unicode.Rune) a);
err |= fs.Append((Unicode.Rune) b);
err |= fs.Append((Unicode.Rune) c);
err |= fs.Append((Unicode.Rune) d);
err |= fs.Append((Unicode.Rune) e);
err |= fs.Append((Unicode.Rune) f);
err |= fs.Append((Unicode.Rune) g);
err |= fs.Append((Unicode.Rune) h);
if (err != FormatError.None)
return FormatError.Overflow;
return FormatError.None;
}
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
internal static FormatError AppendScientific<T>(ref this T fs, char *source, int sourceLength, int decimalExponent, char decimalSeparator = '.')
where T : struct, INativeList<byte>, IUTF8Bytes
{
FormatError error;
if ((error = fs.Append(source[0])) != FormatError.None)
return error;
if (sourceLength > 1)
{
if ((error = fs.Append(decimalSeparator)) != FormatError.None)
return error;
for (var i = 1; i < sourceLength; ++i)
{
if ((error = fs.Append(source[i])) != FormatError.None)
return error;
}
}
if ((error = fs.Append('E')) != FormatError.None)
return error;
if (decimalExponent < 0)
{
if ((error = fs.Append('-')) != FormatError.None)
return error;
decimalExponent *= -1;
decimalExponent -= sourceLength - 1;
}
else
{
if ((error = fs.Append('+')) != FormatError.None)
return error;
decimalExponent += sourceLength - 1;
}
var ascii = stackalloc char[2];
const int decimalDigits = 2;
for (var i = 0; i < decimalDigits; ++i)
{
var decimalDigit = decimalExponent % 10;
ascii[1 - i] = (char)('0' + decimalDigit);
decimalExponent /= 10;
}
for (var i = 0; i < decimalDigits; ++i)
if ((error = fs.Append(ascii[i])) != FormatError.None)
return error;
return FormatError.None;
}
/// <summary>
/// Check if runes a, b, c are found at offset offset
/// </summary>
/// <param name="offset">The target offset</param>
/// <param name="a">rune a</param>
/// <param name="b">rune b</param>
/// <param name="c">rune c</param>
/// <returns></returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
internal static bool Found<T>(ref this T fs, ref int offset, char a, char b, char c)
where T : struct, INativeList<byte>, IUTF8Bytes
{
int old = offset;
if ((fs.Read(ref offset).value | 32) == a
&& (fs.Read(ref offset).value | 32) == b
&& (fs.Read(ref offset).value | 32) == c)
return true;
offset = old;
return false;
}
/// <summary>
///
/// </summary>
/// <param name="offset"></param>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="c"></param>
/// <param name="d"></param>
/// <param name="e"></param>
/// <param name="f"></param>
/// <param name="g"></param>
/// <param name="h"></param>
/// <returns></returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
internal static bool Found<T>(ref this T fs, ref int offset, char a, char b, char c, char d, char e, char f, char g, char h)
where T : struct, INativeList<byte>, IUTF8Bytes
{
int old = offset;
if ((fs.Read(ref offset).value | 32) == a
&& (fs.Read(ref offset).value | 32) == b
&& (fs.Read(ref offset).value | 32) == c
&& (fs.Read(ref offset).value | 32) == d
&& (fs.Read(ref offset).value | 32) == e
&& (fs.Read(ref offset).value | 32) == f
&& (fs.Read(ref offset).value | 32) == g
&& (fs.Read(ref offset).value | 32) == h)
return true;
offset = old;
return false;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d1db1dc6fae245e9af37d2ab33aac17a
timeCreated: 1593671154

View file

@ -0,0 +1,403 @@
using System;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Collections
{
/// <summary>
/// Provides extension methods for FixedString*N*.
/// </summary>
[BurstCompatible]
public unsafe static partial class FixedStringMethods
{
/// <summary>
/// Returns the index of the first occurrence of a byte sequence in this string.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to search.</param>
/// <param name="bytes">A byte sequence to search for within this string.</param>
/// <param name="bytesLen">The number of bytes in the byte sequence.</param>
/// <returns>The index of the first occurrence of the byte sequence in this string. Returns -1 if no occurrence is found.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static int IndexOf<T>(ref this T fs, byte* bytes, int bytesLen)
where T : struct, INativeList<byte>, IUTF8Bytes
{
var dst = fs.GetUnsafePtr();
var dstLen = fs.Length;
for (var i = 0; i <= dstLen - bytesLen; ++i)
{
for (var j = 0; j < bytesLen; ++j)
if (dst[i + j] != bytes[j])
goto end_of_loop;
return i;
end_of_loop : {}
}
return -1;
}
/// <summary>
/// Returns the index of the first occurrence of a byte sequence within a subrange of this string.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to search.</param>
/// <param name="bytes">A byte sequence to search for within this string.</param>
/// <param name="bytesLen">The number of bytes in the byte sequence.</param>
/// <param name="startIndex">The first index in this string to consider as the first byte of the byte sequence.</param>
/// <param name="distance">The last index in this string to consider as the first byte of the byte sequence.</param>
/// <returns>The index of the first occurrence of the byte sequence in this string. Returns -1 if no occurrence is found.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static int IndexOf<T>(ref this T fs, byte* bytes, int bytesLen, int startIndex, int distance = Int32.MaxValue)
where T : struct, INativeList<byte>, IUTF8Bytes
{
var dst = fs.GetUnsafePtr();
var dstLen = fs.Length;
var searchrange = Math.Min(distance - 1, dstLen - bytesLen);
for (var i = startIndex; i <= searchrange; ++i)
{
for (var j = 0; j < bytesLen; ++j)
if (dst[i + j] != bytes[j])
goto end_of_loop;
return i;
end_of_loop : {}
}
return -1;
}
/// <summary>
/// Returns the index of the first occurrence of a substring within this string.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <typeparam name="T2">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to search.</param>
/// <param name="other">A substring to search for within this string.</param>
/// <returns>The index of the first occurrence of the second string within this string. Returns -1 if no occurrence is found.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes), typeof(FixedString128Bytes) })]
public static int IndexOf<T,T2>(ref this T fs, in T2 other)
where T : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
{
ref var oref = ref UnsafeUtilityExtensions.AsRef(in other);
return fs.IndexOf(oref.GetUnsafePtr(), oref.Length);
}
/// <summary>
/// Returns the index of the first occurrence of a substring within a subrange of this string.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <typeparam name="T2">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to search.</param>
/// <param name="other">A substring to search for within this string.</param>
/// <param name="startIndex">The first index in this string to consider as an occurrence of the second string.</param>
/// <param name="distance">The last index in this string to consider as an occurrence of the second string.</param>
/// <returns>The index of the first occurrence of the substring within this string. Returns -1 if no occurrence is found.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes), typeof(FixedString128Bytes) })]
public static int IndexOf<T,T2>(ref this T fs, in T2 other, int startIndex, int distance = Int32.MaxValue)
where T : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
{
ref var oref = ref UnsafeUtilityExtensions.AsRef(in other);
return fs.IndexOf(oref.GetUnsafePtr(), oref.Length, startIndex, distance);
}
/// <summary>
/// Returns true if a given substring occurs within this string.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <typeparam name="T2">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to search.</param>
/// <param name="other">A substring to search for within this string.</param>
/// <returns>True if the substring occurs within this string.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes), typeof(FixedString128Bytes) })]
public static bool Contains<T,T2>(ref this T fs, in T2 other)
where T : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
{
return fs.IndexOf(in other) != -1;
}
/// <summary>
/// Returns the index of the last occurrence of a byte sequence within this string.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to search.</param>
/// <param name="bytes">A byte sequence to search for within this string.</param>
/// <param name="bytesLen">The number of bytes in the byte sequence.</param>
/// <returns>The index of the last occurrence of the byte sequence within this string. Returns -1 if no occurrence is found.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static int LastIndexOf<T>(ref this T fs, byte* bytes, int bytesLen)
where T : struct, INativeList<byte>, IUTF8Bytes
{
var dst = fs.GetUnsafePtr();
var dstLen = fs.Length;
for (var i = dstLen - bytesLen; i >= 0; --i)
{
for (var j = 0; j < bytesLen; ++j)
if (dst[i + j] != bytes[j])
goto end_of_loop;
return i;
end_of_loop : {}
}
return -1;
}
/// <summary>
/// Returns the index of the last occurrence of a byte sequence within a subrange of this string.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to search.</param>
/// <param name="bytes">A byte sequence to search for within this string.</param>
/// <param name="bytesLen">The number of bytes in the byte sequence.</param>
/// <param name="startIndex">The smallest index in this string to consider as the first byte of the byte sequence.</param>
/// <param name="distance">The greatest index in this string to consider as the first byte of the byte sequence.</param>
/// <returns>The index of the last occurrence of the byte sequence within this string. Returns -1 if no occurrences found.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static int LastIndexOf<T>(ref this T fs, byte* bytes, int bytesLen, int startIndex, int distance = int.MaxValue)
where T : struct, INativeList<byte>, IUTF8Bytes
{
var dst = fs.GetUnsafePtr();
var dstLen = fs.Length;
startIndex = Math.Min(dstLen - bytesLen, startIndex);
var searchrange = Math.Max(0, startIndex - distance);
for (var i = startIndex; i >= searchrange; --i)
{
for (var j = 0; j < bytesLen; ++j)
if (dst[i + j] != bytes[j])
goto end_of_loop;
return i;
end_of_loop : {}
}
return -1;
}
/// <summary>
/// Returns the index of the last occurrence of a substring within this string.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <typeparam name="T2">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to search.</param>
/// <param name="other">A substring to search for in the this string.</param>
/// <returns>The index of the last occurrence of the substring within this string. Returns -1 if no occurrence is found.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes), typeof(FixedString128Bytes) })]
public static int LastIndexOf<T,T2>(ref this T fs, in T2 other)
where T : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
{
ref var oref = ref UnsafeUtilityExtensions.AsRef(in other);
return fs.LastIndexOf(oref.GetUnsafePtr(), oref.Length);
}
/// <summary>
/// Returns the index of the last occurrence of a substring within a subrange of this string.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <typeparam name="T2">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to search.</param>
/// <param name="other">A substring to search for within this string.</param>
/// <param name="startIndex">The greatest index in this string to consider as an occurrence of the substring.</param>
/// <param name="distance">The smallest index in this string to consider as an occurrence of the substring.</param>
/// <returns>the index of the last occurrence of the substring within the first string. Returns -1 if no occurrence is found.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes), typeof(FixedString128Bytes) })]
public static int LastIndexOf<T,T2>(ref this T fs, in T2 other, int startIndex, int distance = Int32.MaxValue)
where T : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
{
ref var oref = ref UnsafeUtilityExtensions.AsRef(in other);
return fs.LastIndexOf(oref.GetUnsafePtr(), oref.Length, startIndex, distance);
}
/// <summary>
/// Returns the sort position of this string relative to a byte sequence.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to compare.</param>
/// <param name="bytes">A byte sequence to compare.</param>
/// <param name="bytesLen">The number of bytes in the byte sequence.</param>
/// <returns>A number denoting the sort position of this string relative to the byte sequence:
///
/// 0 denotes that this string and byte sequence have the same sort position.<br/>
/// -1 denotes that this string should be sorted to precede the byte sequence.<br/>
/// +1 denotes that this string should be sorted to follow the byte sequence.<br/>
/// </returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static int CompareTo<T>(ref this T fs, byte* bytes, int bytesLen)
where T : struct, INativeList<byte>, IUTF8Bytes
{
var a = fs.GetUnsafePtr();
var aa = fs.Length;
int chars = aa < bytesLen ? aa : bytesLen;
for (var i = 0; i < chars; ++i)
{
if (a[i] < bytes[i])
return -1;
if (a[i] > bytes[i])
return 1;
}
if (aa < bytesLen)
return -1;
if (aa > bytesLen)
return 1;
return 0;
}
/// <summary>
/// Returns the sort position of this string relative to another.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <typeparam name="T2">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to compare.</param>
/// <param name="other">Another string to compare.</param>
/// <returns>A number denoting the relative sort position of the strings:
///
/// 0 denotes that the strings have the same sort position.<br/>
/// -1 denotes that this string should be sorted to precede the other.<br/>
/// +1 denotes that this first string should be sorted to follow the other.<br/>
/// </returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes), typeof(FixedString128Bytes) })]
public static int CompareTo<T,T2>(ref this T fs, in T2 other)
where T : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
{
ref var oref = ref UnsafeUtilityExtensions.AsRef(in other);
return fs.CompareTo(oref.GetUnsafePtr(), oref.Length);
}
/// <summary>
/// Returns true if this string and a byte sequence are equal (meaning they have the same length and content).
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to compare for equality.</param>
/// <param name="bytes">A sequence of bytes to compare for equality.</param>
/// <param name="bytesLen">The number of bytes in the byte sequence.</param>
/// <returns>True if this string and the byte sequence have the same length and if this string's character bytes match the byte sequence.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static bool Equals<T>(ref this T fs, byte* bytes, int bytesLen)
where T : struct, INativeList<byte>, IUTF8Bytes
{
var a = fs.GetUnsafePtr();
var aa = fs.Length;
if (aa != bytesLen)
return false;
if (a == bytes)
return true;
return fs.CompareTo(bytes, bytesLen) == 0;
}
/// <summary>
/// Returns true if this string is equal to another.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <typeparam name="T2">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to compare for equality.</param>
/// <param name="other">Another string to compare for equality.</param>
/// <returns>true if the two strings have the same length and matching content.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes), typeof(FixedString128Bytes) })]
public static bool Equals<T,T2>(ref this T fs, in T2 other)
where T : struct, INativeList<byte>, IUTF8Bytes
where T2 : struct, INativeList<byte>, IUTF8Bytes
{
ref var oref = ref UnsafeUtilityExtensions.AsRef(in other);
return fs.Equals(oref.GetUnsafePtr(), oref.Length);
}
/// <summary>
/// Returns the Unicode.Rune at an index of this string.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to read.</param>
/// <param name="index">A reference to an index in bytes (not characters).</param>
/// <returns>The Unicode.Rune (character) which starts at the byte index. Returns Unicode.BadRune
/// if the byte(s) at the index do not form a valid UTF-8 encoded character.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static Unicode.Rune Peek<T>(ref this T fs, int index)
where T : struct, INativeList<byte>, IUTF8Bytes
{
if (index >= fs.Length)
return Unicode.BadRune;
Unicode.Utf8ToUcs(out var rune, fs.GetUnsafePtr(), ref index, fs.Capacity);
return rune;
}
/// <summary>
/// Returns the Unicode.Rune at an index of this string. Increments the index to the position of the next character.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to read.</param>
/// <param name="index">A reference to an index in bytes (not characters). Incremented by 1 to 4 depending upon the UTF-8 encoded size of the character read.</param>
/// <returns>The character (as a `Unicode.Rune`) which starts at the byte index. Returns `Unicode.BadRune`
/// if the byte(s) at the index do not form a valid UTF-8 encoded character.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static Unicode.Rune Read<T>(ref this T fs, ref int index)
where T : struct, INativeList<byte>, IUTF8Bytes
{
if (index >= fs.Length)
return Unicode.BadRune;
Unicode.Utf8ToUcs(out var rune, fs.GetUnsafePtr(), ref index, fs.Capacity);
return rune;
}
/// <summary>
/// Writes a Unicode.Rune at an index of this string. Increments the index to the position of the next character.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to modify.</param>
/// <param name="index">A reference to an index in bytes (not characters). Incremented by 1 to 4 depending upon the UTF-8 encoded size of the character written.</param>
/// <param name="rune">A rune to write to the string, encoded as UTF-8.</param>
/// <returns>FormatError.None if successful. Returns FormatError.Overflow if the index is invalid or if there is not enough space to store the encoded rune.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static FormatError Write<T>(ref this T fs, ref int index, Unicode.Rune rune)
where T : struct, INativeList<byte>, IUTF8Bytes
{
var err = Unicode.UcsToUtf8(fs.GetUnsafePtr(), ref index, fs.Capacity, rune);
if (err != ConversionError.None)
return FormatError.Overflow;
return FormatError.None;
}
/// <summary>
/// Returns a copy of this string as a managed string.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to copy.</param>
/// <returns>A copy of this string as a managed string.</returns>
[NotBurstCompatible]
public static String ConvertToString<T>(ref this T fs)
where T : struct, INativeList<byte>, IUTF8Bytes
{
var c = stackalloc char[fs.Length * 2];
int length = 0;
Unicode.Utf8ToUtf16(fs.GetUnsafePtr(), fs.Length, c, out length, fs.Length * 2);
return new String(c, 0, length);
}
/// <summary>
/// Returns a hash code of this string.
/// </summary>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to get a hash code of.</param>
/// <returns>A hash code of this string.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static int ComputeHashCode<T>(ref this T fs)
where T : struct, INativeList<byte>, IUTF8Bytes
{
return (int)CollectionHelper.Hash(fs.GetUnsafePtr(), fs.Length);
}
/// <summary>
/// Returns the effective size in bytes of this string.
/// </summary>
/// <remarks>
/// "Effective size" is `Length + 3`, the number of bytes you need to copy when serializing the string.
/// (The plus 3 accounts for the null-terminator byte and the 2 bytes that store the Length).
///
/// Useful for checking whether this string will fit in the space of a smaller FixedString*N*.
/// </remarks>
/// <typeparam name="T">A FixedString*N* type.</typeparam>
/// <param name="fs">A string to get the effective size of.</param>
/// <returns>The effective size in bytes of this string.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static int EffectiveSizeOf<T>(ref this T fs)
where T : struct, INativeList<byte>, IUTF8Bytes
{
return sizeof(ushort) + fs.Length + 1;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ff34e3b577f44d67b146cc64b598b67d
timeCreated: 1591136455

View file

@ -0,0 +1,229 @@
using System;
using System.Runtime.CompilerServices;
namespace Unity.Collections
{
/// <summary>
/// Provides methods for parsing numbers from FixedString*N*Bytes.
/// </summary>
[BurstCompatible]
public unsafe static partial class FixedStringMethods
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
internal static bool ParseLongInternal<T>(ref T fs, ref int offset, out long value)
where T : struct, INativeList<byte>, IUTF8Bytes
{
int resetOffset = offset;
int sign = 1;
if (offset < fs.Length)
{
if (fs.Peek(offset).value == '+')
fs.Read(ref offset);
else if (fs.Peek(offset).value == '-')
{
sign = -1;
fs.Read(ref offset);
}
}
int digitOffset = offset;
value = 0;
while (offset < fs.Length && Unicode.Rune.IsDigit(fs.Peek(offset)))
{
value *= 10;
value += fs.Read(ref offset).value - '0';
}
value = sign * value;
// If there was no number parsed, revert the offset since it's a syntax error and we might
// have erroneously parsed a '-' or '+'
if (offset == digitOffset)
{
offset = resetOffset;
return false;
}
return true;
}
/// <summary>
/// Parses an int from this string starting at a byte offset.
/// </summary>
/// <remarks>
/// Stops parsing after the last number character. (Unlike parsing methods in other API's, this method does not expect to necessarily parse the entire string.)
///
/// The parsed value is bitwise-identical to the result of System.Int32.Parse.
/// </remarks>
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
/// <param name="fs">The string from which to parse.</param>
/// <param name="offset">A reference to an index of the byte at which to parse an int.</param>
/// <param name="output">Outputs the parsed int. Ignore if parsing fails.</param>
/// <returns>ParseError.None if successful. Otherwise returns ParseError.Overflow or ParseError.Syntax.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static ParseError Parse<T>(ref this T fs, ref int offset, ref int output)
where T : struct, INativeList<byte>, IUTF8Bytes
{
if (!ParseLongInternal(ref fs, ref offset, out long value))
return ParseError.Syntax;
if (value > int.MaxValue)
return ParseError.Overflow;
if (value < int.MinValue)
return ParseError.Overflow;
output = (int)value;
return ParseError.None;
}
/// <summary>
/// Parses an uint from this string starting at a byte offset.
/// </summary>
/// <remarks>
/// Stops parsing after the last number character. (Unlike parsing methods in other API's, this method does not expect to necessarily parse the entire string.)
///
/// The parsed value is bitwise-identical to the result of System.UInt32.Parse.
/// </remarks>
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
/// <param name="fs">The string from which to parse.</param>
/// <param name="offset">A reference to an index of the byte at which to parse a uint.</param>
/// <param name="output">Outputs the parsed uint. Ignore if parsing fails.</param>
/// <returns>ParseError.None if successful. Otherwise returns ParseError.Overflow or ParseError.Syntax.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static ParseError Parse<T>(ref this T fs, ref int offset, ref uint output)
where T : struct, INativeList<byte>, IUTF8Bytes
{
if (!ParseLongInternal(ref fs, ref offset, out long value))
return ParseError.Syntax;
if (value > uint.MaxValue)
return ParseError.Overflow;
if (value < uint.MinValue)
return ParseError.Overflow;
output = (uint)value;
return ParseError.None;
}
/// <summary>
/// Parses a float from this string starting at a byte offset.
/// </summary>
/// Stops parsing after the last number character. (Unlike parsing methods in other API's, this method does not expect to necessarily parse the entire string.)
///
/// <remarks>The parsed value is bitwise-identical to the result of System.Single.Parse.</remarks>
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
/// <param name="fs">The string from which to parse.</param>
/// <param name="offset">Index of the byte at which to parse a float.</param>
/// <param name="output">Outputs the parsed float. Ignore if parsing fails.</param>
/// <param name="decimalSeparator">The character used to separate the integer part of the number from the fractional part. Defaults to '.' (period).</param>
/// <returns>ParseError.None if successful. Otherwise returns ParseError.Overflow, ParseError.Underflow, or ParseError.Syntax.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
public static ParseError Parse<T>(ref this T fs, ref int offset, ref float output, char decimalSeparator = '.')
where T : struct, INativeList<byte>, IUTF8Bytes
{
int resetOffset = offset;
int sign = 1;
if (offset < fs.Length)
{
if (fs.Peek(offset).value == '+')
fs.Read(ref offset);
else if (fs.Peek(offset).value == '-')
{
sign = -1;
fs.Read(ref offset);
}
}
if (fs.Found(ref offset, 'n', 'a', 'n'))
{
FixedStringUtils.UintFloatUnion ufu = new FixedStringUtils.UintFloatUnion();
ufu.uintValue = 4290772992U;
output = ufu.floatValue;
return ParseError.None;
}
if (fs.Found(ref offset, 'i', 'n', 'f', 'i', 'n', 'i', 't', 'y'))
{
output = (sign == 1) ? Single.PositiveInfinity : Single.NegativeInfinity;
return ParseError.None;
}
ulong decimalMantissa = 0;
int significantDigits = 0;
int digitsAfterDot = 0;
int mantissaDigits = 0;
while (offset < fs.Length && Unicode.Rune.IsDigit(fs.Peek(offset)))
{
++mantissaDigits;
if (significantDigits < 9)
{
var temp = decimalMantissa * 10 + (ulong)(fs.Peek(offset).value - '0');
if (temp > decimalMantissa)
++significantDigits;
decimalMantissa = temp;
}
else
--digitsAfterDot;
fs.Read(ref offset);
}
if (offset < fs.Length && fs.Peek(offset).value == decimalSeparator)
{
fs.Read(ref offset);
while (offset < fs.Length && Unicode.Rune.IsDigit(fs.Peek(offset)))
{
++mantissaDigits;
if (significantDigits < 9)
{
var temp = decimalMantissa * 10 + (ulong)(fs.Peek(offset).value - '0');
if (temp > decimalMantissa)
++significantDigits;
decimalMantissa = temp;
++digitsAfterDot;
}
fs.Read(ref offset);
}
}
if (mantissaDigits == 0)
{
// Reset offset in case '+' or '-' was erroneously parsed
offset = resetOffset;
return ParseError.Syntax;
}
int decimalExponent = 0;
int decimalExponentSign = 1;
if (offset < fs.Length && (fs.Peek(offset).value | 32) == 'e')
{
fs.Read(ref offset);
if (offset < fs.Length)
{
if (fs.Peek(offset).value == '+')
fs.Read(ref offset);
else if (fs.Peek(offset).value == '-')
{
decimalExponentSign = -1;
fs.Read(ref offset);
}
}
int digitOffset = offset;
while (offset < fs.Length && Unicode.Rune.IsDigit(fs.Peek(offset)))
{
decimalExponent = decimalExponent * 10 + (fs.Peek(offset).value - '0');
fs.Read(ref offset);
}
if (offset == digitOffset)
{
// Reset offset in case '+' or '-' was erroneously parsed
offset = resetOffset;
return ParseError.Syntax;
}
if (decimalExponent > 38)
{
if (decimalExponentSign == 1)
return ParseError.Overflow;
else
return ParseError.Underflow;
}
}
decimalExponent = decimalExponent * decimalExponentSign - digitsAfterDot;
var error = FixedStringUtils.Base10ToBase2(ref output, decimalMantissa, decimalExponent);
if (error != ParseError.None)
return error;
output *= sign;
return ParseError.None;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: af16c54541e443e59c1fdf0919c1b9ea
timeCreated: 1591061254

View file

@ -0,0 +1,145 @@
using System;
using System.Runtime.InteropServices;
using Unity.Collections.LowLevel.Unsafe;
using System.Diagnostics;
namespace Unity.Collections
{
/// <summary>
/// An interface for a sequence of UTF-8 encoded text.
/// </summary>
public interface IUTF8Bytes
{
/// <summary>
/// Whether this IUTF8Bytes is empty.
/// </summary>
/// <value>True if this IUTF8Bytes is empty.</value>
bool IsEmpty { get; }
/// <summary>
/// Returns a pointer to the content of this IUTF8Bytes.
/// </summary>
/// <remarks>The pointer may point to stack memory.</remarks>
/// <returns>A pointer to the content of this IUTF8Bytes.</returns>
unsafe byte* GetUnsafePtr();
/// <summary>
/// Attempt to set the length in bytes of this IUTF8Bytes's content buffer.
/// </summary>
/// <param name="newLength">The new length in bytes of the IUTF8Bytes's content buffer.</param>
/// <param name="clearOptions">Whether any bytes added should be zeroed out.</param>
/// <returns>True if the new length is valid.</returns>
bool TryResize(int newLength, NativeArrayOptions clearOptions = NativeArrayOptions.ClearMemory);
}
[BurstCompatible]
internal unsafe static class FixedStringUtils
{
[StructLayout(LayoutKind.Explicit)]
internal struct UintFloatUnion
{
[FieldOffset(0)]
public uint uintValue;
[FieldOffset(0)]
public float floatValue;
}
internal static ParseError Base10ToBase2(ref float output, ulong mantissa10, int exponent10)
{
if (mantissa10 == 0)
{
output = 0.0f;
return ParseError.None;
}
if (exponent10 == 0)
{
output = mantissa10;
return ParseError.None;
}
var exponent2 = exponent10;
var mantissa2 = mantissa10;
while (exponent10 > 0)
{
while ((mantissa2 & 0xe000000000000000U) != 0)
{
mantissa2 >>= 1;
++exponent2;
}
mantissa2 *= 5;
--exponent10;
}
while (exponent10 < 0)
{
while ((mantissa2 & 0x8000000000000000U) == 0)
{
mantissa2 <<= 1;
--exponent2;
}
mantissa2 /= 5;
++exponent10;
}
// TODO: implement math.ldexpf (which presumably handles denormals (i don't))
UintFloatUnion ufu = new UintFloatUnion();
ufu.floatValue = mantissa2;
var e = (int)((ufu.uintValue >> 23) & 0xFFU) - 127;
e += exponent2;
if (e > 128)
return ParseError.Overflow;
if (e < -127)
return ParseError.Underflow;
ufu.uintValue = (ufu.uintValue & ~(0xFFU << 23)) | ((uint)(e + 127) << 23);
output = ufu.floatValue;
return ParseError.None;
}
internal static void Base2ToBase10(ref ulong mantissa10, ref int exponent10, float input)
{
UintFloatUnion ufu = new UintFloatUnion();
ufu.floatValue = input;
if (ufu.uintValue == 0)
{
mantissa10 = 0;
exponent10 = 0;
return;
}
var mantissa2 = (ufu.uintValue & ((1 << 23) - 1)) | (1 << 23);
var exponent2 = (int)(ufu.uintValue >> 23) - 127 - 23;
mantissa10 = mantissa2;
exponent10 = exponent2;
if (exponent2 > 0)
{
while (exponent2 > 0)
{
// denormalize mantissa10 as much as you can, to minimize loss when doing /5 below.
while (mantissa10 <= UInt64.MaxValue / 10)
{
mantissa10 *= 10;
--exponent10;
}
mantissa10 /= 5;
--exponent2;
}
}
if (exponent2 < 0)
{
while (exponent2 < 0)
{
// normalize mantissa10 just as much as you need, in order to make the *5 below not overflow.
while (mantissa10 > UInt64.MaxValue / 5)
{
mantissa10 /= 10;
++exponent10;
}
mantissa10 *= 5;
++exponent2;
}
}
// normalize mantissa10
while (mantissa10 > 9999999U || mantissa10 % 10 == 0)
{
mantissa10 = (mantissa10 + (mantissa10 < 100000000U ? 5u : 0u)) / 10;
++exponent10;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8172d9c91a2b64bca91bb78a3a3d4552
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,817 @@

//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// TextTransform Samples/Packages/com.unity.collections/Unity.Collections/HeapString.tt
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.Internal;
#if UNITY_PROPERTIES_EXISTS
using Unity.Properties;
#endif
namespace Unity.Collections
{
/// <summary>
/// An unmanaged, allocated, mutable, resizable UTF-8 string.
/// </summary>
/// <remarks>
/// The string is always null-terminated, meaning a zero byte always immediately follows the last character.
/// </remarks>
[BurstCompatible]
[Obsolete("HeapString has been removed and replaced with NativeText (RemovedAfter 2021-07-21) (UnityUpgradable) -> NativeText", false)]
public partial struct HeapString
: INativeList<byte>
, IDisposable
, IUTF8Bytes
, IComparable<String>
, IEquatable<String>
, IComparable<HeapString>
, IEquatable<HeapString>
, IComparable<FixedString32Bytes>
, IEquatable<FixedString32Bytes>
, IComparable<FixedString64Bytes>
, IEquatable<FixedString64Bytes>
, IComparable<FixedString128Bytes>
, IEquatable<FixedString128Bytes>
, IComparable<FixedString512Bytes>
, IEquatable<FixedString512Bytes>
, IComparable<FixedString4096Bytes>
, IEquatable<FixedString4096Bytes>
{
// NOTE! This Length is always > 0, because we have a null terminating byte.
// We hide this byte from HeapString users.
private NativeList<byte> m_Data;
/// <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
{
get {
return m_Data.Length - 1;
}
set {
m_Data.Resize(value + 1, NativeArrayOptions.UninitializedMemory);
m_Data[value] = 0;
}
}
/// <summary>
/// The current capacity 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 of the string.</value>
public int Capacity
{
get {
return m_Data.Capacity - 1;
}
set {
m_Data.Capacity = value + 1;
}
}
/// <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
Length = newLength;
return true;
}
/// <summary>
/// Whether this string has no characters.
/// </summary>
/// <value>True if this string has no characters.</value>
public bool IsEmpty => m_Data.Length == 1;
/// <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 bool IsCreated => m_Data.IsCreated;
/// <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 unsafe byte* GetUnsafePtr()
{
return (byte*) m_Data.GetUnsafePtr();
}
/// <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]
{
get {
CheckIndexInRange(index);
return m_Data[index];
}
set {
CheckIndexInRange(index);
m_Data[index] = value;
}
}
/// <summary>
/// Returns the 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 m_Data.ElementAt(index);
}
/// <summary>
/// Sets the length to 0.
/// </summary>
public void Clear()
{
Length = 0;
}
/// <summary>
/// Appends a byte.
/// </summary>
/// <remarks>
/// A zero byte will always follow the newly appended byte.
///
/// No validation is performed: it is your responsibility for the bytes of the string to form valid UTF-8 when you're done appending bytes.
/// </remarks>
/// <param name="value">A byte to append.</param>
public void Add(in byte value)
{
this[Length++] = value;
}
/// <summary>
/// Returns the lexicographical sort order of this string relative to another.
/// </summary>
/// <param name="other">Another string to compare with.</param>
/// <returns>A number denoting the lexicographical sort order of this string relative to the other string:
///
/// 0 denotes both strings have the same sort position.<br/>
/// -1 denotes that this string should be sorted to precede the other.<br/>
/// +1 denotes that this string should be sorted to follow the other.<br/>
/// </returns>
public int CompareTo(HeapString other)
{
return FixedStringMethods.CompareTo(ref this, other);
}
/// <summary>
/// Returns true if this string and another are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="other">Another string to compare with.</param>
/// <returns>True if the two strings are equal.</returns>
public bool Equals(HeapString other)
{
return FixedStringMethods.Equals(ref this, other);
}
/// <summary>
/// Releases all resources (memory and safety handles).
/// </summary>
public void Dispose()
{
m_Data.Dispose();
}
/// <summary>
/// A copy of this string as a managed string.
/// </summary>
/// <remarks>
/// For internal use only. Use <see cref="ToString"/> instead.
/// </remarks>
/// <value>A copy of this string as a managed string.</value>
[CreateProperty]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[NotBurstCompatible]
public string Value => ToString();
/// <summary>
/// An enumerator over the characters (not bytes) of a HeapString.
/// </summary>
/// <remarks>
/// In an enumerator's initial state, its index is invalid. The first <see cref="MoveNext"/> call advances the enumerator's index to the first character.
/// </remarks>
public struct Enumerator : IEnumerator<Unicode.Rune>
{
HeapString target;
int offset;
Unicode.Rune current;
/// <summary>
/// Initializes and returns an instance of HeapString.Enumerator.
/// </summary>
/// <param name="source">A HeapString for which to create an enumerator.</param>
public Enumerator(HeapString source)
{
target = source;
offset = 0;
current = default;
}
/// <summary>
/// Does nothing.
/// </summary>
public void Dispose()
{
}
/// <summary>
/// Advances the enumerator to the next character, returning true if <see cref="Current"/> is valid to read afterwards.
/// </summary>
/// <returns>True if <see cref="Current"/> is valid to read after the call.</returns>
public bool MoveNext()
{
if (offset >= target.Length)
return false;
unsafe
{
Unicode.Utf8ToUcs(out current, target.GetUnsafePtr(), ref offset, target.Length);
}
return true;
}
/// <summary>
/// Resets the enumerator to its initial state.
/// </summary>
public void Reset()
{
offset = 0;
current = default;
}
object IEnumerator.Current => Current;
/// <summary>
/// The current character.
/// </summary>
/// <value>The current character.</value>
public Unicode.Rune Current => current;
}
/// <summary>
/// Returns an enumerator for iterating over the characters of the HeapString.
/// </summary>
/// <returns>An enumerator for iterating over the characters of the HeapString.</returns>
public Enumerator GetEnumerator()
{
return new Enumerator(this);
}
/// <summary>
/// Returns the lexicographical sort order of this string relative to another.
/// </summary>
/// <param name="other">Another string to compare with.</param>
/// <returns>A number denoting the lexicographical sort order of this string relative to the other string:
///
/// 0 denotes both strings have the same sort position.<br/>
/// -1 denotes that this string should be sorted to precede the other.<br/>
/// +1 denotes that this string should be sorted to follow the other.<br/>
/// </returns>
[NotBurstCompatible]
public int CompareTo(String other)
{
return ToString().CompareTo(other);
}
/// <summary>
/// Returns true if this string and another are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="other">Another string to compare with.</param>
/// <returns>True if the two strings are equal.</returns>
[NotBurstCompatible]
public bool Equals(String other)
{
return ToString().Equals(other);
}
/// <summary>
/// Initializes and returns an instance of HeapString with the characters copied from another string.
/// </summary>
/// <param name="source">A string to copy characters from.</param>
/// <param name="allocator">The allocator to use.</param>
[NotBurstCompatible]
public HeapString(String source, Allocator allocator)
{
m_Data = new NativeList<byte>(source.Length * 2 + 1, allocator);
Length = source.Length * 2; // maximum possible
unsafe
{
fixed (char* sourceptr = source)
{
var error = UTF8ArrayUnsafeUtility.Copy(GetUnsafePtr(), out var actualBytes, Capacity, sourceptr, source.Length);
if (error != CopyError.None)
{
m_Data.Dispose();
m_Data = default;
ThrowCopyError(error, source);
}
this.Length = actualBytes;
}
}
}
/// <summary>
/// Initializes and returns an instance of HeapString with a specified initial capacity.
/// </summary>
/// <param name="capacity">The initial capacity in bytes.</param>
/// <param name="allocator">The allocator to use.</param>
public HeapString(int capacity, Allocator allocator)
{
m_Data = new NativeList<byte>(capacity + 1, allocator);
this.Length = 0;
}
/// <summary>
/// Initializes and returns an instance of HeapString with an initial capacity of 128 bytes.
/// </summary>
/// <param name="allocator">The allocator to use.</param>
public HeapString(Allocator allocator)
{
m_Data = new NativeList<byte>(128 + 1, allocator);
this.Length = 0;
}
/// <summary>
/// Returns the lexicographical sort order of this string relative to another.
/// </summary>
/// <param name="other">Another string to compare with.</param>
/// <returns>A number denoting the lexicographical sort order of this string relative to the other string:
///
/// 0 denotes both strings have the same sort position.<br/>
/// -1 denotes that this string should be sorted to precede the other.<br/>
/// +1 denotes that this string should be sorted to follow the other.<br/>
/// </returns>
public int CompareTo(FixedString32Bytes other)
{
return FixedStringMethods.CompareTo(ref this, other);
}
/// <summary>
/// Initializes and returns an instance of HeapString with the characters copied from another string.
/// </summary>
/// <param name="source">A string to copy characters from.</param>
/// <param name="allocator">The allocator to use.</param>
public HeapString(in FixedString32Bytes source, Allocator allocator)
{
m_Data = new NativeList<byte>(source.utf8LengthInBytes + 1, allocator);
Length = source.utf8LengthInBytes;
unsafe {
byte* sbytes = (byte*) UnsafeUtilityExtensions.AddressOf(source.bytes);
byte* dbytes = (byte*) m_Data.GetUnsafePtr();
UnsafeUtility.MemCpy(dbytes, sbytes, source.utf8LengthInBytes);
}
}
/// <summary>
/// Returns true if two strings are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="a">A string to compare.</param>
/// <param name="b">Another string to compare.</param>
/// <returns>True if the two strings are equal.</returns>
public static bool operator ==(in HeapString a, in FixedString32Bytes b)
{
unsafe {
var aref = UnsafeUtilityExtensions.AsRef(a);
int alen = aref.Length;
int blen = b.utf8LengthInBytes;
byte* aptr = (byte*) aref.GetUnsafePtr();
byte* bptr = (byte*) UnsafeUtilityExtensions.AddressOf(b.bytes);
return UTF8ArrayUnsafeUtility.EqualsUTF8Bytes(aptr, alen, bptr, blen);
}
}
/// <summary>
/// Returns true if two strings are unequal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="a">A string to compare.</param>
/// <param name="b">Another string to compare.</param>
/// <returns>True if the two strings are unequal.</returns>
public static bool operator !=(in HeapString a, in FixedString32Bytes b)
{
return !(a == b);
}
/// <summary>
/// Returns true if this string and another are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="other">Another string to compare with.</param>
/// <returns>True if the two strings are equal.</returns>
public bool Equals(FixedString32Bytes other)
{
return this == other;
}
/// <summary>
/// Returns the lexicographical sort order of this string relative to another.
/// </summary>
/// <param name="other">Another string to compare with.</param>
/// <returns>A number denoting the lexicographical sort order of this string relative to the other string:
///
/// 0 denotes both strings have the same sort position.<br/>
/// -1 denotes that this string should be sorted to precede the other.<br/>
/// +1 denotes that this string should be sorted to follow the other.<br/>
/// </returns>
public int CompareTo(FixedString64Bytes other)
{
return FixedStringMethods.CompareTo(ref this, other);
}
/// <summary>
/// Initializes and returns an instance of HeapString with the characters copied from another string.
/// </summary>
/// <param name="source">A string to copy characters from.</param>
/// <param name="allocator">The allocator to use.</param>
public HeapString(in FixedString64Bytes source, Allocator allocator)
{
m_Data = new NativeList<byte>(source.utf8LengthInBytes + 1, allocator);
Length = source.utf8LengthInBytes;
unsafe {
byte* sbytes = (byte*) UnsafeUtilityExtensions.AddressOf(source.bytes);
byte* dbytes = (byte*) m_Data.GetUnsafePtr();
UnsafeUtility.MemCpy(dbytes, sbytes, source.utf8LengthInBytes);
}
}
/// <summary>
/// Returns true if two strings are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="a">A string to compare.</param>
/// <param name="b">Another string to compare.</param>
/// <returns>True if the two strings are equal.</returns>
public static bool operator ==(in HeapString a, in FixedString64Bytes b)
{
unsafe {
var aref = UnsafeUtilityExtensions.AsRef(a);
int alen = aref.Length;
int blen = b.utf8LengthInBytes;
byte* aptr = (byte*) aref.GetUnsafePtr();
byte* bptr = (byte*) UnsafeUtilityExtensions.AddressOf(b.bytes);
return UTF8ArrayUnsafeUtility.EqualsUTF8Bytes(aptr, alen, bptr, blen);
}
}
/// <summary>
/// Returns true if two strings are unequal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="a">A string to compare.</param>
/// <param name="b">Another string to compare.</param>
/// <returns>True if the two strings are unequal.</returns>
public static bool operator !=(in HeapString a, in FixedString64Bytes b)
{
return !(a == b);
}
/// <summary>
/// Returns true if this string and another are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="other">Another string to compare with.</param>
/// <returns>True if the two strings are equal.</returns>
public bool Equals(FixedString64Bytes other)
{
return this == other;
}
/// <summary>
/// Returns the lexicographical sort order of this string relative to another.
/// </summary>
/// <param name="other">Another string to compare with.</param>
/// <returns>A number denoting the lexicographical sort order of this string relative to the other string:
///
/// 0 denotes both strings have the same sort position.<br/>
/// -1 denotes that this string should be sorted to precede the other.<br/>
/// +1 denotes that this string should be sorted to follow the other.<br/>
/// </returns>
public int CompareTo(FixedString128Bytes other)
{
return FixedStringMethods.CompareTo(ref this, other);
}
/// <summary>
/// Initializes and returns an instance of HeapString with the characters copied from another string.
/// </summary>
/// <param name="source">A string to copy characters from.</param>
/// <param name="allocator">The allocator to use.</param>
public HeapString(in FixedString128Bytes source, Allocator allocator)
{
m_Data = new NativeList<byte>(source.utf8LengthInBytes + 1, allocator);
Length = source.utf8LengthInBytes;
unsafe {
byte* sbytes = (byte*) UnsafeUtilityExtensions.AddressOf(source.bytes);
byte* dbytes = (byte*) m_Data.GetUnsafePtr();
UnsafeUtility.MemCpy(dbytes, sbytes, source.utf8LengthInBytes);
}
}
/// <summary>
/// Returns true if two strings are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="a">A string to compare.</param>
/// <param name="b">Another string to compare.</param>
/// <returns>True if the two strings are equal.</returns>
public static bool operator ==(in HeapString a, in FixedString128Bytes b)
{
unsafe {
var aref = UnsafeUtilityExtensions.AsRef(a);
int alen = aref.Length;
int blen = b.utf8LengthInBytes;
byte* aptr = (byte*) aref.GetUnsafePtr();
byte* bptr = (byte*) UnsafeUtilityExtensions.AddressOf(b.bytes);
return UTF8ArrayUnsafeUtility.EqualsUTF8Bytes(aptr, alen, bptr, blen);
}
}
/// <summary>
/// Returns true if two strings are unequal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="a">A string to compare.</param>
/// <param name="b">Another string to compare.</param>
/// <returns>True if the two strings are unequal.</returns>
public static bool operator !=(in HeapString a, in FixedString128Bytes b)
{
return !(a == b);
}
/// <summary>
/// Returns true if this string and another are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="other">Another string to compare with.</param>
/// <returns>True if the two strings are equal.</returns>
public bool Equals(FixedString128Bytes other)
{
return this == other;
}
/// <summary>
/// Returns the lexicographical sort order of this string relative to another.
/// </summary>
/// <param name="other">Another string to compare with.</param>
/// <returns>A number denoting the lexicographical sort order of this string relative to the other string:
///
/// 0 denotes both strings have the same sort position.<br/>
/// -1 denotes that this string should be sorted to precede the other.<br/>
/// +1 denotes that this string should be sorted to follow the other.<br/>
/// </returns>
public int CompareTo(FixedString512Bytes other)
{
return FixedStringMethods.CompareTo(ref this, other);
}
/// <summary>
/// Initializes and returns an instance of HeapString with the characters copied from another string.
/// </summary>
/// <param name="source">A string to copy characters from.</param>
/// <param name="allocator">The allocator to use.</param>
public HeapString(in FixedString512Bytes source, Allocator allocator)
{
m_Data = new NativeList<byte>(source.utf8LengthInBytes + 1, allocator);
Length = source.utf8LengthInBytes;
unsafe {
byte* sbytes = (byte*) UnsafeUtilityExtensions.AddressOf(source.bytes);
byte* dbytes = (byte*) m_Data.GetUnsafePtr();
UnsafeUtility.MemCpy(dbytes, sbytes, source.utf8LengthInBytes);
}
}
/// <summary>
/// Returns true if two strings are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="a">A string to compare.</param>
/// <param name="b">Another string to compare.</param>
/// <returns>True if the two strings are equal.</returns>
public static bool operator ==(in HeapString a, in FixedString512Bytes b)
{
unsafe {
var aref = UnsafeUtilityExtensions.AsRef(a);
int alen = aref.Length;
int blen = b.utf8LengthInBytes;
byte* aptr = (byte*) aref.GetUnsafePtr();
byte* bptr = (byte*) UnsafeUtilityExtensions.AddressOf(b.bytes);
return UTF8ArrayUnsafeUtility.EqualsUTF8Bytes(aptr, alen, bptr, blen);
}
}
/// <summary>
/// Returns true if two strings are unequal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="a">A string to compare.</param>
/// <param name="b">Another string to compare.</param>
/// <returns>True if the two strings are unequal.</returns>
public static bool operator !=(in HeapString a, in FixedString512Bytes b)
{
return !(a == b);
}
/// <summary>
/// Returns true if this string and another are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="other">Another string to compare with.</param>
/// <returns>True if the two strings are equal.</returns>
public bool Equals(FixedString512Bytes other)
{
return this == other;
}
/// <summary>
/// Returns the lexicographical sort order of this string relative to another.
/// </summary>
/// <param name="other">Another string to compare with.</param>
/// <returns>A number denoting the lexicographical sort order of this string relative to the other string:
///
/// 0 denotes both strings have the same sort position.<br/>
/// -1 denotes that this string should be sorted to precede the other.<br/>
/// +1 denotes that this string should be sorted to follow the other.<br/>
/// </returns>
public int CompareTo(FixedString4096Bytes other)
{
return FixedStringMethods.CompareTo(ref this, other);
}
/// <summary>
/// Initializes and returns an instance of HeapString with the characters copied from another string.
/// </summary>
/// <param name="source">A string to copy characters from.</param>
/// <param name="allocator">The allocator to use.</param>
public HeapString(in FixedString4096Bytes source, Allocator allocator)
{
m_Data = new NativeList<byte>(source.utf8LengthInBytes + 1, allocator);
Length = source.utf8LengthInBytes;
unsafe {
byte* sbytes = (byte*) UnsafeUtilityExtensions.AddressOf(source.bytes);
byte* dbytes = (byte*) m_Data.GetUnsafePtr();
UnsafeUtility.MemCpy(dbytes, sbytes, source.utf8LengthInBytes);
}
}
/// <summary>
/// Returns true if two strings are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="a">A string to compare.</param>
/// <param name="b">Another string to compare.</param>
/// <returns>True if the two strings are equal.</returns>
public static bool operator ==(in HeapString a, in FixedString4096Bytes b)
{
unsafe {
var aref = UnsafeUtilityExtensions.AsRef(a);
int alen = aref.Length;
int blen = b.utf8LengthInBytes;
byte* aptr = (byte*) aref.GetUnsafePtr();
byte* bptr = (byte*) UnsafeUtilityExtensions.AddressOf(b.bytes);
return UTF8ArrayUnsafeUtility.EqualsUTF8Bytes(aptr, alen, bptr, blen);
}
}
/// <summary>
/// Returns true if two strings are unequal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="a">A string to compare.</param>
/// <param name="b">Another string to compare.</param>
/// <returns>True if the two strings are unequal.</returns>
public static bool operator !=(in HeapString a, in FixedString4096Bytes b)
{
return !(a == b);
}
/// <summary>
/// Returns true if this string and another are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="other">Another string to compare with.</param>
/// <returns>True if the two strings are equal.</returns>
public bool Equals(FixedString4096Bytes other)
{
return this == other;
}
/// <summary>
/// Returns a managed string copy of this string.
/// </summary>
/// <returns>A managed string copy of this string.</returns>>
[NotBurstCompatible]
public override String ToString()
{
if (!m_Data.IsCreated)
return "";
return this.ConvertToString();
}
/// <summary>
/// Returns a hash code of this string.
/// </summary>
/// <remarks>The hash code is an integer that is always the same for two equal strings but (very likely) different for two unequal strings.</remarks>
/// <returns>A hash code of this string.</returns>
public override int GetHashCode()
{
return this.ComputeHashCode();
}
/// <summary>
/// Returns true if this string and another object are equal.
/// </summary>
/// <remarks>For the object to be equal, it must itself be a managed string, HeapString, or FixedString*N*Bytes.
///
/// Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="other">Another string to compare with.</param>
/// <returns>True if this string and the object are equal.</returns>
[NotBurstCompatible]
public override bool Equals(object other)
{
if(ReferenceEquals(null, other)) return false;
if(other is String aString) return Equals(aString);
if(other is HeapString aHeapString) return Equals(aHeapString);
if(other is FixedString32Bytes a32) return Equals(a32);
if(other is FixedString64Bytes a64) return Equals(a64);
if(other is FixedString128Bytes a128) return Equals(a128);
if(other is FixedString512Bytes a512) return Equals(a512);
if(other is FixedString4096Bytes a4096) return Equals(a4096);
return false;
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
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 HeapString of {Length} length.");
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void ThrowCopyError(CopyError error, String source)
{
throw new ArgumentException($"HeapString: {error} while copying \"{source}\"");
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 448d2790464848f38fb2816637756c89
timeCreated: 1595470138

View file

@ -0,0 +1,537 @@
<#@ template language="C#" debug="True" #>
<#@ output extension=".gen.cs" encoding="utf-8" #>
<#@ assembly name="System.Core" #>
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// TextTransform Samples/Packages/com.unity.collections/Unity.Collections/HeapString.tt
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.Internal;
#if UNITY_PROPERTIES_EXISTS
using Unity.Properties;
#endif
<#
{
var SIZES = new [] {32,64,128,512,4096};
#>
namespace Unity.Collections
{
/// <summary>
/// An unmanaged, allocated, mutable, resizable UTF-8 string.
/// </summary>
/// <remarks>
/// The string is always null-terminated, meaning a zero byte always immediately follows the last character.
/// </remarks>
[BurstCompatible]
[Obsolete("HeapString has been removed and replaced with NativeText (RemovedAfter 2021-07-21) (UnityUpgradable) -> NativeText", false)]
public partial struct HeapString
: INativeList<byte>
, IDisposable
, IUTF8Bytes
, IComparable<String>
, IEquatable<String>
, IComparable<HeapString>
, IEquatable<HeapString>
<#
foreach (var OTHERBYTES in SIZES)
{
#>
, IComparable<FixedString<#=OTHERBYTES#>Bytes>
, IEquatable<FixedString<#=OTHERBYTES#>Bytes>
<#
}
#>
{
// NOTE! This Length is always > 0, because we have a null terminating byte.
// We hide this byte from HeapString users.
private NativeList<byte> m_Data;
/// <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
{
get {
return m_Data.Length - 1;
}
set {
m_Data.Resize(value + 1, NativeArrayOptions.UninitializedMemory);
m_Data[value] = 0;
}
}
/// <summary>
/// The current capacity 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 of the string.</value>
public int Capacity
{
get {
return m_Data.Capacity - 1;
}
set {
m_Data.Capacity = value + 1;
}
}
/// <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
Length = newLength;
return true;
}
/// <summary>
/// Whether this string has no characters.
/// </summary>
/// <value>True if this string has no characters.</value>
public bool IsEmpty => m_Data.Length == 1;
/// <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 bool IsCreated => m_Data.IsCreated;
/// <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 unsafe byte* GetUnsafePtr()
{
return (byte*) m_Data.GetUnsafePtr();
}
/// <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]
{
get {
CheckIndexInRange(index);
return m_Data[index];
}
set {
CheckIndexInRange(index);
m_Data[index] = value;
}
}
/// <summary>
/// Returns the 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 m_Data.ElementAt(index);
}
/// <summary>
/// Sets the length to 0.
/// </summary>
public void Clear()
{
Length = 0;
}
/// <summary>
/// Appends a byte.
/// </summary>
/// <remarks>
/// A zero byte will always follow the newly appended byte.
///
/// No validation is performed: it is your responsibility for the bytes of the string to form valid UTF-8 when you're done appending bytes.
/// </remarks>
/// <param name="value">A byte to append.</param>
public void Add(in byte value)
{
this[Length++] = value;
}
/// <summary>
/// Returns the lexicographical sort order of this string relative to another.
/// </summary>
/// <param name="other">Another string to compare with.</param>
/// <returns>A number denoting the lexicographical sort order of this string relative to the other string:
///
/// 0 denotes both strings have the same sort position.<br/>
/// -1 denotes that this string should be sorted to precede the other.<br/>
/// +1 denotes that this string should be sorted to follow the other.<br/>
/// </returns>
public int CompareTo(HeapString other)
{
return FixedStringMethods.CompareTo(ref this, other);
}
/// <summary>
/// Returns true if this string and another are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="other">Another string to compare with.</param>
/// <returns>True if the two strings are equal.</returns>
public bool Equals(HeapString other)
{
return FixedStringMethods.Equals(ref this, other);
}
/// <summary>
/// Releases all resources (memory and safety handles).
/// </summary>
public void Dispose()
{
m_Data.Dispose();
}
/// <summary>
/// A copy of this string as a managed string.
/// </summary>
/// <remarks>
/// For internal use only. Use <see cref="ToString"/> instead.
/// </remarks>
/// <value>A copy of this string as a managed string.</value>
[CreateProperty]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[NotBurstCompatible]
public string Value => ToString();
/// <summary>
/// An enumerator over the characters (not bytes) of a HeapString.
/// </summary>
/// <remarks>
/// In an enumerator's initial state, its index is invalid. The first <see cref="MoveNext"/> call advances the enumerator's index to the first character.
/// </remarks>
public struct Enumerator : IEnumerator<Unicode.Rune>
{
HeapString target;
int offset;
Unicode.Rune current;
/// <summary>
/// Initializes and returns an instance of HeapString.Enumerator.
/// </summary>
/// <param name="source">A HeapString for which to create an enumerator.</param>
public Enumerator(HeapString source)
{
target = source;
offset = 0;
current = default;
}
/// <summary>
/// Does nothing.
/// </summary>
public void Dispose()
{
}
/// <summary>
/// Advances the enumerator to the next character, returning true if <see cref="Current"/> is valid to read afterwards.
/// </summary>
/// <returns>True if <see cref="Current"/> is valid to read after the call.</returns>
public bool MoveNext()
{
if (offset >= target.Length)
return false;
unsafe
{
Unicode.Utf8ToUcs(out current, target.GetUnsafePtr(), ref offset, target.Length);
}
return true;
}
/// <summary>
/// Resets the enumerator to its initial state.
/// </summary>
public void Reset()
{
offset = 0;
current = default;
}
object IEnumerator.Current => Current;
/// <summary>
/// The current character.
/// </summary>
/// <value>The current character.</value>
public Unicode.Rune Current => current;
}
/// <summary>
/// Returns an enumerator for iterating over the characters of the HeapString.
/// </summary>
/// <returns>An enumerator for iterating over the characters of the HeapString.</returns>
public Enumerator GetEnumerator()
{
return new Enumerator(this);
}
/// <summary>
/// Returns the lexicographical sort order of this string relative to another.
/// </summary>
/// <param name="other">Another string to compare with.</param>
/// <returns>A number denoting the lexicographical sort order of this string relative to the other string:
///
/// 0 denotes both strings have the same sort position.<br/>
/// -1 denotes that this string should be sorted to precede the other.<br/>
/// +1 denotes that this string should be sorted to follow the other.<br/>
/// </returns>
[NotBurstCompatible]
public int CompareTo(String other)
{
return ToString().CompareTo(other);
}
/// <summary>
/// Returns true if this string and another are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="other">Another string to compare with.</param>
/// <returns>True if the two strings are equal.</returns>
[NotBurstCompatible]
public bool Equals(String other)
{
return ToString().Equals(other);
}
/// <summary>
/// Initializes and returns an instance of HeapString with the characters copied from another string.
/// </summary>
/// <param name="source">A string to copy characters from.</param>
/// <param name="allocator">The allocator to use.</param>
[NotBurstCompatible]
public HeapString(String source, Allocator allocator)
{
m_Data = new NativeList<byte>(source.Length * 2 + 1, allocator);
Length = source.Length * 2; // maximum possible
unsafe
{
fixed (char* sourceptr = source)
{
var error = UTF8ArrayUnsafeUtility.Copy(GetUnsafePtr(), out var actualBytes, Capacity, sourceptr, source.Length);
if (error != CopyError.None)
{
m_Data.Dispose();
m_Data = default;
ThrowCopyError(error, source);
}
this.Length = actualBytes;
}
}
}
/// <summary>
/// Initializes and returns an instance of HeapString with a specified initial capacity.
/// </summary>
/// <param name="capacity">The initial capacity in bytes.</param>
/// <param name="allocator">The allocator to use.</param>
public HeapString(int capacity, Allocator allocator)
{
m_Data = new NativeList<byte>(capacity + 1, allocator);
this.Length = 0;
}
/// <summary>
/// Initializes and returns an instance of HeapString with an initial capacity of 128 bytes.
/// </summary>
/// <param name="allocator">The allocator to use.</param>
public HeapString(Allocator allocator)
{
m_Data = new NativeList<byte>(128 + 1, allocator);
this.Length = 0;
}
<#
//
// Generate easy conversion and comparison between this and other FixedString types
//
foreach (var OTHERBYTES in SIZES)
{
#>
/// <summary>
/// Returns the lexicographical sort order of this string relative to another.
/// </summary>
/// <param name="other">Another string to compare with.</param>
/// <returns>A number denoting the lexicographical sort order of this string relative to the other string:
///
/// 0 denotes both strings have the same sort position.<br/>
/// -1 denotes that this string should be sorted to precede the other.<br/>
/// +1 denotes that this string should be sorted to follow the other.<br/>
/// </returns>
public int CompareTo(FixedString<#=OTHERBYTES#>Bytes other)
{
return FixedStringMethods.CompareTo(ref this, other);
}
/// <summary>
/// Initializes and returns an instance of HeapString with the characters copied from another string.
/// </summary>
/// <param name="source">A string to copy characters from.</param>
/// <param name="allocator">The allocator to use.</param>
public HeapString(in FixedString<#=OTHERBYTES#>Bytes source, Allocator allocator)
{
m_Data = new NativeList<byte>(source.utf8LengthInBytes + 1, allocator);
Length = source.utf8LengthInBytes;
unsafe {
byte* sbytes = (byte*) UnsafeUtilityExtensions.AddressOf(source.bytes);
byte* dbytes = (byte*) m_Data.GetUnsafePtr();
UnsafeUtility.MemCpy(dbytes, sbytes, source.utf8LengthInBytes);
}
}
/// <summary>
/// Returns true if two strings are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="a">A string to compare.</param>
/// <param name="b">Another string to compare.</param>
/// <returns>True if the two strings are equal.</returns>
public static bool operator ==(in HeapString a, in FixedString<#=OTHERBYTES#>Bytes b)
{
unsafe {
var aref = UnsafeUtilityExtensions.AsRef(a);
int alen = aref.Length;
int blen = b.utf8LengthInBytes;
byte* aptr = (byte*) aref.GetUnsafePtr();
byte* bptr = (byte*) UnsafeUtilityExtensions.AddressOf(b.bytes);
return UTF8ArrayUnsafeUtility.EqualsUTF8Bytes(aptr, alen, bptr, blen);
}
}
/// <summary>
/// Returns true if two strings are unequal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="a">A string to compare.</param>
/// <param name="b">Another string to compare.</param>
/// <returns>True if the two strings are unequal.</returns>
public static bool operator !=(in HeapString a, in FixedString<#=OTHERBYTES#>Bytes b)
{
return !(a == b);
}
/// <summary>
/// Returns true if this string and another are equal.
/// </summary>
/// <remarks>Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="other">Another string to compare with.</param>
/// <returns>True if the two strings are equal.</returns>
public bool Equals(FixedString<#=OTHERBYTES#>Bytes other)
{
return this == other;
}
<#
}
#>
/// <summary>
/// Returns a managed string copy of this string.
/// </summary>
/// <returns>A managed string copy of this string.</returns>>
[NotBurstCompatible]
public override String ToString()
{
if (!m_Data.IsCreated)
return "";
return this.ConvertToString();
}
/// <summary>
/// Returns a hash code of this string.
/// </summary>
/// <remarks>The hash code is an integer that is always the same for two equal strings but (very likely) different for two unequal strings.</remarks>
/// <returns>A hash code of this string.</returns>
public override int GetHashCode()
{
return this.ComputeHashCode();
}
/// <summary>
/// Returns true if this string and another object are equal.
/// </summary>
/// <remarks>For the object to be equal, it must itself be a managed string, HeapString, or FixedString*N*Bytes.
///
/// Two strings are equal if they have equal length and all their characters match.</remarks>
/// <param name="other">Another string to compare with.</param>
/// <returns>True if this string and the object are equal.</returns>
[NotBurstCompatible]
public override bool Equals(object other)
{
if(ReferenceEquals(null, other)) return false;
if(other is String aString) return Equals(aString);
if(other is HeapString aHeapString) return Equals(aHeapString);
<#
foreach(var OTHERBYTES in SIZES)
{
#>
if(other is FixedString<#=OTHERBYTES#>Bytes a<#=OTHERBYTES#>) return Equals(a<#=OTHERBYTES#>);
<#
}
#>
return false;
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
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 HeapString of {Length} length.");
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void ThrowCopyError(CopyError error, String source)
{
throw new ArgumentException($"HeapString: {error} while copying \"{source}\"");
}
}
<#}#>
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e910f3b476804d65b90652c09d991834
timeCreated: 1595536658

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3c4dac562d6e2714996a4d5ef5d9ba74
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
namespace Unity.Collections
{
/// <summary>
/// Extension methods for lists.
/// </summary>
public static class ListExtensions
{
/// <summary>
/// Finds and removes the first occurrence of a particular value in the list.
/// </summary>
/// <remarks>
/// If found, the first occurrence of the value is overwritten by the last element of the list, and the list's length is decremented by one.
/// </remarks>
/// <typeparam name="T">The type of elements in the list.</typeparam>
/// <param name="list">The list to search.</param>
/// <param name="value">The value to locate and remove.</param>
/// <returns>Returns true if an element was removed.</returns>
public static bool RemoveSwapBack<T>(this List<T> list, T value)
{
int index = list.IndexOf(value);
if (index < 0)
return false;
RemoveAtSwapBack(list, index);
return true;
}
/// <summary>
/// Finds and removes the first value which satisfies a predicate.
/// </summary>
/// <remarks>
/// The first value satisfying the predicate is overwritten by the last element of the list, and the list's length is decremented by one.
/// </remarks>
/// <typeparam name="T">The type of elements in the list.</typeparam>
/// <param name="list">The list to search.</param>
/// <param name="matcher">The predicate for testing the elements of the list.</param>
/// <returns>Returns true if an element was removed.</returns>
public static bool RemoveSwapBack<T>(this List<T> list, Predicate<T> matcher)
{
int index = list.FindIndex(matcher);
if (index < 0)
return false;
RemoveAtSwapBack(list, index);
return true;
}
/// <summary>
/// Removes the value at an index.
/// </summary>
/// <remarks>
/// The value at the index is overwritten by the last element of the list, and the list's length is decremented by one.
/// </remarks>
/// <typeparam name="T">The type of elements in the list.</typeparam>
/// <param name="list">The list to search.</param>
/// <param name="index">The index at which to remove an element from the list.</param>
public static void RemoveAtSwapBack<T>(this List<T> list, int index)
{
int lastIndex = list.Count - 1;
list[index] = list[lastIndex];
list.RemoveAt(lastIndex);
}
/// <summary>
/// Returns a copy of this list.
/// </summary>
/// <typeparam name="T">The type of elements in the list.</typeparam>
/// <param name="list">The list to copy.</param>
/// <param name="allocator">The allocator to use.</param>
/// <returns>A copy of this list.</returns>
public static NativeList<T> ToNativeList<T>(this List<T> list, AllocatorManager.AllocatorHandle allocator) where T : unmanaged
{
var container = new NativeList<T>(list.Count, allocator);
for (int i = 0; i < list.Count; i++)
{
container.AddNoResize(list[i]);
}
return container;
}
/// <summary>
/// Returns an array that is a copy of this list.
/// </summary>
/// <typeparam name="T">The type of elements in the list.</typeparam>
/// <param name="list">The list to copy.</param>
/// <param name="allocator">The allocator to use.</param>
/// <returns>An array that is a copy of this list.</returns>
public unsafe static NativeArray<T> ToNativeArray<T>(this List<T> list, AllocatorManager.AllocatorHandle allocator) where T : unmanaged
{
var container = CollectionHelper.CreateNativeArray<T>(list.Count, allocator);
for (int i = 0; i < list.Count; i++)
{
container[i] = list[i];
}
return container;
}
}
}

View file

@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 7a3be0dd158385f4081ab23532efbc6c
timeCreated: 1497958388
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,156 @@
using System;
using System.Diagnostics;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
using Unity.Jobs.LowLevel.Unsafe;
namespace Unity.Collections
{
[BurstCompatible]
unsafe internal struct Memory
{
internal const long k_MaximumRamSizeInBytes = 1L << 40; // a terabyte
[BurstCompatible]
internal struct Unmanaged
{
internal static void* Allocate(long size, int align, AllocatorManager.AllocatorHandle allocator)
{
return Array.Resize(null, 0, 1, allocator, size, align);
}
internal static void Free(void* pointer, AllocatorManager.AllocatorHandle allocator)
{
if (pointer == null)
return;
Array.Resize(pointer, 1, 0, allocator, 1, 1);
}
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
internal static T* Allocate<T>(AllocatorManager.AllocatorHandle allocator) where T : unmanaged
{
return Array.Resize<T>(null, 0, 1, allocator);
}
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
internal static void Free<T>(T* pointer, AllocatorManager.AllocatorHandle allocator) where T : unmanaged
{
if (pointer == null)
return;
Array.Resize(pointer, 1, 0, allocator);
}
[BurstCompatible]
internal struct Array
{
static bool IsCustom(AllocatorManager.AllocatorHandle allocator)
{
return (int) allocator.Index >= AllocatorManager.FirstUserIndex;
}
static void* CustomResize(void* oldPointer, long oldCount, long newCount, AllocatorManager.AllocatorHandle allocator, long size, int align)
{
AllocatorManager.Block block = default;
block.Range.Allocator = allocator;
block.Range.Items = (int)newCount;
block.Range.Pointer = (IntPtr)oldPointer;
block.BytesPerItem = (int)size;
block.Alignment = align;
block.AllocatedItems = (int)oldCount;
var error = AllocatorManager.Try(ref block);
AllocatorManager.CheckFailedToAllocate(error);
return (void*)block.Range.Pointer;
}
internal static void* Resize(void* oldPointer, long oldCount, long newCount, AllocatorManager.AllocatorHandle allocator,
long size, int align)
{
// Make the alignment multiple of cacheline size
var alignment = math.max(JobsUtility.CacheLineSize, align);
if (IsCustom(allocator))
return CustomResize(oldPointer, oldCount, newCount, allocator, size, alignment);
void* newPointer = default;
if (newCount > 0)
{
long bytesToAllocate = newCount * size;
CheckByteCountIsReasonable(bytesToAllocate);
newPointer = UnsafeUtility.Malloc(bytesToAllocate, alignment, allocator.ToAllocator);
if (oldCount > 0)
{
long count = math.min(oldCount, newCount);
long bytesToCopy = count * size;
CheckByteCountIsReasonable(bytesToCopy);
UnsafeUtility.MemCpy(newPointer, oldPointer, bytesToCopy);
}
}
if (oldCount > 0)
UnsafeUtility.Free(oldPointer, allocator.ToAllocator);
return newPointer;
}
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
internal static T* Resize<T>(T* oldPointer, long oldCount, long newCount, AllocatorManager.AllocatorHandle allocator) where T : unmanaged
{
return (T*)Resize((byte*)oldPointer, oldCount, newCount, allocator, UnsafeUtility.SizeOf<T>(), UnsafeUtility.AlignOf<T>());
}
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
internal static T* Allocate<T>(long count, AllocatorManager.AllocatorHandle allocator)
where T : unmanaged
{
return Resize<T>(null, 0, count, allocator);
}
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
internal static void Free<T>(T* pointer, long count, AllocatorManager.AllocatorHandle allocator)
where T : unmanaged
{
if (pointer == null)
return;
Resize(pointer, count, 0, allocator);
}
}
}
[BurstCompatible]
internal struct Array
{
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
internal static void Set<T>(T* pointer, long count, T t = default) where T : unmanaged
{
long bytesToSet = count * UnsafeUtility.SizeOf<T>();
CheckByteCountIsReasonable(bytesToSet);
for (var i = 0; i < count; ++i)
pointer[i] = t;
}
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
internal static void Clear<T>(T* pointer, long count) where T : unmanaged
{
long bytesToClear = count * UnsafeUtility.SizeOf<T>();
CheckByteCountIsReasonable(bytesToClear);
UnsafeUtility.MemClear(pointer, bytesToClear);
}
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
internal static void Copy<T>(T* dest, T* src, long count) where T : unmanaged
{
long bytesToCopy = count * UnsafeUtility.SizeOf<T>();
CheckByteCountIsReasonable(bytesToCopy);
UnsafeUtility.MemCpy(dest, src, bytesToCopy);
}
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
internal static void CheckByteCountIsReasonable(long size)
{
if (size < 0)
throw new InvalidOperationException("Attempted to operate on {size} bytes of memory: nonsensical");
if (size > k_MaximumRamSizeInBytes)
throw new InvalidOperationException("Attempted to operate on {size} bytes of memory: too big");
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: babbfc241a32b8241bf31319e1447eb2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,276 @@
using System;
using System.Diagnostics;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Burst;
using static Unity.Collections.AllocatorManager;
namespace Unity.Collections
{
/// <summary>
/// Extension methods for NativeArray.
/// </summary>
[BurstCompatible]
public unsafe static class NativeArrayExtensions
{
public struct NativeArrayStaticId<T>
where T : struct
{
internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<NativeArray<T>>();
}
/// <summary>
/// Returns true if a particular value is present in this array.
/// </summary>
/// <typeparam name="T">The type of elements in this array.</typeparam>
/// <typeparam name="U">The value type.</typeparam>
/// <param name="array">The array to search.</param>
/// <param name="value">The value to locate.</param>
/// <returns>True if the value is present in this array.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public static bool Contains<T, U>(this NativeArray<T> array, U value) where T : struct, IEquatable<U>
{
return IndexOf<T, U>(array.GetUnsafeReadOnlyPtr(), array.Length, value) != -1;
}
/// <summary>
/// Finds the index of the first occurrence of a particular value in this array.
/// </summary>
/// <typeparam name="T">The type of elements in this array.</typeparam>
/// <typeparam name="U">The value type.</typeparam>
/// <param name="array">The array to search.</param>
/// <param name="value">The value to locate.</param>
/// <returns>The index of the first occurrence of the value in this array. Returns -1 if no occurrence is found.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public static int IndexOf<T, U>(this NativeArray<T> array, U value) where T : struct, IEquatable<U>
{
return IndexOf<T, U>(array.GetUnsafeReadOnlyPtr(), array.Length, value);
}
/// <summary>
/// Returns true if a particular value is present in this array.
/// </summary>
/// <typeparam name="T">The type of elements in this array.</typeparam>
/// <typeparam name="U">The value type.</typeparam>
/// <param name="array">The array to search.</param>
/// <param name="value">The value to locate.</param>
/// <returns>True if the value is present in this array.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public static bool Contains<T, U>(this NativeArray<T>.ReadOnly array, U value) where T : struct, IEquatable<U>
{
return IndexOf<T, U>(array.m_Buffer, array.m_Length, value) != -1;
}
/// <summary>
/// Finds the index of the first occurrence of a particular value in this array.
/// </summary>
/// <typeparam name="T">The type of elements in this array.</typeparam>
/// <typeparam name="U">The type of value to locate.</typeparam>
/// <param name="array">The array to search.</param>
/// <param name="value">The value to locate.</param>
/// <returns>The index of the first occurrence of the value in this array. Returns -1 if no occurrence is found.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public static int IndexOf<T, U>(this NativeArray<T>.ReadOnly array, U value) where T : struct, IEquatable<U>
{
return IndexOf<T, U>(array.m_Buffer, array.m_Length, value);
}
/// <summary>
/// Returns true if a particular value is present in this list.
/// </summary>
/// <typeparam name="T">The type of elements in this list.</typeparam>
/// <typeparam name="U">The value type.</typeparam>
/// <param name="list">The list to search.</param>
/// <param name="value">The value to locate.</param>
/// <returns>True if the value is present in this list.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public static bool Contains<T, U>(this NativeList<T> list, U value) where T : unmanaged, IEquatable<U>
{
return IndexOf<T, U>(list.GetUnsafeReadOnlyPtr(), list.Length, value) != -1;
}
/// <summary>
/// Finds the index of the first occurrence of a particular value in this list.
/// </summary>
/// <typeparam name="T">The type of elements in the list.</typeparam>
/// <typeparam name="U">The value type.</typeparam>
/// <param name="list">The list to search.</param>
/// <param name="value">The value to locate.</param>
/// <returns>The index of the first occurrence of the value in this list. Returns -1 if no occurrence is found.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public static int IndexOf<T, U>(this NativeList<T> list, U value) where T : unmanaged, IEquatable<U>
{
return IndexOf<T, U>(list.GetUnsafeReadOnlyPtr(), list.Length, value);
}
/// <summary>
/// Returns true if a particular value is present in a buffer.
/// </summary>
/// <typeparam name="T">The type of elements in the buffer.</typeparam>
/// <typeparam name="U">The value type.</typeparam>
/// <param name="ptr">The buffer.</param>
/// <param name="length">Number of elements in the buffer.</param>
/// <param name="value">The value to locate.</param>
/// <returns>True if the value is present in the buffer.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public static bool Contains<T, U>(void* ptr, int length, U value) where T : struct, IEquatable<U>
{
return IndexOf<T, U>(ptr, length, value) != -1;
}
/// <summary>
/// Finds the index of the first occurrence of a particular value in a buffer.
/// </summary>
/// <typeparam name="T">The type of elements in the buffer.</typeparam>
/// <typeparam name="U">The value type.</typeparam>
/// <param name="ptr">A buffer.</param>
/// <param name="length">Number of elements in the buffer.</param>
/// <param name="value">The value to locate.</param>
/// <returns>The index of the first occurrence of the value in the buffer. Returns -1 if no occurrence is found.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public static int IndexOf<T, U>(void* ptr, int length, U value) where T : struct, IEquatable<U>
{
for (int i = 0; i != length; i++)
{
if (UnsafeUtility.ReadArrayElement<T>(ptr, i).Equals(value))
return i;
}
return -1;
}
/// <summary>
/// Returns the reinterpretation of this array into another kind of NativeArray.
/// See [Array reinterpretation](https://docs.unity3d.com/Packages/com.unity.collections@latest?subfolder=/manual/allocation.html#array-reinterpretation).
/// </summary>
/// <param name="array">The array to reinterpret.</param>
/// <typeparam name="T">Type of elements in the array.</typeparam>
/// <typeparam name="U">Type of elements in the reinterpreted array.</typeparam>
/// <returns>The reinterpretation of this array into another kind of NativeArray.</returns>
/// <exception cref="InvalidOperationException">Thrown if this array's capacity cannot be evenly divided by `sizeof(U)`.</exception>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public static NativeArray<U> Reinterpret<T, U>(this NativeArray<T> array) where U : struct where T : struct
{
var tSize = UnsafeUtility.SizeOf<T>();
var uSize = UnsafeUtility.SizeOf<U>();
var byteLen = ((long)array.Length) * tSize;
var uLen = byteLen / uSize;
CheckReinterpretSize<T, U>(ref array);
var ptr = NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(array);
var result = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<U>(ptr, (int)uLen, Allocator.None);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
var handle = NativeArrayUnsafeUtility.GetAtomicSafetyHandle(array);
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref result, handle);
#endif
return result;
}
/// <summary>
/// Returns true if this array and another have equal length and content.
/// </summary>
/// <typeparam name="T">The type of the source array's elements.</typeparam>
/// <param name="array">The array to compare for equality.</param>
/// <param name="other">The other array to compare for equality.</param>
/// <returns>True if the arrays have equal length and content.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public static bool ArraysEqual<T>(this NativeArray<T> array, NativeArray<T> other) where T : struct, IEquatable<T>
{
if (array.Length != other.Length)
return false;
for (int i = 0; i != array.Length; i++)
{
if (!array[i].Equals(other[i]))
return false;
}
return true;
}
/// <summary>
/// Returns true if this array and another have equal length and content.
/// </summary>
/// <typeparam name="T">The type of the source array's elements.</typeparam>
/// <param name="array">The array to compare for equality.</param>
/// <param name="other">The other array to compare for equality.</param>
/// <returns>True if the arrays have equal length and content.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public static bool ArraysEqual<T>(this NativeList<T> array, NativeArray<T> other) where T : unmanaged, IEquatable<T>
{
return ArraysEqual(array.AsArray(), other);
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
static void CheckReinterpretSize<T, U>(ref NativeArray<T> array) where U : struct where T : struct
{
var tSize = UnsafeUtility.SizeOf<T>();
var uSize = UnsafeUtility.SizeOf<U>();
var byteLen = ((long)array.Length) * tSize;
var uLen = byteLen / uSize;
if (uLen * uSize != byteLen)
{
throw new InvalidOperationException($"Types {typeof(T)} (array length {array.Length}) and {typeof(U)} cannot be aliased due to size constraints. The size of the types and lengths involved must line up.");
}
}
[BurstCompatible(GenericTypeArguments = new[] { typeof(int) })]
internal static void Initialize<T>(ref this NativeArray<T> array,
int length,
AllocatorManager.AllocatorHandle allocator,
NativeArrayOptions options = NativeArrayOptions.UninitializedMemory)
where T : struct
{
AllocatorHandle handle = allocator;
array.m_Buffer = handle.AllocateStruct(default(T), length);
array.m_Length = length;
array.m_AllocatorLabel = Allocator.None;
if (options == NativeArrayOptions.ClearMemory)
{
UnsafeUtility.MemClear(array.m_Buffer, array.m_Length * UnsafeUtility.SizeOf<T>());
}
#if ENABLE_UNITY_COLLECTIONS_CHECKS
array.m_MinIndex = 0;
array.m_MaxIndex = length - 1;
DisposeSentinel.Create(out array.m_Safety, out array.m_DisposeSentinel, 1, handle.ToAllocator);
DisposeSentinel.Clear(ref array.m_DisposeSentinel);
CollectionHelper.SetStaticSafetyId<NativeArray<T>>(ref array.m_Safety, ref NativeArrayStaticId<T>.s_staticSafetyId.Data);
handle.AddSafetyHandle(array.m_Safety);
#endif
}
[BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(AllocatorManager.AllocatorHandle) })]
internal static void Initialize<T, U>(ref this NativeArray<T> array,
int length,
ref U allocator,
NativeArrayOptions options = NativeArrayOptions.ClearMemory)
where T : struct
where U : unmanaged, AllocatorManager.IAllocator
{
array.m_Buffer = allocator.AllocateStruct(default(T), length);
array.m_Length = length;
array.m_AllocatorLabel = Allocator.None;
if (options == NativeArrayOptions.ClearMemory)
{
UnsafeUtility.MemClear(array.m_Buffer, array.m_Length * UnsafeUtility.SizeOf<T>());
}
#if ENABLE_UNITY_COLLECTIONS_CHECKS
array.m_MinIndex = 0;
array.m_MaxIndex = length - 1;
DisposeSentinel.Create(out array.m_Safety, out array.m_DisposeSentinel, 1, allocator.ToAllocator);
DisposeSentinel.Clear(ref array.m_DisposeSentinel);
CollectionHelper.SetStaticSafetyId<NativeArray<T>>(ref array.m_Safety, ref NativeArrayStaticId<T>.s_staticSafetyId.Data);
allocator.Handle.AddSafetyHandle(array.m_Safety);
#endif
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 41cc650015014737ae58d20e77323080
timeCreated: 1515914094

View file

@ -0,0 +1,448 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
namespace Unity.Collections
{
/// <summary>
/// An arbitrarily-sized array of bits.
/// </summary>
/// <remarks>
/// The number of allocated bytes is always a multiple of 8. For example, a 65-bit array could fit in 9 bytes, but its allocation is actually 16 bytes.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
[NativeContainer]
[DebuggerDisplay("Length = {Length}, IsCreated = {IsCreated}")]
[BurstCompatible]
public unsafe struct NativeBitArray
: INativeDisposable
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
internal AtomicSafetyHandle m_Safety;
static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<NativeBitArray>();
#if REMOVE_DISPOSE_SENTINEL
#else
[NativeSetClassTypeToNullOnSchedule]
DisposeSentinel m_DisposeSentinel;
#endif
#endif
[NativeDisableUnsafePtrRestriction]
internal UnsafeBitArray m_BitArray;
/// <summary>
/// Initializes and returns an instance of NativeBitArray.
/// </summary>
/// <param name="numBits">The number of bits.</param>
/// <param name="allocator">The allocator to use.</param>
/// <param name="options">Whether newly allocated bytes should be zeroed out.</param>
public NativeBitArray(int numBits, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory)
: this(numBits, allocator, options, 2)
{
}
NativeBitArray(int numBits, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options, int disposeSentinelStackDepth)
{
CollectionHelper.CheckAllocator(allocator);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
m_Safety = CollectionHelper.CreateSafetyHandle(allocator);
#else
if (allocator.IsCustomAllocator)
{
m_Safety = AtomicSafetyHandle.Create();
m_DisposeSentinel = null;
}
else
{
DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, disposeSentinelStackDepth, allocator.ToAllocator);
}
#endif
CollectionHelper.SetStaticSafetyId(ref m_Safety, ref s_staticSafetyId.Data, "Unity.Collections.NativeBitArray");
#endif
m_BitArray = new UnsafeBitArray(numBits, allocator, options);
}
/// <summary>
/// Whether this array has been allocated (and not yet deallocated).
/// </summary>
/// <value>True if this array has been allocated (and not yet deallocated).</value>
public bool IsCreated => m_BitArray.IsCreated;
/// <summary>
/// Releases all resources (memory and safety handles).
/// </summary>
public void Dispose()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
CollectionHelper.DisposeSafetyHandle(ref m_Safety);
#else
DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel);
#endif
#endif
m_BitArray.Dispose();
}
/// <summary>
/// Creates and schedules a job that will dispose this array.
/// </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 array. The new job depends upon inputDeps.</returns>
[NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
public JobHandle Dispose(JobHandle inputDeps)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
#else
// [DeallocateOnJobCompletion] is not supported, but we want the deallocation
// to happen in a thread. DisposeSentinel needs to be cleared on main thread.
// AtomicSafetyHandle can be destroyed after the job was scheduled (Job scheduling
// will check that no jobs are writing to the container).
DisposeSentinel.Clear(ref m_DisposeSentinel);
#endif
#endif
var jobHandle = m_BitArray.Dispose(inputDeps);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.Release(m_Safety);
#endif
return jobHandle;
}
/// <summary>
/// Returns the number of bits.
/// </summary>
/// <value>The number of bits.</value>
public int Length
{
get
{
CheckRead();
return CollectionHelper.AssumePositive(m_BitArray.Length);
}
}
/// <summary>
/// Sets all the bits to 0.
/// </summary>
public void Clear()
{
CheckWrite();
m_BitArray.Clear();
}
/// <summary>
/// Returns a native array that aliases the content of this array.
/// </summary>
/// <typeparam name="T">The type of elements in the aliased array.</typeparam>
/// <exception cref="InvalidOperationException">Thrown if the number of bits in this array
/// is not evenly divisible by the size of T in bits (`sizeof(T) * 8`).</exception>
/// <returns>A native array that aliases the content of this array.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public NativeArray<T> AsNativeArray<T>() where T : unmanaged
{
CheckReadBounds<T>();
var bitsPerElement = UnsafeUtility.SizeOf<T>() * 8;
var length = m_BitArray.Length / bitsPerElement;
var array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(m_BitArray.Ptr, length, Allocator.None);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.UseSecondaryVersion(ref m_Safety);
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, m_Safety);
#endif
return array;
}
/// <summary>
/// Sets the bit at an index to 0 or 1.
/// </summary>
/// <param name="pos">Index of the bit to set.</param>
/// <param name="value">True for 1, false for 0.</param>
public void Set(int pos, bool value)
{
CheckWrite();
m_BitArray.Set(pos, value);
}
/// <summary>
/// Sets a range of bits to 0 or 1.
/// </summary>
/// <remarks>
/// The range runs from index `pos` up to (but not including) `pos + numBits`.
/// No exception is thrown if `pos + numBits` exceeds the length.
/// </remarks>
/// <param name="pos">Index of the first bit to set.</param>
/// <param name="value">True for 1, false for 0.</param>
/// <param name="numBits">Number of bits to set.</param>
/// <exception cref="ArgumentException">Thrown if pos is out of bounds or if numBits is less than 1.</exception>
public void SetBits(int pos, bool value, int numBits)
{
CheckWrite();
m_BitArray.SetBits(pos, value, numBits);
}
/// <summary>
/// Copies bits of a ulong to bits in this array.
/// </summary>
/// <remarks>
/// The destination bits in this array run from index pos up to (but not including) `pos + numBits`.
/// No exception is thrown if `pos + numBits` exceeds the length.
///
/// The lowest bit of the ulong is copied to the first destination bit; the second-lowest bit of the ulong is
/// copied to the second destination bit; and so forth.
/// </remarks>
/// <param name="pos">Index of the first bit to set.</param>
/// <param name="value">Unsigned long from which to copy bits.</param>
/// <param name="numBits">Number of bits to set (must be between 1 and 64).</param>
/// <exception cref="ArgumentException">Thrown if pos is out of bounds or if numBits is not between 1 and 64.</exception>
public void SetBits(int pos, ulong value, int numBits = 1)
{
CheckWrite();
m_BitArray.SetBits(pos, value, numBits);
}
/// <summary>
/// Returns a ulong which has bits copied from this array.
/// </summary>
/// <remarks>
/// The source bits in this array run from index pos up to (but not including) `pos + numBits`.
/// No exception is thrown if `pos + numBits` exceeds the length.
///
/// The first source bit is copied to the lowest bit of the ulong; the second source bit is copied to the second-lowest bit of the ulong; and so forth. Any remaining bits in the ulong will be 0.
/// </remarks>
/// <param name="pos">Index of the first bit to get.</param>
/// <param name="numBits">Number of bits to get (must be between 1 and 64).</param>
/// <exception cref="ArgumentException">Thrown if pos is out of bounds or if numBits is not between 1 and 64.</exception>
/// <returns>A ulong which has bits copied from this array.</returns>
public ulong GetBits(int pos, int numBits = 1)
{
CheckRead();
return m_BitArray.GetBits(pos, numBits);
}
/// <summary>
/// Returns true if the bit at an index is 1.
/// </summary>
/// <param name="pos">Index of the bit to test.</param>
/// <returns>True if the bit at the index is 1.</returns>
/// <exception cref="ArgumentException">Thrown if `pos` is out of bounds.</exception>
public bool IsSet(int pos)
{
CheckRead();
return m_BitArray.IsSet(pos);
}
/// <summary>
/// Copies a range of bits from this array to another range in this array.
/// </summary>
/// <remarks>
/// The bits to copy run from index `srcPos` up to (but not including) `srcPos + numBits`.
/// The bits to set run from index `dstPos` up to (but not including) `dstPos + numBits`.
///
/// The ranges may overlap, but the result in the overlapping region is undefined.
/// </remarks>
/// <param name="dstPos">Index of the first bit to set.</param>
/// <param name="srcPos">Index of the first bit to copy.</param>
/// <param name="numBits">Number of bits to copy.</param>
/// <exception cref="ArgumentException">Thrown if either `dstPos + numBits` or `srcPos + numBits` exceed the length of this array.</exception>
public void Copy(int dstPos, int srcPos, int numBits)
{
CheckWrite();
m_BitArray.Copy(dstPos, srcPos, numBits);
}
/// <summary>
/// Copies a range of bits from an array to a range of bits in this array.
/// </summary>
/// <remarks>
/// The bits to copy in the source array run from index srcPos up to (but not including) `srcPos + numBits`.
/// The bits to set in the destination array run from index dstPos up to (but not including) `dstPos + numBits`.
///
/// When the source and destination are the same array, the ranges may still overlap, but the result in the overlapping region is undefined.
/// </remarks>
/// <param name="dstPos">Index of the first bit to set.</param>
/// <param name="srcBitArray">The source array.</param>
/// <param name="srcPos">Index of the first bit to copy.</param>
/// <param name="numBits">The number of bits to copy.</param>
/// <exception cref="ArgumentException">Thrown if either `dstPos + numBits` or `srcBitArray + numBits` exceed the length of this array.</exception>
public void Copy(int dstPos, ref NativeBitArray srcBitArray, int srcPos, int numBits)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(srcBitArray.m_Safety);
#endif
CheckWrite();
m_BitArray.Copy(dstPos, ref srcBitArray.m_BitArray, srcPos, numBits);
}
/// <summary>
/// Finds the first length-*N* contiguous sequence of 0 bits in this bit array.
/// </summary>
/// <param name="pos">Index at which to start searching.</param>
/// <param name="numBits">Number of contiguous 0 bits to look for.</param>
/// <returns>The index in this array where the sequence is found. The index will be greater than or equal to `pos`.
/// Returns -1 if no occurrence is found.</returns>
public int Find(int pos, int numBits)
{
CheckRead();
return m_BitArray.Find(pos, numBits);
}
/// <summary>
/// Finds the first length-*N* contiguous sequence of 0 bits in this bit array. Searches only a subsection.
/// </summary>
/// <param name="pos">Index at which to start searching.</param>
/// <param name="numBits">Number of contiguous 0 bits to look for.</param>
/// <param name="count">Number of bits to search.</param>
/// <returns>The index in this array where the sequence is found. The index will be greater than or equal to `pos` but less than `pos + count`.
/// Returns -1 if no occurrence is found.</returns>
public int Find(int pos, int count, int numBits)
{
CheckRead();
return m_BitArray.Find(pos, count, numBits);
}
/// <summary>
/// Returns true if none of the bits in a range are 1 (*i.e.* all bits in the range are 0).
/// </summary>
/// <param name="pos">Index of the bit at which to start searching.</param>
/// <param name="numBits">Number of bits to test. Defaults to 1.</param>
/// <returns>Returns true if none of the bits in range `pos` up to (but not including) `pos + numBits` are 1.</returns>
/// <exception cref="ArgumentException">Thrown if `pos` is out of bounds or `numBits` is less than 1.</exception>
public bool TestNone(int pos, int numBits = 1)
{
CheckRead();
return m_BitArray.TestNone(pos, numBits);
}
/// <summary>
/// Returns true if at least one of the bits in a range is 1.
/// </summary>
/// <param name="pos">Index of the bit at which to start searching.</param>
/// <param name="numBits">Number of bits to test. Defaults to 1.</param>
/// <returns>True if one ore more of the bits in range `pos` up to (but not including) `pos + numBits` are 1.</returns>
/// <exception cref="ArgumentException">Thrown if `pos` is out of bounds or `numBits` is less than 1.</exception>
public bool TestAny(int pos, int numBits = 1)
{
CheckRead();
return m_BitArray.TestAny(pos, numBits);
}
/// <summary>
/// Returns true if all of the bits in a range are 1.
/// </summary>
/// <param name="pos">Index of the bit at which to start searching.</param>
/// <param name="numBits">Number of bits to test. Defaults to 1.</param>
/// <returns>True if all of the bits in range `pos` up to (but not including) `pos + numBits` are 1.</returns>
/// <exception cref="ArgumentException">Thrown if `pos` is out of bounds or `numBits` is less than 1.</exception>
public bool TestAll(int pos, int numBits = 1)
{
CheckRead();
return m_BitArray.TestAll(pos, numBits);
}
/// <summary>
/// Returns the number of bits in a range that are 1.
/// </summary>
/// <param name="pos">Index of the bit at which to start searching.</param>
/// <param name="numBits">Number of bits to test. Defaults to 1.</param>
/// <returns>The number of bits in a range of bits that are 1.</returns>
/// <exception cref="ArgumentException">Thrown if `pos` is out of bounds or `numBits` is less than 1.</exception>
public int CountBits(int pos, int numBits = 1)
{
CheckRead();
return m_BitArray.CountBits(pos, numBits);
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckRead()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckReadBounds<T>() where T : unmanaged
{
CheckRead();
var bitsPerElement = UnsafeUtility.SizeOf<T>() * 8;
var length = m_BitArray.Length / bitsPerElement;
if (length == 0)
{
throw new InvalidOperationException($"Number of bits in the NativeBitArray {m_BitArray.Length} is not sufficient to cast to NativeArray<T> {UnsafeUtility.SizeOf<T>() * 8}.");
}
else if (m_BitArray.Length != bitsPerElement* length)
{
throw new InvalidOperationException($"Number of bits in the NativeBitArray {m_BitArray.Length} couldn't hold multiple of T {UnsafeUtility.SizeOf<T>()}. Output array would be truncated.");
}
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckWrite()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
#endif
}
}
}
namespace Unity.Collections.LowLevel.Unsafe
{
/// <summary>
/// Unsafe helper methods for NativeBitArray.
/// </summary>
[BurstCompatible]
public static class NativeBitArrayUnsafeUtility
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
/// <summary>
/// Returns an array's atomic safety handle.
/// </summary>
/// <param name="container">Array from which to get an AtomicSafetyHandle.</param>
/// <returns>This array's atomic safety handle.</returns>
[BurstCompatible(RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS", CompileTarget = BurstCompatibleAttribute.BurstCompatibleCompileTarget.Editor)]
public static AtomicSafetyHandle GetAtomicSafetyHandle(in NativeBitArray container)
{
return container.m_Safety;
}
/// <summary>
/// Sets an array's atomic safety handle.
/// </summary>
/// <param name="container">Array which the AtomicSafetyHandle is for.</param>
/// <param name="safety">Atomic safety handle for this array.</param>
[BurstCompatible(RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS", CompileTarget = BurstCompatibleAttribute.BurstCompatibleCompileTarget.Editor)]
public static void SetAtomicSafetyHandle(ref NativeBitArray container, AtomicSafetyHandle safety)
{
container.m_Safety = safety;
}
#endif
/// <summary>
/// Returns a bit array with content aliasing a buffer.
/// </summary>
/// <param name="ptr">A buffer.</param>
/// <param name="sizeInBytes">Size of the buffer in bytes. Must be a multiple of 8.</param>
/// <param name="allocator">The allocator that was used to create the buffer.</param>
/// <returns>A bit array with content aliasing a buffer.</returns>
public static unsafe NativeBitArray ConvertExistingDataToNativeBitArray(void* ptr, int sizeInBytes, AllocatorManager.AllocatorHandle allocator)
{
return new NativeBitArray
{
m_BitArray = new UnsafeBitArray(ptr, sizeInBytes, allocator),
};
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e4640e45632730349a32c5a1365847e6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,604 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
namespace Unity.Collections
{
/// <summary>
/// The keys and values of a hash map copied into two parallel arrays.
/// </summary>
/// <remarks>For each key-value pair copied from the hash map, the key is stored in `Keys[i]` while the value is stored in `Values[i]` (for the same `i`).
///
/// NativeKeyValueArrays is not actually itself a native collection: it contains a NativeArray for the keys and a NativeArray for the values,
/// but a NativeKeyValueArrays does not have its own safety handles.</remarks>
/// <typeparam name="TKey">The type of the keys.</typeparam>
/// <typeparam name="TValue">The type of the values.</typeparam>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public struct NativeKeyValueArrays<TKey, TValue>
: INativeDisposable
where TKey : struct
where TValue : struct
{
/// <summary>
/// The keys.
/// </summary>
/// <value>The keys. The key at `Keys[i]` is paired with the value at `Values[i]`.</value>
public NativeArray<TKey> Keys;
/// <summary>
/// The values.
/// </summary>
/// <value>The values. The value at `Values[i]` is paired with the key at `Keys[i]`.</value>
public NativeArray<TValue> Values;
/// <summary>
/// The number of key-value pairs.
/// </summary>
/// <value>The number of key-value pairs.</value>
public int Length => Keys.Length;
/// <summary>
/// Initializes and returns an instance of NativeKeyValueArrays.
/// </summary>
/// <param name="length">The number of keys-value pairs.</param>
/// <param name="allocator">The allocator to use.</param>
/// <param name="options">Whether newly allocated bytes should be zeroed out.</param>
public NativeKeyValueArrays(int length, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options)
{
Keys = CollectionHelper.CreateNativeArray<TKey>(length, allocator, options);
Values = CollectionHelper.CreateNativeArray<TValue>(length, allocator, options);
}
/// <summary>
/// Releases all resources (memory and safety handles).
/// </summary>
public void Dispose()
{
Keys.Dispose();
Values.Dispose();
}
/// <summary>
/// Creates and schedules a job that will dispose this collection's key and value arrays.
/// </summary>
/// <param name="inputDeps">A job handle. The newly scheduled job will depend upon this handle.</param>
/// <returns>The handle of a new job that will dispose this collection's key and value arrays.</returns>
[NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
public JobHandle Dispose(JobHandle inputDeps)
{
return Keys.Dispose(Values.Dispose(inputDeps));
}
}
/// <summary>
/// An unordered, expandable associative array.
/// </summary>
/// <typeparam name="TKey">The type of the keys.</typeparam>
/// <typeparam name="TValue">The type of the values.</typeparam>
[StructLayout(LayoutKind.Sequential)]
[NativeContainer]
[DebuggerDisplay("Count = {m_HashMapData.Count()}, Capacity = {m_HashMapData.Capacity}, IsCreated = {m_HashMapData.IsCreated}, IsEmpty = {IsEmpty}")]
[DebuggerTypeProxy(typeof(NativeHashMapDebuggerTypeProxy<,>))]
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public unsafe struct NativeHashMap<TKey, TValue>
: INativeDisposable
, IEnumerable<KeyValue<TKey, TValue>> // Used by collection initializers.
where TKey : struct, IEquatable<TKey>
where TValue : struct
{
internal UnsafeHashMap<TKey, TValue> m_HashMapData;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
internal AtomicSafetyHandle m_Safety;
static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<NativeHashMap<TKey, TValue>>();
#if REMOVE_DISPOSE_SENTINEL
#else
[NativeSetClassTypeToNullOnSchedule]
DisposeSentinel m_DisposeSentinel;
#endif
#endif
/// <summary>
/// Initializes and returns an instance of NativeHashMap.
/// </summary>
/// <param name="capacity">The number of key-value pairs that should fit in the initial allocation.</param>
/// <param name="allocator">The allocator to use.</param>
public NativeHashMap(int capacity, AllocatorManager.AllocatorHandle allocator)
: this(capacity, allocator, 2)
{
}
NativeHashMap(int capacity, AllocatorManager.AllocatorHandle allocator, int disposeSentinelStackDepth)
{
m_HashMapData = new UnsafeHashMap<TKey, TValue>(capacity, allocator);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
m_Safety = CollectionHelper.CreateSafetyHandle(allocator);
#else
if (AllocatorManager.IsCustomAllocator(allocator.ToAllocator))
{
m_Safety = AtomicSafetyHandle.Create();
m_DisposeSentinel = null;
}
else
{
DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, disposeSentinelStackDepth, allocator.ToAllocator);
}
#endif
CollectionHelper.SetStaticSafetyId<NativeHashMap<TKey, TValue>>(ref m_Safety, ref s_staticSafetyId.Data);
AtomicSafetyHandle.SetBumpSecondaryVersionOnScheduleWrite(m_Safety, true);
#endif
}
/// <summary>
/// Whether this hash map is empty.
/// </summary>
/// <value>True if this hash map is empty or if the map has not been constructed.</value>
public bool IsEmpty
{
get
{
if (!IsCreated)
{
return true;
}
CheckRead();
return m_HashMapData.IsEmpty;
}
}
/// <summary>
/// The current number of key-value pairs in this hash map.
/// </summary>
/// <returns>The current number of key-value pairs in this hash map.</returns>
public int Count()
{
CheckRead();
return m_HashMapData.Count();
}
/// <summary>
/// The number of key-value pairs that fit in the current allocation.
/// </summary>
/// <value>The number of key-value pairs that fit in the current allocation.</value>
/// <param name="value">A new capacity. Must be larger than the current capacity.</param>
/// <exception cref="Exception">Thrown if `value` is less than the current capacity.</exception>
public int Capacity
{
get
{
CheckRead();
return m_HashMapData.Capacity;
}
set
{
CheckWrite();
m_HashMapData.Capacity = value;
}
}
/// <summary>
/// Removes all key-value pairs.
/// </summary>
/// <remarks>Does not change the capacity.</remarks>
public void Clear()
{
CheckWrite();
m_HashMapData.Clear();
}
/// <summary>
/// Adds a new key-value pair.
/// </summary>
/// <remarks>If the key is already present, this method returns false without modifying the hash map.</remarks>
/// <param name="key">The key to add.</param>
/// <param name="item">The value to add.</param>
/// <returns>True if the key-value pair was added.</returns>
public bool TryAdd(TKey key, TValue item)
{
CheckWrite();
return m_HashMapData.TryAdd(key, item);
}
/// <summary>
/// Adds a new key-value pair.
/// </summary>
/// <remarks>If the key is already present, this method throws without modifying the hash map.</remarks>
/// <param name="key">The key to add.</param>
/// <param name="item">The value to add.</param>
/// <exception cref="ArgumentException">Thrown if the key was already present.</exception>
public void Add(TKey key, TValue item)
{
var added = TryAdd(key, item);
if (!added)
{
ThrowKeyAlreadyAdded(key);
}
}
/// <summary>
/// Removes a key-value pair.
/// </summary>
/// <param name="key">The key to remove.</param>
/// <returns>True if a key-value pair was removed.</returns>
public bool Remove(TKey key)
{
CheckWrite();
return m_HashMapData.Remove(key);
}
/// <summary>
/// Returns the value associated with a key.
/// </summary>
/// <param name="key">The key to look up.</param>
/// <param name="item">Outputs the value associated with the key. Outputs default if the key was not present.</param>
/// <returns>True if the key was present.</returns>
public bool TryGetValue(TKey key, out TValue item)
{
CheckRead();
return m_HashMapData.TryGetValue(key, out item);
}
/// <summary>
/// Returns true if a given key is present in this hash map.
/// </summary>
/// <param name="key">The key to look up.</param>
/// <returns>True if the key was present.</returns>
public bool ContainsKey(TKey key)
{
CheckRead();
return m_HashMapData.ContainsKey(key);
}
/// <summary>
/// Gets and sets values by key.
/// </summary>
/// <remarks>Getting a key that is not present will throw. Setting a key that is not already present will add the key.</remarks>
/// <param name="key">The key to look up.</param>
/// <value>The value associated with the key.</value>
/// <exception cref="ArgumentException">For getting, thrown if the key was not present.</exception>
public TValue this[TKey key]
{
get
{
CheckRead();
TValue res;
if (m_HashMapData.TryGetValue(key, out res))
{
return res;
}
ThrowKeyNotPresent(key);
return default;
}
set
{
CheckWrite();
m_HashMapData[key] = value;
}
}
/// <summary>
/// Whether this hash map has been allocated (and not yet deallocated).
/// </summary>
/// <value>True if this hash map has been allocated (and not yet deallocated).</value>
public bool IsCreated => m_HashMapData.IsCreated;
/// <summary>
/// Releases all resources (memory and safety handles).
/// </summary>
public void Dispose()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
CollectionHelper.DisposeSafetyHandle(ref m_Safety);
#else
DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel);
#endif
#endif
m_HashMapData.Dispose();
}
/// <summary>
/// Creates and schedules a job that will dispose this hash map.
/// </summary>
/// <param name="inputDeps">A job handle. The newly scheduled job will depend upon this handle.</param>
/// <returns>The handle of a new job that will dispose this hash map.</returns>
[NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
public JobHandle Dispose(JobHandle inputDeps)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
#else
// [DeallocateOnJobCompletion] is not supported, but we want the deallocation
// to happen in a thread. DisposeSentinel needs to be cleared on main thread.
// AtomicSafetyHandle can be destroyed after the job was scheduled (Job scheduling
// will check that no jobs are writing to the container).
DisposeSentinel.Clear(ref m_DisposeSentinel);
#endif
var jobHandle = new UnsafeHashMapDataDisposeJob { Data = new UnsafeHashMapDataDispose { m_Buffer = m_HashMapData.m_Buffer, m_AllocatorLabel = m_HashMapData.m_AllocatorLabel, m_Safety = m_Safety } }.Schedule(inputDeps);
AtomicSafetyHandle.Release(m_Safety);
#else
var jobHandle = new UnsafeHashMapDataDisposeJob { Data = new UnsafeHashMapDataDispose { m_Buffer = m_HashMapData.m_Buffer, m_AllocatorLabel = m_HashMapData.m_AllocatorLabel } }.Schedule(inputDeps);
#endif
m_HashMapData.m_Buffer = null;
return jobHandle;
}
/// <summary>
/// Returns an array with a copy of all this hash map's keys (in no particular order).
/// </summary>
/// <param name="allocator">The allocator to use.</param>
/// <returns>An array with a copy of all this hash map's keys (in no particular order).</returns>
public NativeArray<TKey> GetKeyArray(AllocatorManager.AllocatorHandle allocator)
{
CheckRead();
return m_HashMapData.GetKeyArray(allocator);
}
/// <summary>
/// Returns an array with a copy of all this hash map's values (in no particular order).
/// </summary>
/// <param name="allocator">The allocator to use.</param>
/// <returns>An array with a copy of all this hash map's values (in no particular order).</returns>
public NativeArray<TValue> GetValueArray(AllocatorManager.AllocatorHandle allocator)
{
CheckRead();
return m_HashMapData.GetValueArray(allocator);
}
/// <summary>
/// Returns a NativeKeyValueArrays with a copy of all this hash map's keys and values.
/// </summary>
/// <remarks>The key-value pairs are copied in no particular order. For all `i`, `Values[i]` will be the value associated with `Keys[i]`.</remarks>
/// <param name="allocator">The allocator to use.</param>
/// <returns>A NativeKeyValueArrays with a copy of all this hash map's keys and values.</returns>
public NativeKeyValueArrays<TKey, TValue> GetKeyValueArrays(AllocatorManager.AllocatorHandle allocator)
{
CheckRead();
return m_HashMapData.GetKeyValueArrays(allocator);
}
/// <summary>
/// Returns a parallel writer for this hash map.
/// </summary>
/// <returns>A parallel writer for this hash map.</returns>
public ParallelWriter AsParallelWriter()
{
ParallelWriter writer;
writer.m_Writer = m_HashMapData.AsParallelWriter();
#if ENABLE_UNITY_COLLECTIONS_CHECKS
writer.m_Safety = m_Safety;
CollectionHelper.SetStaticSafetyId<ParallelWriter>(ref writer.m_Safety, ref ParallelWriter.s_staticSafetyId.Data);
#endif
return writer;
}
/// <summary>
/// A parallel writer for a NativeHashMap.
/// </summary>
/// <remarks>
/// Use <see cref="AsParallelWriter"/> to create a parallel writer for a NativeHashMap.
/// </remarks>
[NativeContainer]
[NativeContainerIsAtomicWriteOnly]
[DebuggerDisplay("Capacity = {m_Writer.Capacity}")]
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public unsafe struct ParallelWriter
{
internal UnsafeHashMap<TKey, TValue>.ParallelWriter m_Writer;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
internal AtomicSafetyHandle m_Safety;
internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<ParallelWriter>();
#endif
/// <summary>
/// Returns the index of the current thread.
/// </summary>
/// <remarks>In a job, each thread gets its own copy of the ParallelWriter struct, and the job system assigns
/// each copy the index of its thread.</remarks>
/// <value>The index of the current thread.</value>
public int m_ThreadIndex => m_Writer.m_ThreadIndex;
/// <summary>
/// The number of key-value pairs that fit in the current allocation.
/// </summary>
/// <value>The number of key-value pairs that fit in the current allocation.</value>
public int Capacity
{
get
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
return m_Writer.Capacity;
}
}
/// <summary>
/// Adds a new key-value pair.
/// </summary>
/// <remarks>If the key is already present, this method returns false without modifying this hash map.</remarks>
/// <param name="key">The key to add.</param>
/// <param name="item">The value to add.</param>
/// <returns>True if the key-value pair was added.</returns>
public bool TryAdd(TKey key, TValue item)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety);
#endif
return m_Writer.TryAdd(key, item);
}
}
/// <summary>
/// Returns an enumerator over the key-value pairs of this hash map.
/// </summary>
/// <returns>An enumerator over the key-value pairs of this hash map.</returns>
public Enumerator GetEnumerator()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckGetSecondaryDataPointerAndThrow(m_Safety);
var ash = m_Safety;
AtomicSafetyHandle.UseSecondaryVersion(ref ash);
#endif
return new Enumerator
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_Safety = ash,
#endif
m_Enumerator = new UnsafeHashMapDataEnumerator(m_HashMapData.m_Buffer),
};
}
/// <summary>
/// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
/// </summary>
/// <returns>Throws NotImplementedException.</returns>
/// <exception cref="NotImplementedException">Method is not implemented.</exception>
IEnumerator<KeyValue<TKey, TValue>> IEnumerable<KeyValue<TKey, TValue>>.GetEnumerator()
{
throw new NotImplementedException();
}
/// <summary>
/// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
/// </summary>
/// <returns>Throws NotImplementedException.</returns>
/// <exception cref="NotImplementedException">Method is not implemented.</exception>
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
/// <summary>
/// An enumerator over the key-value pairs of a hash map.
/// </summary>
/// <remarks>
/// In an enumerator's initial state, <see cref="Current"/> is not valid to read.
/// From this state, the first <see cref="MoveNext"/> call advances the enumerator to the first key-value pair.
/// </remarks>
[NativeContainer]
[NativeContainerIsReadOnly]
public struct Enumerator : IEnumerator<KeyValue<TKey, TValue>>
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
internal AtomicSafetyHandle m_Safety;
#endif
internal UnsafeHashMapDataEnumerator m_Enumerator;
/// <summary>
/// Does nothing.
/// </summary>
public void Dispose() { }
/// <summary>
/// Advances the enumerator to the next key-value pair.
/// </summary>
/// <returns>True if <see cref="Current"/> is valid to read after the call.</returns>
public bool MoveNext()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
return m_Enumerator.MoveNext();
}
/// <summary>
/// Resets the enumerator to its initial state.
/// </summary>
public void Reset()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
m_Enumerator.Reset();
}
/// <summary>
/// The current key-value pair.
/// </summary>
/// <value>The current key-value pair.</value>
public KeyValue<TKey, TValue> Current => m_Enumerator.GetCurrent<TKey, TValue>();
object IEnumerator.Current => Current;
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckRead()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckWrite()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety);
#endif
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void ThrowKeyNotPresent(TKey key)
{
throw new ArgumentException($"Key: {key} is not present in the NativeHashMap.");
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void ThrowKeyAlreadyAdded(TKey key)
{
throw new ArgumentException("An item with the same key has already been added", nameof(key));
}
}
internal sealed class NativeHashMapDebuggerTypeProxy<TKey, TValue>
where TKey : struct, IEquatable<TKey>
where TValue : struct
{
#if !NET_DOTS
UnsafeHashMap<TKey, TValue> m_Target;
public NativeHashMapDebuggerTypeProxy(NativeHashMap<TKey, TValue> target)
{
m_Target = target.m_HashMapData;
}
public List<Pair<TKey, TValue>> Items
{
get
{
var result = new List<Pair<TKey, TValue>>();
using (var kva = m_Target.GetKeyValueArrays(Allocator.Temp))
{
for (var i = 0; i < kva.Length; ++i)
{
result.Add(new Pair<TKey, TValue>(kva.Keys[i], kva.Values[i]));
}
}
return result;
}
}
#endif
}
}

View file

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1f49bcab900e54c498a852d067a7636c
timeCreated: 1485127823
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,174 @@
using System;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Collections
{
/// <summary>
/// Provides extension methods for hash maps.
/// </summary>
[BurstCompatible]
public static class NativeHashMapExtensions
{
/// <summary>
/// Removes duplicate values from this sorted array and returns the number of values remaining.
/// </summary>
/// <remarks>
/// Uses `Equals` to determine whether values are duplicates.
///
/// Expects the array to already be sorted.
///
/// The remaining elements will be tightly packed at the front of the array.
/// </remarks>
/// <typeparam name="T">The type of values in the array.</typeparam>
/// <param name="array">The array from which to remove duplicates.</param>
/// <returns>The number of unique elements in this array.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public static int Unique<T>(this NativeArray<T> array)
where T : struct, IEquatable<T>
{
if (array.Length == 0)
{
return 0;
}
int first = 0;
int last = array.Length;
var result = first;
while (++first != last)
{
if (!array[result].Equals(array[first]))
{
array[++result] = array[first];
}
}
return ++result;
}
#if !NET_DOTS // Tuple is not supported by TinyBCL
/// <summary>
/// Returns an array populated with the unique keys from this multi hash map.
/// </summary>
/// <typeparam name="TKey">The type of the keys.</typeparam>
/// <typeparam name="TValue">The type of the values.</typeparam>
/// <param name="container">The multi hash map.</param>
/// <param name="allocator">The allocator to use.</param>
/// <returns>An array populated with the unique keys from this multi hash map.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(int) })]
public static (NativeArray<TKey>, int) GetUniqueKeyArray<TKey, TValue>(this UnsafeMultiHashMap<TKey, TValue> container, AllocatorManager.AllocatorHandle allocator)
where TKey : struct, IEquatable<TKey>, IComparable<TKey>
where TValue : struct
{
var result = container.GetKeyArray(allocator);
result.Sort();
int uniques = result.Unique();
return (result, uniques);
}
/// <summary>
/// Returns an array populated with the unique keys from this multi hash map.
/// </summary>
/// <typeparam name="TKey">The type of the keys.</typeparam>
/// <typeparam name="TValue">The type of the values.</typeparam>
/// <param name="container">The multi hash map.</param>
/// <param name="allocator">The allocator to use.</param>
/// <returns>An array populated with the unique keys from this multi hash map.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(int) })]
public static (NativeArray<TKey>, int) GetUniqueKeyArray<TKey, TValue>(this NativeMultiHashMap<TKey, TValue> container, AllocatorManager.AllocatorHandle allocator)
where TKey : struct, IEquatable<TKey>, IComparable<TKey>
where TValue : struct
{
var result = container.GetKeyArray(allocator);
result.Sort();
int uniques = result.Unique();
return (result, uniques);
}
#endif
/// <summary>
/// Returns a "bucket" view of this hash map.
/// </summary>
/// <remarks>
/// Internally, the elements of a hash map are split into buckets of type <see cref="UnsafeHashMapBucketData"/>.
///
/// With buckets, a job can safely access the elements of a hash map concurrently as long as each individual bucket is accessed
/// only from an individual thread. Effectively, it is not safe to read elements of an individual bucket concurrently,
/// but it is safe to read elements of separate buckets concurrently.
/// </remarks>
/// <typeparam name="TKey">The type of the keys.</typeparam>
/// <typeparam name="TValue">The type of the values.</typeparam>
/// <param name="container">The hash map.</param>
/// <returns>A "bucket" view of this hash map.</returns>
[Obsolete("GetBucketData is deprecated, please use GetUnsafeBucketData instead. (RemovedAfter 2021-07-08) (UnityUpgradable) -> GetUnsafeBucketData<TKey,TValue>(*)", false)]
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public static unsafe UnsafeHashMapBucketData GetBucketData<TKey, TValue>(this NativeHashMap<TKey, TValue> container)
where TKey : struct, IEquatable<TKey>
where TValue : struct
{
return container.m_HashMapData.m_Buffer->GetBucketData();
}
/// <summary>
/// Returns a "bucket" view of this hash map.
/// </summary>
/// <remarks>
/// Internally, the elements of a hash map are split into buckets of type <see cref="UnsafeHashMapBucketData"/>.
///
/// With buckets, a job can safely access the elements of a hash map concurrently as long as each individual bucket is accessed
/// only from an individual thread. Effectively, it is not safe to read elements of an individual bucket concurrently,
/// but it is safe to read elements of separate buckets concurrently.
/// </remarks>
/// <typeparam name="TKey">The type of the keys.</typeparam>
/// <typeparam name="TValue">The type of the values.</typeparam>
/// <param name="container">The hash map.</param>
/// <returns>A "bucket" view of this hash map.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(int) })]
public static unsafe UnsafeHashMapBucketData GetUnsafeBucketData<TKey, TValue>(this NativeHashMap<TKey, TValue> container)
where TKey : struct, IEquatable<TKey>
where TValue : struct
{
return container.m_HashMapData.m_Buffer->GetBucketData();
}
/// <summary>
/// Returns a "bucket" view of this multi hash map.
/// </summary>
/// <remarks>
/// Internally, the elements of a hash map are split into buckets of type <see cref="UnsafeHashMapBucketData"/>.
///
/// With buckets, a job can safely access the elements of a hash map concurrently as long as each individual bucket is accessed
/// only from an individual thread. Effectively, it is not safe to read elements of an individual bucket concurrently,
/// but it is safe to read elements of separate buckets concurrently.
/// </remarks>
/// <typeparam name="TKey">The type of the keys.</typeparam>
/// <typeparam name="TValue">The type of the values.</typeparam>
/// <param name="container">The multi hash map.</param>
/// <returns>A "bucket" view of this multi hash map.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public static unsafe UnsafeHashMapBucketData GetUnsafeBucketData<TKey, TValue>(this NativeMultiHashMap<TKey, TValue> container)
where TKey : struct, IEquatable<TKey>
where TValue : struct
{
return container.m_MultiHashMapData.m_Buffer->GetBucketData();
}
/// <summary>
/// Removes all occurrences of a particular key-value pair.
/// </summary>
/// <remarks>Removes all key-value pairs which have a particular key and which *also have* a particular value.
/// In other words: (key *AND* value) rather than (key *OR* value).</remarks>
/// <typeparam name="TKey">The type of the keys.</typeparam>
/// <typeparam name="TValue">The type of the values.</typeparam>
/// <param name="container">The multi hash map.</param>
/// <param name="key">The key of the key-value pairs to remove.</param>
/// <param name="value">The value of the key-value pairs to remove.</param>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public static void Remove<TKey, TValue>(this NativeMultiHashMap<TKey, TValue> container, TKey key, TValue value) where TKey : struct, IEquatable<TKey> where TValue : struct, IEquatable<TValue>
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(container.m_Safety);
#endif
container.m_MultiHashMapData.Remove(key, value);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 859b8b7a1710f0b4396876700dda6319
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,295 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using UnityEngine.Internal;
using Unity.Burst;
namespace Unity.Collections
{
/// <summary>
/// An unordered, expandable set of unique values.
/// </summary>
/// <typeparam name="T">The type of the values.</typeparam>
[StructLayout(LayoutKind.Sequential)]
[DebuggerTypeProxy(typeof(NativeHashSetDebuggerTypeProxy<>))]
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public unsafe struct NativeHashSet<T>
: INativeDisposable
, IEnumerable<T> // Used by collection initializers.
where T : unmanaged, IEquatable<T>
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<NativeHashSet<T>>();
#endif
internal NativeHashMap<T, bool> m_Data;
/// <summary>
/// Initializes and returns an instance of NativeHashSet.
/// </summary>
/// <param name="capacity">The number of values that should fit in the initial allocation.</param>
/// <param name="allocator">The allocator to use.</param>
public NativeHashSet(int capacity, AllocatorManager.AllocatorHandle allocator)
{
m_Data = new NativeHashMap<T, bool>(capacity, allocator);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
CollectionHelper.SetStaticSafetyId<NativeHashSet<T>>(ref m_Data.m_Safety, ref s_staticSafetyId.Data);
#endif
}
/// <summary>
/// Whether this set is empty.
/// </summary>
/// <value>True if this set is empty or if the set has not been constructed.</value>
public bool IsEmpty => m_Data.IsEmpty;
/// <summary>
/// Returns the current number of values in this set.
/// </summary>
/// <returns>The current number of values in this set.</returns>
public int Count() => m_Data.Count();
/// <summary>
/// The number of values that fit in the current allocation.
/// </summary>
/// <value>The number of values that fit in the current allocation.</value>
/// <param name="value">A new capacity. Must be larger than current capacity.</param>
/// <exception cref="Exception">Thrown if `value` is less than the current capacity.</exception>
public int Capacity { get => m_Data.Capacity; set => m_Data.Capacity = value; }
/// <summary>
/// Whether this set has been allocated (and not yet deallocated).
/// </summary>
/// <value>True if this set has been allocated (and not yet deallocated).</value>
public bool IsCreated => m_Data.IsCreated;
/// <summary>
/// Releases all resources (memory and safety handles).
/// </summary>
public void Dispose() => m_Data.Dispose();
/// <summary>
/// Creates and schedules a job that will dispose this set.
/// </summary>
/// <param name="inputDeps">A job handle. The newly scheduled job will depend upon this handle.</param>
/// <returns>The handle of a new job that will dispose this set.</returns>
[NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
public JobHandle Dispose(JobHandle inputDeps) => m_Data.Dispose(inputDeps);
/// <summary>
/// Removes all values.
/// </summary>
/// <remarks>Does not change the capacity.</remarks>
public void Clear() => m_Data.Clear();
/// <summary>
/// Adds a new value (unless it is already present).
/// </summary>
/// <param name="item">The value to add.</param>
/// <returns>True if the value was not already present.</returns>
public bool Add(T item) => m_Data.TryAdd(item, false);
/// <summary>
/// Removes a particular value.
/// </summary>
/// <param name="item">The value to remove.</param>
/// <returns>True if the value was present.</returns>
public bool Remove(T item) => m_Data.Remove(item);
/// <summary>
/// Returns true if a particular value is present.
/// </summary>
/// <param name="item">The value to check for.</param>
/// <returns>True if the value was present.</returns>
public bool Contains(T item) => m_Data.ContainsKey(item);
/// <summary>
/// Returns an array with a copy of this set's values (in no particular order).
/// </summary>
/// <param name="allocator">The allocator to use.</param>
/// <returns>An array with a copy of the set's values.</returns>
public NativeArray<T> ToNativeArray(AllocatorManager.AllocatorHandle allocator) => m_Data.GetKeyArray(allocator);
/// <summary>
/// Returns a parallel writer.
/// </summary>
/// <returns>A parallel writer.</returns>
public ParallelWriter AsParallelWriter()
{
ParallelWriter writer;
writer.m_Data = m_Data.AsParallelWriter();
#if ENABLE_UNITY_COLLECTIONS_CHECKS
CollectionHelper.SetStaticSafetyId<ParallelWriter>(ref writer.m_Data.m_Safety, ref ParallelWriter.s_staticSafetyId.Data);
#endif
return writer;
}
/// <summary>
/// A parallel writer for a NativeHashSet.
/// </summary>
/// <remarks>
/// Use <see cref="AsParallelWriter"/> to create a parallel writer for a set.
/// </remarks>
[NativeContainerIsAtomicWriteOnly]
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public struct ParallelWriter
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<ParallelWriter>();
#endif
internal NativeHashMap<T, bool>.ParallelWriter m_Data;
/// <summary>
/// The number of values that fit in the current allocation.
/// </summary>
/// <value>The number of values that fit in the current allocation.</value>
public int Capacity => m_Data.Capacity;
/// <summary>
/// Adds a new value (unless it is already present).
/// </summary>
/// <param name="item">The value to add.</param>
/// <returns>True if the value is not already present.</returns>
public bool Add(T item) => m_Data.TryAdd(item, false);
}
/// <summary>
/// Returns an enumerator over the values of this set.
/// </summary>
/// <returns>An enumerator over the values of this set.</returns>
public Enumerator GetEnumerator()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckGetSecondaryDataPointerAndThrow(m_Data.m_Safety);
var ash = m_Data.m_Safety;
AtomicSafetyHandle.UseSecondaryVersion(ref ash);
#endif
return new Enumerator
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_Safety = ash,
#endif
m_Enumerator = new UnsafeHashMapDataEnumerator(m_Data.m_HashMapData.m_Buffer),
};
}
/// <summary>
/// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
/// </summary>
/// <returns>Throws NotImplementedException.</returns>
/// <exception cref="NotImplementedException">Method is not implemented.</exception>
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
throw new NotImplementedException();
}
/// <summary>
/// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
/// </summary>
/// <returns>Throws NotImplementedException.</returns>
/// <exception cref="NotImplementedException">Method is not implemented.</exception>
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
/// <summary>
/// An enumerator over the values of a set.
/// </summary>
/// <remarks>
/// In an enumerator's initial state, <see cref="Current"/> is invalid.
/// The first <see cref="MoveNext"/> call advances the enumerator to the first value.
/// </remarks>
[NativeContainer]
[NativeContainerIsReadOnly]
public struct Enumerator : IEnumerator<T>
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
internal AtomicSafetyHandle m_Safety;
#endif
internal UnsafeHashMapDataEnumerator m_Enumerator;
/// <summary>
/// Does nothing.
/// </summary>
public void Dispose() { }
/// <summary>
/// Advances the enumerator to the next value.
/// </summary>
/// <returns>True if `Current` is valid to read after the call.</returns>
public bool MoveNext()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
return m_Enumerator.MoveNext();
}
/// <summary>
/// Resets the enumerator to its initial state.
/// </summary>
public void Reset()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
m_Enumerator.Reset();
}
/// <summary>
/// The current value.
/// </summary>
/// <value>The current value.</value>
public T Current
{
get
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
return m_Enumerator.GetCurrentKey<T>();
}
}
/// <summary>
/// Gets the element at the current position of the enumerator in the container.
/// </summary>
object IEnumerator.Current => Current;
}
}
sealed internal class NativeHashSetDebuggerTypeProxy<T>
where T : unmanaged, IEquatable<T>
{
#if !NET_DOTS
NativeHashSet<T> Data;
public NativeHashSetDebuggerTypeProxy(NativeHashSet<T> data)
{
Data = data;
}
public List<T> Items
{
get
{
var result = new List<T>();
using (var keys = Data.ToNativeArray(Allocator.Temp))
{
for (var k = 0; k < keys.Length; ++k)
{
result.Add(keys[k]);
}
}
return result;
}
}
#endif
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: da168a32991917b48989353c3d849677
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,444 @@
using System;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Collections
{
/// <summary>
/// Provides extension methods for sets.
/// </summary>
public unsafe static class HashSetExtensions
{
/// <summary>
/// Removes the values from this set which are also present in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void ExceptWith<T>(this NativeHashSet<T> container, FixedList128Bytes<T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Remove(item);
}
}
/// <summary>
/// Removes the values from this set which are absent in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void IntersectWith<T>(this NativeHashSet<T> container, FixedList128Bytes<T> other)
where T : unmanaged, IEquatable<T>
{
var result = new UnsafeList<T>(container.Count(), Allocator.Temp);
foreach (var item in other)
{
if (container.Contains(item))
{
result.Add(item);
}
}
container.Clear();
container.UnionWith(result);
result.Dispose();
}
/// <summary>
/// Adds all values from a collection to this set.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to add values to.</param>
/// <param name="other">The collection to copy values from.</param>
public static void UnionWith<T>(this NativeHashSet<T> container, FixedList128Bytes<T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Add(item);
}
}
/// <summary>
/// Removes the values from this set which are also present in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void ExceptWith<T>(this NativeHashSet<T> container, FixedList32Bytes<T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Remove(item);
}
}
/// <summary>
/// Removes the values from this set which are absent in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void IntersectWith<T>(this NativeHashSet<T> container, FixedList32Bytes<T> other)
where T : unmanaged, IEquatable<T>
{
var result = new UnsafeList<T>(container.Count(), Allocator.Temp);
foreach (var item in other)
{
if (container.Contains(item))
{
result.Add(item);
}
}
container.Clear();
container.UnionWith(result);
result.Dispose();
}
/// <summary>
/// Adds all values from a collection to this set.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to add values to.</param>
/// <param name="other">The collection to copy values from.</param>
public static void UnionWith<T>(this NativeHashSet<T> container, FixedList32Bytes<T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Add(item);
}
}
/// <summary>
/// Removes the values from this set which are also present in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void ExceptWith<T>(this NativeHashSet<T> container, FixedList4096Bytes<T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Remove(item);
}
}
/// <summary>
/// Removes the values from this set which are absent in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void IntersectWith<T>(this NativeHashSet<T> container, FixedList4096Bytes<T> other)
where T : unmanaged, IEquatable<T>
{
var result = new UnsafeList<T>(container.Count(), Allocator.Temp);
foreach (var item in other)
{
if (container.Contains(item))
{
result.Add(item);
}
}
container.Clear();
container.UnionWith(result);
result.Dispose();
}
/// <summary>
/// Adds all values from a collection to this set.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to add values to.</param>
/// <param name="other">The collection to copy values from.</param>
public static void UnionWith<T>(this NativeHashSet<T> container, FixedList4096Bytes<T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Add(item);
}
}
/// <summary>
/// Removes the values from this set which are also present in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void ExceptWith<T>(this NativeHashSet<T> container, FixedList512Bytes<T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Remove(item);
}
}
/// <summary>
/// Removes the values from this set which are absent in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void IntersectWith<T>(this NativeHashSet<T> container, FixedList512Bytes<T> other)
where T : unmanaged, IEquatable<T>
{
var result = new UnsafeList<T>(container.Count(), Allocator.Temp);
foreach (var item in other)
{
if (container.Contains(item))
{
result.Add(item);
}
}
container.Clear();
container.UnionWith(result);
result.Dispose();
}
/// <summary>
/// Adds all values from a collection to this set.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to add values to.</param>
/// <param name="other">The collection to copy values from.</param>
public static void UnionWith<T>(this NativeHashSet<T> container, FixedList512Bytes<T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Add(item);
}
}
/// <summary>
/// Removes the values from this set which are also present in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void ExceptWith<T>(this NativeHashSet<T> container, FixedList64Bytes<T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Remove(item);
}
}
/// <summary>
/// Removes the values from this set which are absent in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void IntersectWith<T>(this NativeHashSet<T> container, FixedList64Bytes<T> other)
where T : unmanaged, IEquatable<T>
{
var result = new UnsafeList<T>(container.Count(), Allocator.Temp);
foreach (var item in other)
{
if (container.Contains(item))
{
result.Add(item);
}
}
container.Clear();
container.UnionWith(result);
result.Dispose();
}
/// <summary>
/// Adds all values from a collection to this set.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to add values to.</param>
/// <param name="other">The collection to copy values from.</param>
public static void UnionWith<T>(this NativeHashSet<T> container, FixedList64Bytes<T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Add(item);
}
}
/// <summary>
/// Removes the values from this set which are also present in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void ExceptWith<T>(this NativeHashSet<T> container, NativeArray<T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Remove(item);
}
}
/// <summary>
/// Removes the values from this set which are absent in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void IntersectWith<T>(this NativeHashSet<T> container, NativeArray<T> other)
where T : unmanaged, IEquatable<T>
{
var result = new UnsafeList<T>(container.Count(), Allocator.Temp);
foreach (var item in other)
{
if (container.Contains(item))
{
result.Add(item);
}
}
container.Clear();
container.UnionWith(result);
result.Dispose();
}
/// <summary>
/// Adds all values from a collection to this set.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to add values to.</param>
/// <param name="other">The collection to copy values from.</param>
public static void UnionWith<T>(this NativeHashSet<T> container, NativeArray<T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Add(item);
}
}
/// <summary>
/// Removes the values from this set which are also present in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void ExceptWith<T>(this NativeHashSet<T> container, NativeHashSet<T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Remove(item);
}
}
/// <summary>
/// Removes the values from this set which are absent in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void IntersectWith<T>(this NativeHashSet<T> container, NativeHashSet<T> other)
where T : unmanaged, IEquatable<T>
{
var result = new UnsafeList<T>(container.Count(), Allocator.Temp);
foreach (var item in other)
{
if (container.Contains(item))
{
result.Add(item);
}
}
container.Clear();
container.UnionWith(result);
result.Dispose();
}
/// <summary>
/// Adds all values from a collection to this set.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to add values to.</param>
/// <param name="other">The collection to copy values from.</param>
public static void UnionWith<T>(this NativeHashSet<T> container, NativeHashSet<T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Add(item);
}
}
/// <summary>
/// Removes the values from this set which are also present in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void ExceptWith<T>(this NativeHashSet<T> container, NativeList<T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Remove(item);
}
}
/// <summary>
/// Removes the values from this set which are absent in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void IntersectWith<T>(this NativeHashSet<T> container, NativeList<T> other)
where T : unmanaged, IEquatable<T>
{
var result = new UnsafeList<T>(container.Count(), Allocator.Temp);
foreach (var item in other)
{
if (container.Contains(item))
{
result.Add(item);
}
}
container.Clear();
container.UnionWith(result);
result.Dispose();
}
/// <summary>
/// Adds all values from a collection to this set.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to add values to.</param>
/// <param name="other">The collection to copy values from.</param>
public static void UnionWith<T>(this NativeHashSet<T> container, NativeList<T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Add(item);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 66713ae417f65c84c944fd6fa9e80129
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,88 @@
<#/*THIS IS A T4 FILE - see t4_text_templating.md for what it is and how to run codegen*/#>
<#@ template debug="True" #>
<#@ output extension=".gen.cs" #>
<#@ assembly name="System.Core" #>
using System;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Collections
{
/// <summary>
/// Provides extension methods for sets.
/// </summary>
public unsafe static class HashSetExtensions
{
<#
{
foreach (var ContainerType in new[] {
( "NativeHashSet" ),
}) {
foreach (var OtherContainerType in new[] {
( "FixedList128Bytes" ),
( "FixedList32Bytes" ),
( "FixedList4096Bytes" ),
( "FixedList512Bytes" ),
( "FixedList64Bytes" ),
( "NativeArray" ),
( "NativeHashSet" ),
( "NativeList" ),
}) {
#>
/// <summary>
/// Removes the values from this set which are also present in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void ExceptWith<T>(this <#=ContainerType#><T> container, <#=OtherContainerType#><T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Remove(item);
}
}
/// <summary>
/// Removes the values from this set which are absent in another collection.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to remove values from.</param>
/// <param name="other">The collection to compare with.</param>
public static void IntersectWith<T>(this <#=ContainerType#><T> container, <#=OtherContainerType#><T> other)
where T : unmanaged, IEquatable<T>
{
var result = new UnsafeList<T>(container.Count(), Allocator.Temp);
foreach (var item in other)
{
if (container.Contains(item))
{
result.Add(item);
}
}
container.Clear();
container.UnionWith(result);
result.Dispose();
}
/// <summary>
/// Adds all values from a collection to this set.
/// </summary>
/// <typeparam name="T">The type of values.</typeparam>
/// <param name="container">The set to add values to.</param>
/// <param name="other">The collection to copy values from.</param>
public static void UnionWith<T>(this <#=ContainerType#><T> container, <#=OtherContainerType#><T> other)
where T : unmanaged, IEquatable<T>
{
foreach (var item in other)
{
container.Add(item);
}
}
<#}}}#>
}
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 6f9fa81af98728e4f8a0db3762124ff9
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 0ddd82532ba0b41c7af9888fdbc562e1
timeCreated: 1485127823
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,698 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Collections.NotBurstCompatible;
using Unity.Jobs;
namespace Unity.Collections
{
/// <summary>
/// An iterator over all values associated with an individual key in a multi hash map.
/// </summary>
/// <remarks>The iteration order over the values associated with a key is an implementation detail. Do not rely upon any particular ordering.</remarks>
/// <typeparam name="TKey">The type of the keys.</typeparam>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public struct NativeMultiHashMapIterator<TKey>
where TKey : struct
{
internal TKey key;
internal int NextEntryIndex;
internal int EntryIndex;
/// <summary>
/// Returns the entry index.
/// </summary>
/// <returns>The entry index.</returns>
public int GetEntryIndex() => EntryIndex;
}
/// <summary>
/// An unordered, expandable associative array. Each key can have more than one associated value.
/// </summary>
/// <remarks>
/// Unlike a regular NativeHashMap, a NativeMultiHashMap can store multiple key-value pairs with the same key.
///
/// The keys are not deduplicated: two key-value pairs with the same key are stored as fully separate key-value pairs.
/// </remarks>
/// <typeparam name="TKey">The type of the keys.</typeparam>
/// <typeparam name="TValue">The type of the values.</typeparam>
[StructLayout(LayoutKind.Sequential)]
[NativeContainer]
[DebuggerTypeProxy(typeof(NativeMultiHashMapDebuggerTypeProxy<,>))]
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public unsafe struct NativeMultiHashMap<TKey, TValue>
: INativeDisposable
, IEnumerable<KeyValue<TKey, TValue>> // Used by collection initializers.
where TKey : struct, IEquatable<TKey>
where TValue : struct
{
internal UnsafeMultiHashMap<TKey, TValue> m_MultiHashMapData;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
internal AtomicSafetyHandle m_Safety;
internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<NativeMultiHashMap<TKey, TValue>>();
#if REMOVE_DISPOSE_SENTINEL
#else
[NativeSetClassTypeToNullOnSchedule]
internal DisposeSentinel m_DisposeSentinel;
#endif
#endif
/// <summary>
/// Returns a newly allocated multi hash map.
/// </summary>
/// <param name="capacity">The number of key-value pairs that should fit in the initial allocation.</param>
/// <param name="allocator">The allocator to use.</param>
public NativeMultiHashMap(int capacity, AllocatorManager.AllocatorHandle allocator)
: this(capacity, allocator, 2)
{
}
[BurstCompatible(GenericTypeArguments = new[] { typeof(AllocatorManager.AllocatorHandle) })]
internal void Initialize<U>(int capacity, ref U allocator, int disposeSentinelStackDepth)
where U : unmanaged, AllocatorManager.IAllocator
{
m_MultiHashMapData = new UnsafeMultiHashMap<TKey, TValue>(capacity, allocator.Handle);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
m_Safety = CollectionHelper.CreateSafetyHandle(allocator);
#else
if (allocator.IsCustomAllocator)
{
m_Safety = AtomicSafetyHandle.Create();
m_DisposeSentinel = null;
}
else
{
DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, disposeSentinelStackDepth, allocator.ToAllocator);
}
#endif
CollectionHelper.SetStaticSafetyId<NativeMultiHashMap<TKey, TValue>>(ref m_Safety, ref s_staticSafetyId.Data);
AtomicSafetyHandle.SetBumpSecondaryVersionOnScheduleWrite(m_Safety, true);
#endif
}
NativeMultiHashMap(int capacity, AllocatorManager.AllocatorHandle allocator, int disposeSentinelStackDepth)
{
this = default;
Initialize(capacity, ref allocator, disposeSentinelStackDepth);
}
/// <summary>
/// Whether this hash map is empty.
/// </summary>
/// <value>True if the hash map is empty or if the hash map has not been constructed.</value>
public bool IsEmpty
{
get
{
CheckRead();
return m_MultiHashMapData.IsEmpty;
}
}
/// <summary>
/// Returns the current number of key-value pairs in this hash map.
/// </summary>
/// <remarks>Key-value pairs with matching keys are counted as separate, individual pairs.</remarks>
/// <returns>The current number of key-value pairs in this hash map.</returns>
public int Count()
{
CheckRead();
return m_MultiHashMapData.Count();
}
/// <summary>
/// Returns the number of key-value pairs that fit in the current allocation.
/// </summary>
/// <value>The number of key-value pairs that fit in the current allocation.</value>
/// <param name="value">A new capacity. Must be larger than the current capacity.</param>
/// <exception cref="Exception">Thrown if `value` is less than the current capacity.</exception>
public int Capacity
{
get
{
CheckRead();
return m_MultiHashMapData.Capacity;
}
set
{
CheckWrite();
m_MultiHashMapData.Capacity = value;
}
}
/// <summary>
/// Removes all key-value pairs.
/// </summary>
/// <remarks>Does not change the capacity.</remarks>
public void Clear()
{
CheckWrite();
m_MultiHashMapData.Clear();
}
/// <summary>
/// Adds a new key-value pair.
/// </summary>
/// <remarks>
/// If a key-value pair with this key is already present, an additional separate key-value pair is added.
/// </remarks>
/// <param name="key">The key to add.</param>
/// <param name="item">The value to add.</param>
public void Add(TKey key, TValue item)
{
CheckWrite();
m_MultiHashMapData.Add(key, item);
}
/// <summary>
/// Removes a key and its associated value(s).
/// </summary>
/// <param name="key">The key to remove.</param>
/// <returns>The number of removed key-value pairs. If the key was not present, returns 0.</returns>
public int Remove(TKey key)
{
CheckWrite();
return m_MultiHashMapData.Remove(key);
}
/// <summary>
/// Removes a single key-value pair.
/// </summary>
/// <param name="it">An iterator representing the key-value pair to remove.</param>
/// <exception cref="InvalidOperationException">Thrown if the iterator is invalid.</exception>
public void Remove(NativeMultiHashMapIterator<TKey> it)
{
CheckWrite();
m_MultiHashMapData.Remove(it);
}
/// <summary>
/// Gets an iterator for a key.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="item">Outputs the associated value represented by the iterator.</param>
/// <param name="it">Outputs an iterator.</param>
/// <returns>True if the key was present.</returns>
public bool TryGetFirstValue(TKey key, out TValue item, out NativeMultiHashMapIterator<TKey> it)
{
CheckRead();
return m_MultiHashMapData.TryGetFirstValue(key, out item, out it);
}
/// <summary>
/// Advances an iterator to the next value associated with its key.
/// </summary>
/// <param name="item">Outputs the next value.</param>
/// <param name="it">A reference to the iterator to advance.</param>
/// <returns>True if the key was present and had another value.</returns>
public bool TryGetNextValue(out TValue item, ref NativeMultiHashMapIterator<TKey> it)
{
CheckRead();
return m_MultiHashMapData.TryGetNextValue(out item, ref it);
}
/// <summary>
/// Returns true if a given key is present in this hash map.
/// </summary>
/// <param name="key">The key to look up.</param>
/// <returns>True if the key was present in this hash map.</returns>
public bool ContainsKey(TKey key)
{
return TryGetFirstValue(key, out var temp0, out var temp1);
}
/// <summary>
/// Returns the number of values associated with a given key.
/// </summary>
/// <param name="key">The key to look up.</param>
/// <returns>The number of values associated with the key. Returns 0 if the key was not present.</returns>
public int CountValuesForKey(TKey key)
{
if (!TryGetFirstValue(key, out var value, out var iterator))
{
return 0;
}
var count = 1;
while (TryGetNextValue(out value, ref iterator))
{
count++;
}
return count;
}
/// <summary>
/// Sets a new value for an existing key-value pair.
/// </summary>
/// <param name="item">The new value.</param>
/// <param name="it">The iterator representing a key-value pair.</param>
/// <returns>True if a value was overwritten.</returns>
public bool SetValue(TValue item, NativeMultiHashMapIterator<TKey> it)
{
CheckWrite();
return m_MultiHashMapData.SetValue(item, it);
}
/// <summary>
/// Whether this hash map has been allocated (and not yet deallocated).
/// </summary>
/// <value>True if this hash map has been allocated (and not yet deallocated).</value>
public bool IsCreated => m_MultiHashMapData.IsCreated;
/// <summary>
/// Releases all resources (memory and safety handles).
/// </summary>
public void Dispose()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
CollectionHelper.DisposeSafetyHandle(ref m_Safety);
#else
DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel);
#endif
#endif
m_MultiHashMapData.Dispose();
}
/// <summary>
/// Creates and schedules a job that will dispose this hash map.
/// </summary>
/// <param name="inputDeps">A job handle. The newly scheduled job will depend upon this handle.</param>
/// <returns>The handle of a new job that will dispose this hash map.</returns>
[NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
public JobHandle Dispose(JobHandle inputDeps)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
#else
// [DeallocateOnJobCompletion] is not supported, but we want the deallocation
// to happen in a thread. DisposeSentinel needs to be cleared on main thread.
// AtomicSafetyHandle can be destroyed after the job was scheduled (Job scheduling
// will check that no jobs are writing to the container).
DisposeSentinel.Clear(ref m_DisposeSentinel);
#endif
var jobHandle = new UnsafeHashMapDataDisposeJob { Data = new UnsafeHashMapDataDispose { m_Buffer = m_MultiHashMapData.m_Buffer, m_AllocatorLabel = m_MultiHashMapData.m_AllocatorLabel, m_Safety = m_Safety } }.Schedule(inputDeps);
AtomicSafetyHandle.Release(m_Safety);
#else
var jobHandle = new UnsafeHashMapDataDisposeJob { Data = new UnsafeHashMapDataDispose { m_Buffer = m_MultiHashMapData.m_Buffer, m_AllocatorLabel = m_MultiHashMapData.m_AllocatorLabel } }.Schedule(inputDeps);
#endif
m_MultiHashMapData.m_Buffer = null;
return jobHandle;
}
/// <summary>
/// Returns an array with a copy of all the keys (in no particular order).
/// </summary>
/// <remarks>A key with *N* values is included *N* times in the array.
///
/// Use `GetUniqueKeyArray` of <see cref="Unity.Collections.NativeHashMapExtensions"/> instead if you only want one occurrence of each key.</remarks>
/// <param name="allocator">The allocator to use.</param>
/// <returns>An array with a copy of all the keys (in no particular order).</returns>
public NativeArray<TKey> GetKeyArray(AllocatorManager.AllocatorHandle allocator)
{
CheckRead();
return m_MultiHashMapData.GetKeyArray(allocator);
}
/// <summary>
/// Returns an array with a copy of all the values (in no particular order).
/// </summary>
/// <remarks>The values are not deduplicated. If you sort the returned array,
/// you can use <see cref="Unity.Collections.NativeHashMapExtensions.Unique{T}"/> to remove duplicate values.</remarks>
/// <param name="allocator">The allocator to use.</param>
/// <returns>An array with a copy of all the values (in no particular order).</returns>
public NativeArray<TValue> GetValueArray(AllocatorManager.AllocatorHandle allocator)
{
CheckRead();
return m_MultiHashMapData.GetValueArray(allocator);
}
/// <summary>
/// Returns a NativeKeyValueArrays with a copy of all the keys and values (in no particular order).
/// </summary>
/// <remarks>A key with *N* values is included *N* times in the array.
/// </remarks>
/// <param name="allocator">The allocator to use.</param>
/// <returns>A NativeKeyValueArrays with a copy of all the keys and values (in no particular order).</returns>
public NativeKeyValueArrays<TKey, TValue> GetKeyValueArrays(AllocatorManager.AllocatorHandle allocator)
{
CheckRead();
return m_MultiHashMapData.GetKeyValueArrays(allocator);
}
/// <summary>
/// Returns a parallel writer for this hash map.
/// </summary>
/// <returns>A parallel writer for this hash map.</returns>
public ParallelWriter AsParallelWriter()
{
ParallelWriter writer;
writer.m_Writer = m_MultiHashMapData.AsParallelWriter();
#if ENABLE_UNITY_COLLECTIONS_CHECKS
writer.m_Safety = m_Safety;
CollectionHelper.SetStaticSafetyId<ParallelWriter>(ref writer.m_Safety, ref s_staticSafetyId.Data);
#endif
return writer;
}
/// <summary>
/// A parallel writer for a NativeMultiHashMap.
/// </summary>
/// <remarks>
/// Use <see cref="AsParallelWriter"/> to create a parallel writer for a NativeMultiHashMap.
/// </remarks>
[NativeContainer]
[NativeContainerIsAtomicWriteOnly]
[BurstCompatible(GenericTypeArguments = new [] { typeof(int), typeof(int) })]
public unsafe struct ParallelWriter
{
internal UnsafeMultiHashMap<TKey, TValue>.ParallelWriter m_Writer;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
internal AtomicSafetyHandle m_Safety;
internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<ParallelWriter>();
#endif
/// <summary>
/// Returns the index of the current thread.
/// </summary>
/// <remarks>In a job, each thread gets its own copy of the ParallelWriter struct, and the job system assigns
/// each copy the index of its thread.</remarks>
/// <value>The index of the current thread.</value>
public int m_ThreadIndex => m_Writer.m_ThreadIndex;
/// <summary>
/// Returns the number of key-value pairs that fit in the current allocation.
/// </summary>
/// <value>The number of key-value pairs that fit in the current allocation.</value>
public int Capacity
{
get
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
return m_Writer.Capacity;
}
}
/// <summary>
/// Adds a new key-value pair.
/// </summary>
/// <remarks>
/// If a key-value pair with this key is already present, an additional separate key-value pair is added.
/// </remarks>
/// <param name="key">The key to add.</param>
/// <param name="item">The value to add.</param>
public void Add(TKey key, TValue item)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety);
#endif
m_Writer.Add(key, item);
}
}
/// <summary>
/// Returns an enumerator over the values of an individual key.
/// </summary>
/// <param name="key">The key to get an enumerator for.</param>
/// <returns>An enumerator over the values of a key.</returns>
public Enumerator GetValuesForKey(TKey key)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
return new Enumerator { hashmap = this, key = key, isFirst = true };
}
/// <summary>
/// An enumerator over the values of an individual key in a multi hash map.
/// </summary>
/// <remarks>
/// In an enumerator's initial state, <see cref="Current"/> is not valid to read.
/// The first <see cref="MoveNext"/> call advances the enumerator to the first value of the key.
/// </remarks>
public struct Enumerator : IEnumerator<TValue>
{
internal NativeMultiHashMap<TKey, TValue> hashmap;
internal TKey key;
internal bool isFirst;
TValue value;
NativeMultiHashMapIterator<TKey> iterator;
/// <summary>
/// Does nothing.
/// </summary>
public void Dispose() { }
/// <summary>
/// Advances the enumerator to the next value of the key.
/// </summary>
/// <returns>True if <see cref="Current"/> is valid to read after the call.</returns>
public bool MoveNext()
{
//Avoids going beyond the end of the collection.
if (isFirst)
{
isFirst = false;
return hashmap.TryGetFirstValue(key, out value, out iterator);
}
return hashmap.TryGetNextValue(out value, ref iterator);
}
/// <summary>
/// Resets the enumerator to its initial state.
/// </summary>
public void Reset() => isFirst = true;
/// <summary>
/// The current value.
/// </summary>
/// <value>The current value.</value>
public TValue Current => value;
object IEnumerator.Current => Current;
/// <summary>
/// Returns this enumerator.
/// </summary>
/// <returns>This enumerator.</returns>
public Enumerator GetEnumerator() { return this; }
}
/// <summary>
/// Returns an enumerator over the key-value pairs of this hash map.
/// </summary>
/// <remarks>A key with *N* values is visited by the enumerator *N* times.</remarks>
/// <returns>An enumerator over the key-value pairs of this hash map.</returns>
public KeyValueEnumerator GetEnumerator()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckGetSecondaryDataPointerAndThrow(m_Safety);
var ash = m_Safety;
AtomicSafetyHandle.UseSecondaryVersion(ref ash);
#endif
return new KeyValueEnumerator
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_Safety = ash,
#endif
m_Enumerator = new UnsafeHashMapDataEnumerator(m_MultiHashMapData.m_Buffer),
};
}
/// <summary>
/// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
/// </summary>
/// <returns>Throws NotImplementedException.</returns>
/// <exception cref="NotImplementedException">Method is not implemented.</exception>
IEnumerator<KeyValue<TKey, TValue>> IEnumerable<KeyValue<TKey, TValue>>.GetEnumerator()
{
throw new NotImplementedException();
}
/// <summary>
/// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
/// </summary>
/// <returns>Throws NotImplementedException.</returns>
/// <exception cref="NotImplementedException">Method is not implemented.</exception>
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
/// <summary>
/// An enumerator over the key-value pairs of a multi hash map.
/// </summary>
/// <remarks>A key with *N* values is visited by the enumerator *N* times.
///
/// In an enumerator's initial state, <see cref="Current"/> is not valid to read.
/// The first <see cref="MoveNext"/> call advances the enumerator to the first key-value pair.
/// </remarks>
[NativeContainer]
[NativeContainerIsReadOnly]
public struct KeyValueEnumerator : IEnumerator<KeyValue<TKey, TValue>>
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
internal AtomicSafetyHandle m_Safety;
#endif
internal UnsafeHashMapDataEnumerator m_Enumerator;
/// <summary>
/// Does nothing.
/// </summary>
public void Dispose() { }
/// <summary>
/// Advances the enumerator to the next key-value pair.
/// </summary>
/// <returns>True if <see cref="Current"/> is valid to read after the call.</returns>
public unsafe bool MoveNext()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
return m_Enumerator.MoveNext();
}
/// <summary>
/// Resets the enumerator to its initial state.
/// </summary>
public void Reset()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
m_Enumerator.Reset();
}
/// <summary>
/// The current key-value pair.
/// </summary>
/// <value>The current key-value pair.</value>
public KeyValue<TKey, TValue> Current
{
get
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
return m_Enumerator.GetCurrent<TKey, TValue>();
}
}
object IEnumerator.Current => Current;
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckRead()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckWrite()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety);
#endif
}
}
internal sealed class NativeMultiHashMapDebuggerTypeProxy<TKey, TValue>
where TKey : struct, IEquatable<TKey>, IComparable<TKey>
where TValue : struct
{
#if !NET_DOTS
NativeMultiHashMap<TKey, TValue> m_Target;
public NativeMultiHashMapDebuggerTypeProxy(NativeMultiHashMap<TKey, TValue> target)
{
m_Target = target;
}
public List<ListPair<TKey, List<TValue>>> Items
{
get
{
var result = new List<ListPair<TKey, List<TValue>>>();
var keys = m_Target.GetUniqueKeyArray(Allocator.Temp);
using (keys.Item1)
{
for (var k = 0; k < keys.Item2; ++k)
{
var values = new List<TValue>();
if (m_Target.TryGetFirstValue(keys.Item1[k], out var value, out var iterator))
{
do
{
values.Add(value);
}
while (m_Target.TryGetNextValue(out value, ref iterator));
}
result.Add(new ListPair<TKey, List<TValue>>(keys.Item1[k], values));
}
}
return result;
}
}
#endif
}
[BurstCompatible]
public unsafe static class NativeMultiHashMapExtensions
{
[BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(int), typeof(AllocatorManager.AllocatorHandle) })]
internal static void Initialize<TKey, TValue, U>(ref this NativeMultiHashMap<TKey, TValue> nativeMultiHashMap,
int capacity,
ref U allocator,
int disposeSentinelStackDepth = 2)
where TKey : struct, IEquatable<TKey>
where TValue : struct
where U : unmanaged, AllocatorManager.IAllocator
{
nativeMultiHashMap.m_MultiHashMapData = new UnsafeMultiHashMap<TKey, TValue>(capacity, allocator.Handle);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
nativeMultiHashMap.m_Safety = CollectionHelper.CreateSafetyHandle(allocator.Handle);
#else
if (allocator.IsCustomAllocator)
{
nativeMultiHashMap.m_Safety = AtomicSafetyHandle.Create();
nativeMultiHashMap.m_DisposeSentinel = null;
}
else
{
DisposeSentinel.Create(out nativeMultiHashMap.m_Safety,
out nativeMultiHashMap.m_DisposeSentinel,
disposeSentinelStackDepth,
allocator.ToAllocator);
}
#endif
CollectionHelper.SetStaticSafetyId<NativeMultiHashMap<TKey, TValue>>(ref nativeMultiHashMap.m_Safety, ref NativeMultiHashMap<TKey, TValue>.s_staticSafetyId.Data);
#endif
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1de5774def35a6642a480cfd1b8c11d7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,88 @@
//#define USE_NOT_BURST_COMPATIBLE_EXTENSIONS
using System;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Collections.NotBurstCompatible
{
/// <summary>
/// Provides some extension methods for various collections.
/// </summary>
public static class Extensions
{
/// <summary>
/// Returns a new managed array with all the elements copied from a set.
/// </summary>
/// <typeparam name="T">The type of elements.</typeparam>
/// <param name="set">The set whose elements are copied to the array.</param>
/// <returns>A new managed array with all the elements copied from a set.</returns>
[NotBurstCompatible]
public static T[] ToArray<T>(this NativeHashSet<T> set)
where T : unmanaged, IEquatable<T>
{
var array = set.ToNativeArray(Allocator.TempJob);
var managed = array.ToArray();
array.Dispose();
return managed;
}
/// <summary>
/// Returns a new managed array which is a copy of this list.
/// </summary>
/// <typeparam name="T">The type of elements.</typeparam>
/// <param name="list">The list to copy.</param>
/// <returns>A new managed array which is a copy of this list.</returns>
[NotBurstCompatible]
public static T[] ToArrayNBC<T>(this NativeList<T> list)
where T : unmanaged
{
return list.AsArray().ToArray();
}
/// <summary>
/// Clears this list and then copies all the elements of an array to this list.
/// </summary>
/// <typeparam name="T">The type of elements.</typeparam>
/// <param name="list">This list.</param>
/// <param name="array">The managed array to copy from.</param>
[NotBurstCompatible]
public static void CopyFromNBC<T>(this NativeList<T> list, T[] array)
where T : unmanaged
{
list.Clear();
list.Resize(array.Length, NativeArrayOptions.UninitializedMemory);
NativeArray<T> na = list.AsArray();
na.CopyFrom(array);
}
#if !NET_DOTS // Tuple is not supported by TinyBCL
/// <summary>
/// Returns an array with the unique keys of this multi hash map.
/// </summary>
/// <typeparam name="TKey">The type of the keys.</typeparam>
/// <typeparam name="TValue">The type of the values.</typeparam>
/// <param name="hashmap">The multi hash map.</param>
/// <param name="allocator">The allocator to use.</param>
/// <returns>An array with the unique keys of this multi hash map.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(int) })]
[Obsolete("Burst now supports tuple, please use `GetUniqueKeyArray` method from `Unity.Collections.UnsafeMultiHashMap` instead.", false)]
public static (NativeArray<TKey>, int) GetUniqueKeyArrayNBC<TKey, TValue>(this UnsafeMultiHashMap<TKey, TValue> hashmap, AllocatorManager.AllocatorHandle allocator)
where TKey : struct, IEquatable<TKey>, IComparable<TKey>
where TValue : struct => hashmap.GetUniqueKeyArray(allocator);
/// <summary>
/// Returns an array with the unique keys of this multi hash map.
/// </summary>
/// <typeparam name="TKey">The type of the keys.</typeparam>
/// <typeparam name="TValue">The type of the values.</typeparam>
/// <param name="hashmap">The multi hash map.</param>
/// <param name="allocator">The allocator to use.</param>
/// <returns>An array with the unique keys of this multi hash map.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(int) })]
[Obsolete("Burst now supports tuple, please use `GetUniqueKeyArray` method from `Unity.Collections.NativeMultiHashMap` instead.", false)]
public static (NativeArray<TKey>, int) GetUniqueKeyArrayNBC<TKey, TValue>(this NativeMultiHashMap<TKey, TValue> hashmap, AllocatorManager.AllocatorHandle allocator)
where TKey : struct, IEquatable<TKey>, IComparable<TKey>
where TValue : struct => hashmap.GetUniqueKeyArray(allocator);
#endif
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b029fb97ffda0ec4aaa9d76de93a0608
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,693 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Burst;
using Unity.Jobs;
using Unity.Jobs.LowLevel.Unsafe;
using System.Diagnostics;
namespace Unity.Collections
{
unsafe struct NativeQueueBlockHeader
{
public NativeQueueBlockHeader* m_NextBlock;
public int m_NumItems;
}
[StructLayout(LayoutKind.Sequential)]
[BurstCompatible]
internal unsafe struct NativeQueueBlockPoolData
{
internal IntPtr m_FirstBlock;
internal int m_NumBlocks;
internal int m_MaxBlocks;
internal const int m_BlockSize = 16 * 1024;
internal int m_AllocLock;
public NativeQueueBlockHeader* AllocateBlock()
{
// There can only ever be a single thread allocating an entry from the free list since it needs to
// access the content of the block (the next pointer) before doing the CAS.
// If there was no lock thread A could read the next pointer, thread B could quickly allocate
// the same block then free it with another next pointer before thread A performs the CAS which
// leads to an invalid free list potentially causing memory corruption.
// Having multiple threads freeing data concurrently to each other while another thread is allocating
// is no problems since there is only ever a single thread modifying global data in that case.
while (Interlocked.CompareExchange(ref m_AllocLock, 1, 0) != 0)
{
}
NativeQueueBlockHeader* checkBlock = (NativeQueueBlockHeader*)m_FirstBlock;
NativeQueueBlockHeader* block;
do
{
block = checkBlock;
if (block == null)
{
Interlocked.Exchange(ref m_AllocLock, 0);
Interlocked.Increment(ref m_NumBlocks);
block = (NativeQueueBlockHeader*)Memory.Unmanaged.Allocate(m_BlockSize, 16, Allocator.Persistent);
return block;
}
checkBlock = (NativeQueueBlockHeader*)Interlocked.CompareExchange(ref m_FirstBlock, (IntPtr)block->m_NextBlock, (IntPtr)block);
}
while (checkBlock != block);
Interlocked.Exchange(ref m_AllocLock, 0);
return block;
}
public void FreeBlock(NativeQueueBlockHeader* block)
{
if (m_NumBlocks > m_MaxBlocks)
{
if (Interlocked.Decrement(ref m_NumBlocks) + 1 > m_MaxBlocks)
{
Memory.Unmanaged.Free(block, Allocator.Persistent);
return;
}
Interlocked.Increment(ref m_NumBlocks);
}
NativeQueueBlockHeader* checkBlock = (NativeQueueBlockHeader*)m_FirstBlock;
NativeQueueBlockHeader* nextPtr;
do
{
nextPtr = checkBlock;
block->m_NextBlock = checkBlock;
checkBlock = (NativeQueueBlockHeader*)Interlocked.CompareExchange(ref m_FirstBlock, (IntPtr)block, (IntPtr)checkBlock);
}
while (checkBlock != nextPtr);
}
}
internal unsafe class NativeQueueBlockPool
{
static readonly SharedStatic<IntPtr> Data = SharedStatic<IntPtr>.GetOrCreate<NativeQueueBlockPool>();
internal static NativeQueueBlockPoolData* GetQueueBlockPool()
{
var pData = (NativeQueueBlockPoolData**)Data.UnsafeDataPointer;
var data = *pData;
if (data == null)
{
data = (NativeQueueBlockPoolData*)Memory.Unmanaged.Allocate(UnsafeUtility.SizeOf<NativeQueueBlockPoolData>(), 8, Allocator.Persistent);
*pData = data;
data->m_NumBlocks = data->m_MaxBlocks = 256;
data->m_AllocLock = 0;
// Allocate MaxBlocks items
NativeQueueBlockHeader* prev = null;
for (int i = 0; i < data->m_MaxBlocks; ++i)
{
NativeQueueBlockHeader* block = (NativeQueueBlockHeader*)Memory.Unmanaged.Allocate(NativeQueueBlockPoolData.m_BlockSize, 16, Allocator.Persistent);
block->m_NextBlock = prev;
prev = block;
}
data->m_FirstBlock = (IntPtr)prev;
AppDomainOnDomainUnload();
}
return data;
}
[BurstDiscard]
static void AppDomainOnDomainUnload()
{
#if !UNITY_DOTSRUNTIME
AppDomain.CurrentDomain.DomainUnload += OnDomainUnload;
#endif
}
#if !UNITY_DOTSRUNTIME
static void OnDomainUnload(object sender, EventArgs e)
{
var pData = (NativeQueueBlockPoolData**)Data.UnsafeDataPointer;
var data = *pData;
while (data->m_FirstBlock != IntPtr.Zero)
{
NativeQueueBlockHeader* block = (NativeQueueBlockHeader*)data->m_FirstBlock;
data->m_FirstBlock = (IntPtr)block->m_NextBlock;
Memory.Unmanaged.Free(block, Allocator.Persistent);
--data->m_NumBlocks;
}
Memory.Unmanaged.Free(data, Allocator.Persistent);
*pData = null;
}
#endif
}
[StructLayout(LayoutKind.Sequential)]
[BurstCompatible]
internal unsafe struct NativeQueueData
{
public IntPtr m_FirstBlock;
public IntPtr m_LastBlock;
public int m_MaxItems;
public int m_CurrentRead;
public byte* m_CurrentWriteBlockTLS;
internal NativeQueueBlockHeader* GetCurrentWriteBlockTLS(int threadIndex)
{
var data = (NativeQueueBlockHeader**)&m_CurrentWriteBlockTLS[threadIndex * JobsUtility.CacheLineSize];
return *data;
}
internal void SetCurrentWriteBlockTLS(int threadIndex, NativeQueueBlockHeader* currentWriteBlock)
{
var data = (NativeQueueBlockHeader**)&m_CurrentWriteBlockTLS[threadIndex * JobsUtility.CacheLineSize];
*data = currentWriteBlock;
}
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public static NativeQueueBlockHeader* AllocateWriteBlockMT<T>(NativeQueueData* data, NativeQueueBlockPoolData* pool, int threadIndex) where T : struct
{
NativeQueueBlockHeader* currentWriteBlock = data->GetCurrentWriteBlockTLS(threadIndex);
if (currentWriteBlock != null
&& currentWriteBlock->m_NumItems == data->m_MaxItems)
{
currentWriteBlock = null;
}
if (currentWriteBlock == null)
{
currentWriteBlock = pool->AllocateBlock();
currentWriteBlock->m_NextBlock = null;
currentWriteBlock->m_NumItems = 0;
NativeQueueBlockHeader* prevLast = (NativeQueueBlockHeader*)Interlocked.Exchange(ref data->m_LastBlock, (IntPtr)currentWriteBlock);
if (prevLast == null)
{
data->m_FirstBlock = (IntPtr)currentWriteBlock;
}
else
{
prevLast->m_NextBlock = currentWriteBlock;
}
data->SetCurrentWriteBlockTLS(threadIndex, currentWriteBlock);
}
return currentWriteBlock;
}
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public unsafe static void AllocateQueue<T>(AllocatorManager.AllocatorHandle label, out NativeQueueData* outBuf) where T : struct
{
var queueDataSize = CollectionHelper.Align(UnsafeUtility.SizeOf<NativeQueueData>(), JobsUtility.CacheLineSize);
var data = (NativeQueueData*)Memory.Unmanaged.Allocate(
queueDataSize
+ JobsUtility.CacheLineSize * JobsUtility.MaxJobThreadCount
, JobsUtility.CacheLineSize
, label
);
data->m_CurrentWriteBlockTLS = (((byte*)data) + queueDataSize);
data->m_FirstBlock = IntPtr.Zero;
data->m_LastBlock = IntPtr.Zero;
data->m_MaxItems = (NativeQueueBlockPoolData.m_BlockSize - UnsafeUtility.SizeOf<NativeQueueBlockHeader>()) / UnsafeUtility.SizeOf<T>();
data->m_CurrentRead = 0;
for (int threadIndex = 0; threadIndex < JobsUtility.MaxJobThreadCount; ++threadIndex)
{
data->SetCurrentWriteBlockTLS(threadIndex, null);
}
outBuf = data;
}
public unsafe static void DeallocateQueue(NativeQueueData* data, NativeQueueBlockPoolData* pool, AllocatorManager.AllocatorHandle allocation)
{
NativeQueueBlockHeader* firstBlock = (NativeQueueBlockHeader*)data->m_FirstBlock;
while (firstBlock != null)
{
NativeQueueBlockHeader* next = firstBlock->m_NextBlock;
pool->FreeBlock(firstBlock);
firstBlock = next;
}
Memory.Unmanaged.Free(data, allocation);
}
}
/// <summary>
/// An unmanaged queue.
/// </summary>
/// <typeparam name="T">The type of the elements.</typeparam>
[StructLayout(LayoutKind.Sequential)]
[NativeContainer]
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public unsafe struct NativeQueue<T>
: INativeDisposable
where T : struct
{
[NativeDisableUnsafePtrRestriction]
NativeQueueData* m_Buffer;
[NativeDisableUnsafePtrRestriction]
NativeQueueBlockPoolData* m_QueuePool;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle m_Safety;
static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<NativeQueue<T>>();
#if REMOVE_DISPOSE_SENTINEL
#else
[NativeSetClassTypeToNullOnSchedule]
DisposeSentinel m_DisposeSentinel;
#endif
#endif
AllocatorManager.AllocatorHandle m_AllocatorLabel;
/// <summary>
/// Initializes and returns an instance of NativeQueue.
/// </summary>
/// <param name="allocator">The allocator to use.</param>
public NativeQueue(AllocatorManager.AllocatorHandle allocator)
{
CollectionHelper.CheckIsUnmanaged<T>();
m_QueuePool = NativeQueueBlockPool.GetQueueBlockPool();
m_AllocatorLabel = allocator;
NativeQueueData.AllocateQueue<T>(allocator, out m_Buffer);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
m_Safety = CollectionHelper.CreateSafetyHandle(allocator);
#else
if (allocator.IsCustomAllocator)
{
m_Safety = AtomicSafetyHandle.Create();
m_DisposeSentinel = null;
}
else
{
DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 0, allocator.ToAllocator);
}
#endif
CollectionHelper.SetStaticSafetyId<NativeQueue<T>>(ref m_Safety, ref s_staticSafetyId.Data);
#endif
}
/// <summary>
/// Returns true if this queue is empty.
/// </summary>
/// <value>True if this queue has no items or if the queue has not been constructed.</value>
public bool IsEmpty()
{
if (!IsCreated)
{
return true;
}
CheckRead();
int count = 0;
var currentRead = m_Buffer->m_CurrentRead;
for (NativeQueueBlockHeader* block = (NativeQueueBlockHeader*)m_Buffer->m_FirstBlock
; block != null
; block = block->m_NextBlock
)
{
count += block->m_NumItems;
if (count > currentRead)
{
return false;
}
}
return count == currentRead;
}
/// <summary>
/// Returns the current number of elements in this queue.
/// </summary>
/// <remarks>Note that getting the count requires traversing the queue's internal linked list of blocks.
/// Where possible, cache this value instead of reading the property repeatedly.</remarks>
/// <returns>The current number of elements in this queue.</returns>
public int Count
{
get
{
CheckRead();
int count = 0;
for (NativeQueueBlockHeader* block = (NativeQueueBlockHeader*)m_Buffer->m_FirstBlock
; block != null
; block = block->m_NextBlock
)
{
count += block->m_NumItems;
}
return count - m_Buffer->m_CurrentRead;
}
}
internal static int PersistentMemoryBlockCount
{
get { return NativeQueueBlockPool.GetQueueBlockPool()->m_MaxBlocks; }
set { Interlocked.Exchange(ref NativeQueueBlockPool.GetQueueBlockPool()->m_MaxBlocks, value); }
}
internal static int MemoryBlockSize
{
get { return NativeQueueBlockPoolData.m_BlockSize; }
}
/// <summary>
/// Returns the element at the end of this queue without removing it.
/// </summary>
/// <returns>The element at the end of this queue.</returns>
public T Peek()
{
CheckReadNotEmpty();
NativeQueueBlockHeader* firstBlock = (NativeQueueBlockHeader*)m_Buffer->m_FirstBlock;
return UnsafeUtility.ReadArrayElement<T>(firstBlock + 1, m_Buffer->m_CurrentRead);
}
/// <summary>
/// Adds an element at the front of this queue.
/// </summary>
/// <param name="value">The value to be enqueued.</param>
public void Enqueue(T value)
{
CheckWrite();
NativeQueueBlockHeader* writeBlock = NativeQueueData.AllocateWriteBlockMT<T>(m_Buffer, m_QueuePool, 0);
UnsafeUtility.WriteArrayElement(writeBlock + 1, writeBlock->m_NumItems, value);
++writeBlock->m_NumItems;
}
/// <summary>
/// Removes and returns the element at the end of this queue.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if this queue is empty.</exception>
/// <returns>The element at the end of this queue.</returns>
public T Dequeue()
{
if (!TryDequeue(out T item))
{
ThrowEmpty();
}
return item;
}
/// <summary>
/// Removes and outputs the element at the end of this queue.
/// </summary>
/// <param name="item">Outputs the removed element.</param>
/// <returns>True if this queue was not empty.</returns>
public bool TryDequeue(out T item)
{
CheckWrite();
NativeQueueBlockHeader* firstBlock = (NativeQueueBlockHeader*)m_Buffer->m_FirstBlock;
if (firstBlock == null)
{
item = default(T);
return false;
}
var currentRead = m_Buffer->m_CurrentRead++;
item = UnsafeUtility.ReadArrayElement<T>(firstBlock + 1, currentRead);
if (m_Buffer->m_CurrentRead >= firstBlock->m_NumItems)
{
m_Buffer->m_CurrentRead = 0;
m_Buffer->m_FirstBlock = (IntPtr)firstBlock->m_NextBlock;
if (m_Buffer->m_FirstBlock == IntPtr.Zero)
{
m_Buffer->m_LastBlock = IntPtr.Zero;
}
for (int threadIndex = 0; threadIndex < JobsUtility.MaxJobThreadCount; ++threadIndex)
{
if (m_Buffer->GetCurrentWriteBlockTLS(threadIndex) == firstBlock)
{
m_Buffer->SetCurrentWriteBlockTLS(threadIndex, null);
}
}
m_QueuePool->FreeBlock(firstBlock);
}
return true;
}
/// <summary>
/// Returns an array containing a copy of this queue's content.
/// </summary>
/// <param name="allocator">The allocator to use.</param>
/// <returns>An array containing a copy of this queue's content. The elements are ordered in the same order they were
/// enqueued, *e.g.* the earliest enqueued element is copied to index 0 of the array.</returns>
public NativeArray<T> ToArray(AllocatorManager.AllocatorHandle allocator)
{
CheckRead();
NativeQueueBlockHeader* firstBlock = (NativeQueueBlockHeader*)m_Buffer->m_FirstBlock;
var outputArray = CollectionHelper.CreateNativeArray<T>(Count, allocator);
NativeQueueBlockHeader* currentBlock = firstBlock;
var arrayPtr = (byte*)outputArray.GetUnsafePtr();
int size = UnsafeUtility.SizeOf<T>();
int dstOffset = 0;
int srcOffset = m_Buffer->m_CurrentRead * size;
int srcOffsetElements = m_Buffer->m_CurrentRead;
while (currentBlock != null)
{
int bytesToCopy = (currentBlock->m_NumItems - srcOffsetElements) * size;
UnsafeUtility.MemCpy(arrayPtr + dstOffset, (byte*)(currentBlock + 1) + srcOffset, bytesToCopy);
srcOffset = srcOffsetElements = 0;
dstOffset += bytesToCopy;
currentBlock = currentBlock->m_NextBlock;
}
return outputArray;
}
/// <summary>
/// Removes all elements of this queue.
/// </summary>
/// <remarks>Does not change the capacity.</remarks>
public void Clear()
{
CheckWrite();
NativeQueueBlockHeader* firstBlock = (NativeQueueBlockHeader*)m_Buffer->m_FirstBlock;
while (firstBlock != null)
{
NativeQueueBlockHeader* next = firstBlock->m_NextBlock;
m_QueuePool->FreeBlock(firstBlock);
firstBlock = next;
}
m_Buffer->m_FirstBlock = IntPtr.Zero;
m_Buffer->m_LastBlock = IntPtr.Zero;
m_Buffer->m_CurrentRead = 0;
for (int threadIndex = 0; threadIndex < JobsUtility.MaxJobThreadCount; ++threadIndex)
{
m_Buffer->SetCurrentWriteBlockTLS(threadIndex, null);
}
}
/// <summary>
/// Whether this queue has been allocated (and not yet deallocated).
/// </summary>
/// <value>True if this queue has been allocated (and not yet deallocated).</value>
public bool IsCreated => m_Buffer != null;
/// <summary>
/// Releases all resources (memory and safety handles).
/// </summary>
public void Dispose()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
CollectionHelper.DisposeSafetyHandle(ref m_Safety);
#else
DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel);
#endif
#endif
NativeQueueData.DeallocateQueue(m_Buffer, m_QueuePool, m_AllocatorLabel);
m_Buffer = null;
}
/// <summary>
/// Creates and schedules a job that releases all resources (memory and safety handles) of this queue.
/// </summary>
/// <param name="inputDeps">The dependency for the new job.</param>
/// <returns>The handle of the new job. The job depends upon `inputDeps` and releases all resources (memory and safety handles) of this queue.</returns>
[NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
public JobHandle Dispose(JobHandle inputDeps)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
#else
// [DeallocateOnJobCompletion] is not supported, but we want the deallocation
// to happen in a thread. DisposeSentinel needs to be cleared on main thread.
// AtomicSafetyHandle can be destroyed after the job was scheduled (Job scheduling
// will check that no jobs are writing to the container).
DisposeSentinel.Clear(ref m_DisposeSentinel);
#endif
var jobHandle = new NativeQueueDisposeJob { Data = new NativeQueueDispose { m_Buffer = m_Buffer, m_QueuePool = m_QueuePool, m_AllocatorLabel = m_AllocatorLabel, m_Safety = m_Safety } }.Schedule(inputDeps);
AtomicSafetyHandle.Release(m_Safety);
#else
var jobHandle = new NativeQueueDisposeJob { Data = new NativeQueueDispose { m_Buffer = m_Buffer, m_QueuePool = m_QueuePool, m_AllocatorLabel = m_AllocatorLabel } }.Schedule(inputDeps);
#endif
m_Buffer = null;
return jobHandle;
}
/// <summary>
/// Returns a parallel writer for this queue.
/// </summary>
/// <returns>A parallel writer for this queue.</returns>
public ParallelWriter AsParallelWriter()
{
ParallelWriter writer;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
writer.m_Safety = m_Safety;
CollectionHelper.SetStaticSafetyId<ParallelWriter>(ref writer.m_Safety, ref ParallelWriter.s_staticSafetyId.Data);
#endif
writer.m_Buffer = m_Buffer;
writer.m_QueuePool = m_QueuePool;
writer.m_ThreadIndex = 0;
return writer;
}
/// <summary>
/// A parallel writer for a NativeQueue.
/// </summary>
/// <remarks>
/// Use <see cref="AsParallelWriter"/> to create a parallel writer for a NativeQueue.
/// </remarks>
[NativeContainer]
[NativeContainerIsAtomicWriteOnly]
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public unsafe struct ParallelWriter
{
[NativeDisableUnsafePtrRestriction]
internal NativeQueueData* m_Buffer;
[NativeDisableUnsafePtrRestriction]
internal NativeQueueBlockPoolData* m_QueuePool;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
internal AtomicSafetyHandle m_Safety;
internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<ParallelWriter>();
#endif
[NativeSetThreadIndex]
internal int m_ThreadIndex;
/// <summary>
/// Adds an element at the front of the queue.
/// </summary>
/// <param name="value">The value to be enqueued.</param>
public void Enqueue(T value)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
#endif
NativeQueueBlockHeader* writeBlock = NativeQueueData.AllocateWriteBlockMT<T>(m_Buffer, m_QueuePool, m_ThreadIndex);
UnsafeUtility.WriteArrayElement(writeBlock + 1, writeBlock->m_NumItems, value);
++writeBlock->m_NumItems;
}
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckRead()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckReadNotEmpty()
{
CheckRead();
if (m_Buffer->m_FirstBlock == (IntPtr)0)
{
ThrowEmpty();
}
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckWrite()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
#endif
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
static void ThrowEmpty()
{
throw new InvalidOperationException("Trying to read from an empty queue.");
}
}
[NativeContainer]
[BurstCompatible]
internal unsafe struct NativeQueueDispose
{
[NativeDisableUnsafePtrRestriction]
internal NativeQueueData* m_Buffer;
[NativeDisableUnsafePtrRestriction]
internal NativeQueueBlockPoolData* m_QueuePool;
internal AllocatorManager.AllocatorHandle m_AllocatorLabel;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
internal AtomicSafetyHandle m_Safety;
#endif
public void Dispose()
{
NativeQueueData.DeallocateQueue(m_Buffer, m_QueuePool, m_AllocatorLabel);
}
}
[BurstCompile]
struct NativeQueueDisposeJob : IJob
{
public NativeQueueDispose Data;
public void Execute()
{
Data.Dispose();
}
}
}

View file

@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 2d8c01f04a0874c9384ae360c07d0098
timeCreated: 1503921931
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,439 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
namespace Unity.Collections
{
/// <summary>
/// An unmanaged single value.
/// </summary>
/// <remarks>The functional equivalent of an array of length 1.
/// When you need just one value, NativeReference can be preferable to an array because it better conveys the intent.</remarks>
/// <typeparam name="T">The type of value.</typeparam>
[StructLayout(LayoutKind.Sequential)]
[NativeContainer]
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public unsafe struct NativeReference<T>
: INativeDisposable
, IEquatable<NativeReference<T>>
where T : unmanaged
{
[NativeDisableUnsafePtrRestriction]
internal void* m_Data;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
internal AtomicSafetyHandle m_Safety;
static readonly SharedStatic<int> s_SafetyId = SharedStatic<int>.GetOrCreate<NativeReference<T>>();
#if REMOVE_DISPOSE_SENTINEL
#else
[NativeSetClassTypeToNullOnSchedule]
DisposeSentinel m_DisposeSentinel;
#endif
#endif
internal AllocatorManager.AllocatorHandle m_AllocatorLabel;
/// <summary>
/// Initializes and returns an instance of NativeReference.
/// </summary>
/// <param name="allocator">The allocator to use.</param>
/// <param name="options">Whether newly allocated bytes should be zeroed out.</param>
public NativeReference(AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory)
{
Allocate(allocator, out this);
if (options == NativeArrayOptions.ClearMemory)
{
UnsafeUtility.MemClear(m_Data, UnsafeUtility.SizeOf<T>());
}
}
/// <summary>
/// Initializes and returns an instance of NativeReference.
/// </summary>
/// <param name="allocator">The allocator to use.</param>
/// <param name="value">The initial value.</param>
public NativeReference(T value, AllocatorManager.AllocatorHandle allocator)
{
Allocate(allocator, out this);
*(T*)m_Data = value;
}
static void Allocate(AllocatorManager.AllocatorHandle allocator, out NativeReference<T> reference)
{
CollectionHelper.CheckAllocator(allocator);
reference = default;
reference.m_Data = Memory.Unmanaged.Allocate(UnsafeUtility.SizeOf<T>(), UnsafeUtility.AlignOf<T>(), allocator);
reference.m_AllocatorLabel = allocator;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
reference.m_Safety = CollectionHelper.CreateSafetyHandle(allocator);
#else
if (allocator.IsCustomAllocator)
{
reference.m_Safety = AtomicSafetyHandle.Create();
reference.m_DisposeSentinel = null;
}
else
{
DisposeSentinel.Create(out reference.m_Safety, out reference.m_DisposeSentinel, 1, allocator.ToAllocator);
}
#endif
CollectionHelper.SetStaticSafetyId<NativeQueue<T>>(ref reference.m_Safety, ref s_SafetyId.Data);
#endif
}
/// <summary>
/// The value stored in this reference.
/// </summary>
/// <param name="value">The new value to store in this reference.</param>
/// <value>The value stored in this reference.</value>
public T Value
{
get
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
return *(T*)m_Data;
}
set
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
#endif
*(T*)m_Data = value;
}
}
/// <summary>
/// Whether this reference has been allocated (and not yet deallocated).
/// </summary>
/// <value>True if this reference has been allocated (and not yet deallocated).</value>
public bool IsCreated => m_Data != null;
/// <summary>
/// Releases all resources (memory and safety handles).
/// </summary>
public void Dispose()
{
CheckNotDisposed();
if (CollectionHelper.ShouldDeallocate(m_AllocatorLabel))
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
CollectionHelper.DisposeSafetyHandle(ref m_Safety);
#else
DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel);
#endif
#endif
Memory.Unmanaged.Free(m_Data, m_AllocatorLabel);
m_AllocatorLabel = Allocator.Invalid;
}
m_Data = null;
}
/// <summary>
/// Creates and schedules a job that will release all resources (memory and safety handles) of this reference.
/// </summary>
/// <param name="inputDeps">A job handle. The newly scheduled job will depend upon this handle.</param>
/// <returns>The handle of a new job that will release all resources (memory and safety handles) of this reference.</returns>
[NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
public JobHandle Dispose(JobHandle inputDeps)
{
CheckNotDisposed();
if (CollectionHelper.ShouldDeallocate(m_AllocatorLabel))
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
#else
// [DeallocateOnJobCompletion] is not supported, but we want the deallocation
// to happen in a thread. DisposeSentinel needs to be cleared on main thread.
// AtomicSafetyHandle can be destroyed after the job was scheduled (Job scheduling
// will check that no jobs are writing to the container).
DisposeSentinel.Clear(ref m_DisposeSentinel);
#endif
#endif
var jobHandle = new NativeReferenceDisposeJob
{
Data = new NativeReferenceDispose
{
m_Data = m_Data,
m_AllocatorLabel = m_AllocatorLabel,
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_Safety = m_Safety
#endif
}
}.Schedule(inputDeps);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.Release(m_Safety);
#endif
m_Data = null;
m_AllocatorLabel = Allocator.Invalid;
return jobHandle;
}
m_Data = null;
return inputDeps;
}
/// <summary>
/// Copy the value of another reference to this reference.
/// </summary>
/// <param name="reference">The reference to copy from.</param>
public void CopyFrom(NativeReference<T> reference)
{
Copy(this, reference);
}
/// <summary>
/// Copy the value of this reference to another reference.
/// </summary>
/// <param name="reference">The reference to copy to.</param>
public void CopyTo(NativeReference<T> reference)
{
Copy(reference, this);
}
/// <summary>
/// Returns true if the value stored in this reference is equal to the value stored in another reference.
/// </summary>
/// <param name="other">A reference to compare with.</param>
/// <returns>True if the value stored in this reference is equal to the value stored in another reference.</returns>
[NotBurstCompatible]
public bool Equals(NativeReference<T> other)
{
return Value.Equals(other.Value);
}
/// <summary>
/// Returns true if the value stored in this reference is equal to an object.
/// </summary>
/// <remarks>Can only be equal if the object is itself a NativeReference.</remarks>
/// <param name="obj">An object to compare with.</param>
/// <returns>True if the value stored in this reference is equal to the object.</returns>
[NotBurstCompatible]
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is NativeReference<T> && Equals((NativeReference<T>)obj);
}
/// <summary>
/// Returns the hash code of this reference.
/// </summary>
/// <returns>The hash code of this reference.</returns>
public override int GetHashCode()
{
return Value.GetHashCode();
}
/// <summary>
/// Returns true if the values stored in two references are equal.
/// </summary>
/// <param name="left">A reference.</param>
/// <param name="right">Another reference.</param>
/// <returns>True if the two values are equal.</returns>
public static bool operator ==(NativeReference<T> left, NativeReference<T> right)
{
return left.Equals(right);
}
/// <summary>
/// Returns true if the values stored in two references are unequal.
/// </summary>
/// <param name="left">A reference.</param>
/// <param name="right">Another reference.</param>
/// <returns>True if the two values are unequal.</returns>
public static bool operator !=(NativeReference<T> left, NativeReference<T> right)
{
return !left.Equals(right);
}
/// <summary>
/// Copies the value of a reference to another reference.
/// </summary>
/// <param name="dst">The destination reference.</param>
/// <param name="src">The source reference.</param>
public static void Copy(NativeReference<T> dst, NativeReference<T> src)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(src.m_Safety);
AtomicSafetyHandle.CheckWriteAndThrow(dst.m_Safety);
#endif
UnsafeUtility.MemCpy(dst.m_Data, src.m_Data, UnsafeUtility.SizeOf<T>());
}
/// <summary>
/// Returns a read-only reference aliasing the value of this reference.
/// </summary>
/// <returns>A read-only reference aliasing the value of this reference.</returns>
public ReadOnly AsReadOnly()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
return new ReadOnly(m_Data, ref m_Safety);
#else
return new ReadOnly(m_Data);
#endif
}
/// <summary>
/// A read-only alias for the value of a NativeReference. Does not have its own allocated storage.
/// </summary>
[NativeContainer]
[NativeContainerIsReadOnly]
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public unsafe struct ReadOnly
{
[NativeDisableUnsafePtrRestriction]
readonly void* m_Data;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle m_Safety;
internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<ReadOnly>();
[BurstCompatible(CompileTarget = BurstCompatibleAttribute.BurstCompatibleCompileTarget.Editor)]
internal ReadOnly(void* data, ref AtomicSafetyHandle safety)
{
m_Data = data;
m_Safety = safety;
CollectionHelper.SetStaticSafetyId<ReadOnly>(ref m_Safety, ref s_staticSafetyId.Data);
}
#else
internal ReadOnly(void* data)
{
m_Data = data;
}
#endif
/// <summary>
/// The value aliased by this reference.
/// </summary>
/// <value>The value aliased by the reference.</value>
public T Value
{
get
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
return *(T*)m_Data;
}
}
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckNotDisposed()
{
if (m_Data == null)
throw new ObjectDisposedException("The NativeReference is already disposed.");
}
}
[NativeContainer]
unsafe struct NativeReferenceDispose
{
[NativeDisableUnsafePtrRestriction]
internal void* m_Data;
internal AllocatorManager.AllocatorHandle m_AllocatorLabel;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
internal AtomicSafetyHandle m_Safety;
#endif
public void Dispose()
{
Memory.Unmanaged.Free(m_Data, m_AllocatorLabel);
}
}
[BurstCompile]
struct NativeReferenceDisposeJob : IJob
{
internal NativeReferenceDispose Data;
public void Execute()
{
Data.Dispose();
}
}
}
namespace Unity.Collections.LowLevel.Unsafe
{
/// <summary>
/// Provides extension methods for NativeReference.
/// </summary>
[BurstCompatible]
public static class NativeReferenceUnsafeUtility
{
/// <summary>
/// Returns a pointer to this reference's stored value.
/// </summary>
/// <remarks>Performs a job safety check for read-write access.</remarks>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="reference">The reference.</param>
/// <returns>A pointer to this reference's stored value.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public static unsafe void* GetUnsafePtr<T>(this NativeReference<T> reference)
where T : unmanaged
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndThrow(reference.m_Safety);
#endif
return reference.m_Data;
}
/// <summary>
/// Returns a pointer to this reference's stored value.
/// </summary>
/// <remarks>Performs a job safety check for read-only access.</remarks>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="reference">The reference.</param>
/// <returns>A pointer to this reference's stored value.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public static unsafe void* GetUnsafeReadOnlyPtr<T>(this NativeReference<T> reference)
where T : unmanaged
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(reference.m_Safety);
#endif
return reference.m_Data;
}
/// <summary>
/// Returns a pointer to this reference's stored value.
/// </summary>
/// <remarks>Performs no job safety checks.</remarks>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="reference">The reference.</param>
/// <returns>A pointer to this reference's stored value.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public static unsafe void* GetUnsafePtrWithoutChecks<T>(this NativeReference<T> reference)
where T : unmanaged
{
return reference.m_Data;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ca5dd80efed23904a980e0f7fb23ed5d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: ea14c18964a134080bea2f114390f80b
timeCreated: 1492169478
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,754 @@
using System;
using System.Diagnostics;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Burst;
using Unity.Jobs;
using UnityEngine.Assertions;
namespace Unity.Collections
{
/// <summary>
/// A set of untyped, append-only buffers. Allows for concurrent reading and concurrent writing without synchronization.
/// </summary>
/// <remarks>
/// As long as each individual buffer is written in one thread and read in one thread, multiple
/// threads can read and write the stream concurrently, *e.g.*
/// while thread *A* reads from buffer *X* of a stream, thread *B* can read from
/// buffer *Y* of the same stream.
///
/// Each buffer is stored as a chain of blocks. When a write exceeds a buffer's current capacity, another block
/// is allocated and added to the end of the chain. Effectively, expanding the buffer never requires copying the existing
/// data (unlike with <see cref="NativeList{T}"/>, for example).
///
/// **All writing to a stream should be completed before the stream is first read. Do not write to a stream after the first read.**
/// Violating these rules won't *necessarily* cause any problems, but they are the intended usage pattern.
///
/// Writing is done with <see cref="NativeStream.Writer"/>, and reading is done with <see cref="NativeStream.Reader"/>.
/// An individual reader or writer cannot be used concurrently across threads: each thread must use its own.
///
/// The data written to an individual buffer can be heterogeneous in type, and the data written
/// to different buffers of a stream can be entirely different in type, number, and order. Just make sure
/// that the code reading from a particular buffer knows what to expect to read from it.
/// </remarks>
[NativeContainer]
[BurstCompatible]
public unsafe struct NativeStream : IDisposable
{
UnsafeStream m_Stream;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle m_Safety;
internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<NativeStream>();
#if REMOVE_DISPOSE_SENTINEL
#else
[NativeSetClassTypeToNullOnSchedule]
DisposeSentinel m_DisposeSentinel;
#endif
#endif
/// <summary>
/// Initializes and returns an instance of NativeStream.
/// </summary>
/// <param name="bufferCount">The number of buffers to give the stream. You usually want
/// one buffer for each thread that will read or write the stream.</param>
/// <param name="allocator">The allocator to use.</param>
public NativeStream(int bufferCount, AllocatorManager.AllocatorHandle allocator)
{
AllocateBlock(out this, allocator);
m_Stream.AllocateForEach(bufferCount);
}
/// <summary>
/// Creates and schedules a job to allocate a new stream.
/// </summary>
/// <remarks>The stream can be used on the main thread after completing the returned job or used in other jobs that depend upon the returned job.
///
/// Using a job to allocate the buffers can be more efficient, particularly for a stream with many buffers.
/// </remarks>
/// <typeparam name="T">Ignored.</typeparam>
/// <param name="stream">Outputs the new stream.</param>
/// <param name="bufferCount">A list whose length determines the number of buffers in the stream.</param>
/// <param name="dependency">A job handle. The new job will depend upon this handle.</param>
/// <param name="allocator">The allocator to use.</param>
/// <returns>The handle of the new job.</returns>
[NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
public static JobHandle ScheduleConstruct<T>(out NativeStream stream, NativeList<T> bufferCount, JobHandle dependency, AllocatorManager.AllocatorHandle allocator)
where T : unmanaged
{
AllocateBlock(out stream, allocator);
var jobData = new ConstructJobList { List = (UntypedUnsafeList*)bufferCount.GetUnsafeList(), Container = stream };
return jobData.Schedule(dependency);
}
/// <summary>
/// Creates and schedules a job to allocate a new stream.
/// </summary>
/// <remarks>The stream can be used...
/// - after completing the returned job
/// - or in other jobs that depend upon the returned job.
///
/// Allocating the buffers in a job can be more efficient, particularly for a stream with many buffers.
/// </remarks>
/// <param name="stream">Outputs the new stream.</param>
/// <param name="bufferCount">An array whose value at index 0 determines the number of buffers in the stream.</param>
/// <param name="dependency">A job handle. The new job will depend upon this handle.</param>
/// <param name="allocator">The allocator to use.</param>
/// <returns>The handle of the new job.</returns>
[NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
public static JobHandle ScheduleConstruct(out NativeStream stream, NativeArray<int> bufferCount, JobHandle dependency, AllocatorManager.AllocatorHandle allocator)
{
AllocateBlock(out stream, allocator);
var jobData = new ConstructJob { Length = bufferCount, Container = stream };
return jobData.Schedule(dependency);
}
/// <summary>
/// Returns true if this stream is empty.
/// </summary>
/// <returns>True if this stream is empty or the stream has not been constructed.</returns>
public bool IsEmpty()
{
CheckReadAccess();
return m_Stream.IsEmpty();
}
/// <summary>
/// Whether this stream has been allocated (and not yet deallocated).
/// </summary>
/// <remarks>Does not necessarily reflect whether the buffers of the stream have themselves been allocated.</remarks>
/// <value>True if this stream has been allocated (and not yet deallocated).</value>
public bool IsCreated => m_Stream.IsCreated;
/// <summary>
/// The number of buffers in this stream.
/// </summary>
/// <value>The number of buffers in this stream.</value>
public int ForEachCount
{
get
{
CheckReadAccess();
return m_Stream.ForEachCount;
}
}
/// <summary>
/// Returns a reader of this stream.
/// </summary>
/// <returns>A reader of this stream.</returns>
public Reader AsReader()
{
return new Reader(ref this);
}
/// <summary>
/// Returns a writer of this stream.
/// </summary>
/// <returns>A writer of this stream.</returns>
public Writer AsWriter()
{
return new Writer(ref this);
}
/// <summary>
/// Returns the total number of items in the buffers of this stream.
/// </summary>
/// <remarks>Each <see cref="Writer.Write{T}"/> and <see cref="Writer.Allocate"/> call increments this number.</remarks>
/// <returns>The total number of items in the buffers of this stream.</returns>
public int Count()
{
CheckReadAccess();
return m_Stream.Count();
}
/// <summary>
/// Returns a new NativeArray copy of this stream's data.
/// </summary>
/// <remarks>The length of the array will equal the count of this stream.
///
/// Each buffer of this stream is copied to the array, one after the other.
/// </remarks>
/// <typeparam name="T">The type of values in the array.</typeparam>
/// <param name="allocator">The allocator to use.</param>
/// <returns>A new NativeArray copy of this stream's data.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public NativeArray<T> ToNativeArray<T>(AllocatorManager.AllocatorHandle allocator) where T : struct
{
CheckReadAccess();
return m_Stream.ToNativeArray<T>(allocator);
}
/// <summary>
/// Releases all resources (memory and safety handles).
/// </summary>
public void Dispose()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
CollectionHelper.DisposeSafetyHandle(ref m_Safety);
#else
DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel);
#endif
#endif
m_Stream.Dispose();
}
/// <summary>
/// Creates and schedules a job that will release all resources (memory and safety handles) of this stream.
/// </summary>
/// <param name="inputDeps">A job handle which the newly scheduled job will depend upon.</param>
/// <returns>The handle of a new job that will release all resources (memory and safety handles) of this stream.</returns>
[NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
public JobHandle Dispose(JobHandle inputDeps)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
#else
// [DeallocateOnJobCompletion] is not supported, but we want the deallocation
// to happen in a thread. DisposeSentinel needs to be cleared on main thread.
// AtomicSafetyHandle can be destroyed after the job was scheduled (Job scheduling
// will check that no jobs are writing to the container).
DisposeSentinel.Clear(ref m_DisposeSentinel);
#endif
#endif
var jobHandle = m_Stream.Dispose(inputDeps);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.Release(m_Safety);
#endif
return jobHandle;
}
[BurstCompile]
struct ConstructJobList : IJob
{
public NativeStream Container;
[ReadOnly]
[NativeDisableUnsafePtrRestriction]
public UntypedUnsafeList* List;
public void Execute()
{
Container.AllocateForEach(List->m_length);
}
}
[BurstCompile]
struct ConstructJob : IJob
{
public NativeStream Container;
[ReadOnly]
public NativeArray<int> Length;
public void Execute()
{
Container.AllocateForEach(Length[0]);
}
}
static void AllocateBlock(out NativeStream stream, AllocatorManager.AllocatorHandle allocator)
{
CollectionHelper.CheckAllocator(allocator);
UnsafeStream.AllocateBlock(out stream.m_Stream, allocator);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
#if REMOVE_DISPOSE_SENTINEL
stream.m_Safety = CollectionHelper.CreateSafetyHandle(allocator);
#else
if (allocator.IsCustomAllocator)
{
stream.m_Safety = AtomicSafetyHandle.Create();
stream.m_DisposeSentinel = null;
}
else
{
DisposeSentinel.Create(out stream.m_Safety, out stream.m_DisposeSentinel, 0, allocator.ToAllocator);
}
#endif
CollectionHelper.SetStaticSafetyId(ref stream.m_Safety, ref s_staticSafetyId.Data, "Unity.Collections.NativeStream");
#endif
}
void AllocateForEach(int forEachCount)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
CheckForEachCountGreaterThanZero(forEachCount);
Assert.IsTrue(m_Stream.m_Block->Ranges == null);
Assert.AreEqual(0, m_Stream.m_Block->RangeCount);
Assert.AreNotEqual(0, m_Stream.m_Block->BlockCount);
#endif
m_Stream.AllocateForEach(forEachCount);
}
/// <summary>
/// Writes data into a buffer of a <see cref="NativeStream"/>.
/// </summary>
/// <remarks>An individual writer can only be used for one buffer of one stream.
/// Do not create more than one writer for an individual buffer.</remarks>
[NativeContainer]
[NativeContainerSupportsMinMaxWriteRestriction]
[BurstCompatible]
public unsafe struct Writer
{
UnsafeStream.Writer m_Writer;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle m_Safety;
internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<Writer>();
#pragma warning disable CS0414 // warning CS0414: The field 'NativeStream.Writer.m_Length' is assigned but its value is never used
int m_Length;
#pragma warning restore CS0414
int m_MinIndex;
int m_MaxIndex;
[NativeDisableUnsafePtrRestriction]
void* m_PassByRefCheck;
#endif
internal Writer(ref NativeStream stream)
{
m_Writer = stream.m_Stream.AsWriter();
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_Safety = stream.m_Safety;
CollectionHelper.SetStaticSafetyId(ref m_Safety, ref s_staticSafetyId.Data, "Unity.Collections.NativeStream.Writer");
m_Length = int.MaxValue;
m_MinIndex = int.MinValue;
m_MaxIndex = int.MinValue;
m_PassByRefCheck = null;
#endif
}
/// <summary>
/// The number of buffers in the stream of this writer.
/// </summary>
/// <value>The number of buffers in the stream of this writer.</value>
public int ForEachCount
{
get
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
#endif
return m_Writer.ForEachCount;
}
}
/// <summary>
/// For internal use only.
/// </summary>
/// <param name="foreEachIndex"></param>
public void PatchMinMaxRange(int foreEachIndex)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_MinIndex = foreEachIndex;
m_MaxIndex = foreEachIndex;
#endif
}
/// <summary>
/// Readies this writer to write to a particular buffer of the stream.
/// </summary>
/// <remarks>Must be called before using this writer. For an individual writer, call this method only once.
///
/// When done using this writer, you must call <see cref="EndForEachIndex"/>.</remarks>
/// <param name="foreachIndex">The index of the buffer to write.</param>
public void BeginForEachIndex(int foreachIndex)
{
//@TODO: Check that no one writes to the same for each index multiple times...
CheckBeginForEachIndex(foreachIndex);
m_Writer.BeginForEachIndex(foreachIndex);
}
/// <summary>
/// Readies the buffer written by this writer for reading.
/// </summary>
/// <remarks>Must be called before reading the buffer written by this writer.</remarks>
public void EndForEachIndex()
{
CheckEndForEachIndex();
m_Writer.EndForEachIndex();
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_Writer.m_ForeachIndex = int.MinValue;
#endif
}
/// <summary>
/// Write a value to a buffer.
/// </summary>
/// <remarks>The value is written to the buffer which was specified
/// with <see cref="BeginForEachIndex"/>.</remarks>
/// <typeparam name="T">The type of value to write.</typeparam>
/// <param name="value">The value to write.</param>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public void Write<T>(T value) where T : struct
{
ref T dst = ref Allocate<T>();
dst = value;
}
/// <summary>
/// Allocate space in a buffer.
/// </summary>
/// <remarks>The space is allocated in the buffer which was specified
/// with <see cref="BeginForEachIndex"/>.</remarks>
/// <typeparam name="T">The type of value to allocate space for.</typeparam>
/// <returns>A reference to the allocation.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public ref T Allocate<T>() where T : struct
{
CollectionHelper.CheckIsUnmanaged<T>();
int size = UnsafeUtility.SizeOf<T>();
return ref UnsafeUtility.AsRef<T>(Allocate(size));
}
/// <summary>
/// Allocate space in a buffer.
/// </summary>
/// <remarks>The space is allocated in the buffer which was specified
/// with <see cref="BeginForEachIndex"/>.</remarks>
/// <param name="size">The number of bytes to allocate.</param>
/// <returns>The allocation.</returns>
public byte* Allocate(int size)
{
CheckAllocateSize(size);
return m_Writer.Allocate(size);
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckBeginForEachIndex(int foreachIndex)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
if (m_PassByRefCheck == null)
{
m_PassByRefCheck = UnsafeUtility.AddressOf(ref this);
}
if (foreachIndex < m_MinIndex || foreachIndex > m_MaxIndex)
{
// When the code is not running through the job system no ParallelForRange patching will occur
// We can't grab m_BlockStream->RangeCount on creation of the writer because the RangeCount can be initialized
// in a job after creation of the writer
if (m_MinIndex == int.MinValue && m_MaxIndex == int.MinValue)
{
m_MinIndex = 0;
m_MaxIndex = m_Writer.m_BlockStream->RangeCount - 1;
}
if (foreachIndex < m_MinIndex || foreachIndex > m_MaxIndex)
{
throw new ArgumentException($"Index {foreachIndex} is out of restricted IJobParallelFor range [{m_MinIndex}...{m_MaxIndex}] in NativeStream.");
}
}
if (m_Writer.m_ForeachIndex != int.MinValue)
{
throw new ArgumentException($"BeginForEachIndex must always be balanced by a EndForEachIndex call");
}
if (0 != m_Writer.m_BlockStream->Ranges[foreachIndex].ElementCount)
{
throw new ArgumentException($"BeginForEachIndex can only be called once for the same index ({foreachIndex}).");
}
Assert.IsTrue(foreachIndex >= 0 && foreachIndex < m_Writer.m_BlockStream->RangeCount);
#endif
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckEndForEachIndex()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
if (m_Writer.m_ForeachIndex == int.MinValue)
{
throw new System.ArgumentException("EndForEachIndex must always be called balanced by a BeginForEachIndex or AppendForEachIndex call");
}
#endif
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckAllocateSize(int size)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
if (m_PassByRefCheck != UnsafeUtility.AddressOf(ref this))
{
throw new ArgumentException("NativeStream.Writer must be passed by ref once it is in use");
}
if (m_Writer.m_ForeachIndex == int.MinValue)
{
throw new ArgumentException("Allocate must be called within BeginForEachIndex / EndForEachIndex");
}
if (size > UnsafeStreamBlockData.AllocationSize - sizeof(void*))
{
throw new ArgumentException("Allocation size is too large");
}
#endif
}
}
/// <summary>
/// Reads data from a buffer of a <see cref="NativeStream"/>.
/// </summary>
/// <remarks>An individual reader can only be used for one buffer of one stream.
/// Do not create more than one reader for an individual buffer.</remarks>
[NativeContainer]
[NativeContainerIsReadOnly]
[BurstCompatible]
public unsafe struct Reader
{
UnsafeStream.Reader m_Reader;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
int m_RemainingBlocks;
internal AtomicSafetyHandle m_Safety;
internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<Reader>();
#endif
internal Reader(ref NativeStream stream)
{
m_Reader = stream.m_Stream.AsReader();
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_RemainingBlocks = 0;
m_Safety = stream.m_Safety;
CollectionHelper.SetStaticSafetyId(ref m_Safety, ref s_staticSafetyId.Data, "Unity.Collections.NativeStream.Reader");
#endif
}
/// <summary>
/// Readies this reader to read a particular buffer of the stream.
/// </summary>
/// <remarks>Must be called before using this reader. For an individual reader, call this method only once.
///
/// When done using this reader, you must call <see cref="EndForEachIndex"/>.</remarks>
/// <param name="foreachIndex">The index of the buffer to read.</param>
/// <returns>The number of elements left to read from the buffer.</returns>
public int BeginForEachIndex(int foreachIndex)
{
CheckBeginForEachIndex(foreachIndex);
var remainingItemCount = m_Reader.BeginForEachIndex(foreachIndex);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_RemainingBlocks = m_Reader.m_BlockStream->Ranges[foreachIndex].NumberOfBlocks;
if (m_RemainingBlocks == 0)
{
m_Reader.m_CurrentBlockEnd = (byte*)m_Reader.m_CurrentBlock + m_Reader.m_LastBlockSize;
}
#endif
return remainingItemCount;
}
/// <summary>
/// Checks if all data has been read from the buffer.
/// </summary>
/// <remarks>If you intentionally don't want to read *all* the data in the buffer, don't call this method.
/// Otherwise, calling this method is recommended, even though it's not strictly necessary.</remarks>
/// <exception cref="ArgumentException">Thrown if not all the buffer's data has been read.</exception>
public void EndForEachIndex()
{
m_Reader.EndForEachIndex();
CheckEndForEachIndex();
}
/// <summary>
/// The number of buffers in the stream of this reader.
/// </summary>
/// <value>The number of buffers in the stream of this reader.</value>
public int ForEachCount
{
get
{
CheckRead();
return m_Reader.ForEachCount;
}
}
/// <summary>
/// The number of items not yet read from the buffer.
/// </summary>
/// <value>The number of items not yet read from the buffer.</value>
public int RemainingItemCount => m_Reader.RemainingItemCount;
/// <summary>
/// Returns a pointer to the next position to read from the buffer. Advances the reader some number of bytes.
/// </summary>
/// <param name="size">The number of bytes to advance the reader.</param>
/// <returns>A pointer to the next position to read from the buffer.</returns>
/// <exception cref="ArgumentException">Thrown if the reader would advance past the end of the buffer.</exception>
public byte* ReadUnsafePtr(int size)
{
CheckReadSize(size);
m_Reader.m_RemainingItemCount--;
byte* ptr = m_Reader.m_CurrentPtr;
m_Reader.m_CurrentPtr += size;
if (m_Reader.m_CurrentPtr > m_Reader.m_CurrentBlockEnd)
{
/*
* On netfw/mono/il2cpp, doing m_CurrentBlock->Data does not throw, because it knows that it can
* just do pointer + 8. On netcore, doing that throws a NullReferenceException. So, first check for
* out of bounds accesses, and only then update m_CurrentBlock and m_CurrentPtr.
*/
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_RemainingBlocks--;
CheckNotReadingOutOfBounds(size);
#endif
m_Reader.m_CurrentBlock = m_Reader.m_CurrentBlock->Next;
m_Reader.m_CurrentPtr = m_Reader.m_CurrentBlock->Data;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
if (m_RemainingBlocks <= 0)
{
m_Reader.m_CurrentBlockEnd = (byte*)m_Reader.m_CurrentBlock + m_Reader.m_LastBlockSize;
}
else
{
m_Reader.m_CurrentBlockEnd = (byte*)m_Reader.m_CurrentBlock + UnsafeStreamBlockData.AllocationSize;
}
#else
m_Reader.m_CurrentBlockEnd = (byte*)m_Reader.m_CurrentBlock + UnsafeStreamBlockData.AllocationSize;
#endif
ptr = m_Reader.m_CurrentPtr;
m_Reader.m_CurrentPtr += size;
}
return ptr;
}
/// <summary>
/// Reads the next value from the buffer.
/// </summary>
/// <remarks>Each read advances the reader to the next item in the buffer.</remarks>
/// <typeparam name="T">The type of value to read.</typeparam>
/// <returns>A reference to the next value from the buffer.</returns>
/// <exception cref="ArgumentException">Thrown if the reader would advance past the end of the buffer.</exception>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public ref T Read<T>() where T : struct
{
int size = UnsafeUtility.SizeOf<T>();
return ref UnsafeUtility.AsRef<T>(ReadUnsafePtr(size));
}
/// <summary>
/// Reads the next value from the buffer. Does not advance the reader.
/// </summary>
/// <typeparam name="T">The type of value to read.</typeparam>
/// <returns>A reference to the next value from the buffer.</returns>
/// <exception cref="ArgumentException">Thrown if the read would go past the end of the buffer.</exception>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public ref T Peek<T>() where T : struct
{
int size = UnsafeUtility.SizeOf<T>();
CheckReadSize(size);
return ref m_Reader.Peek<T>();
}
/// <summary>
/// Returns the total number of items in the buffers of the stream.
/// </summary>
/// <returns>The total number of items in the buffers of the stream.</returns>
public int Count()
{
CheckRead();
return m_Reader.Count();
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckNotReadingOutOfBounds(int size)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
if (m_RemainingBlocks < 0)
throw new System.ArgumentException("Reading out of bounds");
if (m_RemainingBlocks == 0 && size + sizeof(void*) > m_Reader.m_LastBlockSize)
throw new System.ArgumentException("Reading out of bounds");
#endif
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckRead()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckReadSize(int size)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
Assert.IsTrue(size <= UnsafeStreamBlockData.AllocationSize - (sizeof(void*)));
if (m_Reader.m_RemainingItemCount < 1)
{
throw new ArgumentException("There are no more items left to be read.");
}
#endif
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckBeginForEachIndex(int forEachIndex)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
if ((uint)forEachIndex >= (uint)m_Reader.m_BlockStream->RangeCount)
{
throw new System.ArgumentOutOfRangeException(nameof(forEachIndex), $"foreachIndex: {forEachIndex} must be between 0 and ForEachCount: {m_Reader.m_BlockStream->RangeCount}");
}
#endif
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckEndForEachIndex()
{
if (m_Reader.m_RemainingItemCount != 0)
{
throw new System.ArgumentException("Not all elements (Count) have been read. If this is intentional, simply skip calling EndForEachIndex();");
}
if (m_Reader.m_CurrentBlockEnd != m_Reader.m_CurrentPtr)
{
throw new System.ArgumentException("Not all data (Data Size) has been read. If this is intentional, simply skip calling EndForEachIndex();");
}
}
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
static void CheckForEachCountGreaterThanZero(int forEachCount)
{
if (forEachCount <= 0)
throw new ArgumentException("foreachCount must be > 0", "foreachCount");
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckReadAccess()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 64aa0d92f72fa4af49049126211731d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c96af7680ee0e6c4095162d932aadf52
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 50504580bca9eb0409fcceea1af68eb5
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,358 @@
using AOT;
using System;
using System.Threading;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs.LowLevel.Unsafe;
using Unity.Mathematics;
namespace Unity.Collections
{
struct Spinner
{
int m_value;
public void Lock()
{
while (0 != Interlocked.CompareExchange(ref m_value, 1, 0))
{
}
Interlocked.MemoryBarrier();
}
public void Unlock()
{
Interlocked.MemoryBarrier();
while (1 != Interlocked.CompareExchange(ref m_value, 0, 1))
{
}
}
}
internal struct UnmanagedArray<T> : IDisposable where T : unmanaged
{
IntPtr m_pointer;
int m_length;
AllocatorManager.AllocatorHandle m_allocator;
public UnmanagedArray(int length, AllocatorManager.AllocatorHandle allocator)
{
unsafe
{
m_pointer = (IntPtr)Memory.Unmanaged.Array.Allocate<T>(length, allocator);
}
m_length = length;
m_allocator = allocator;
}
public void Dispose()
{
unsafe
{
Memory.Unmanaged.Free((T*)m_pointer, Allocator.Persistent);
}
}
public unsafe T* GetUnsafePointer()
{
return (T*)m_pointer;
}
public ref T this[int index]
{
get { unsafe { return ref ((T*)m_pointer)[index]; } }
}
}
/// <summary>
/// An allocator that is fast like a linear allocator, is threadsafe, and automatically invalidates
/// all allocations made from it, when "rewound" by the user.
/// </summary>
[BurstCompile]
public struct RewindableAllocator : AllocatorManager.IAllocator
{
[BurstCompatible]
internal unsafe struct MemoryBlock : IDisposable
{
public const int kMaximumAlignment = 16384; // can't align any coarser than this many bytes
public byte* m_pointer; // pointer to contiguous memory
public long m_bytes; // how many bytes of contiguous memory it points to
public long m_current; // next byte to give out, when people "allocate" from this block
public long m_allocations; // how many allocations have been made from this block, so far?
public MemoryBlock(long bytes)
{
m_pointer = (byte*)Memory.Unmanaged.Allocate(bytes, kMaximumAlignment, Allocator.Persistent);
m_bytes = bytes;
m_current = 0;
m_allocations = 0;
}
public void Rewind()
{
m_current = 0;
m_allocations = 0;
}
public void Dispose()
{
Memory.Unmanaged.Free(m_pointer, Allocator.Persistent);
m_pointer = null;
m_bytes = 0;
m_current = 0;
m_allocations = 0;
}
public int TryAllocate(ref AllocatorManager.Block block)
{
// Make the alignment multiple of cacheline size
var alignment = math.max(JobsUtility.CacheLineSize, block.Alignment);
var extra = alignment != JobsUtility.CacheLineSize ? 1 : 0;
var cachelineMask = JobsUtility.CacheLineSize - 1;
if (extra == 1)
{
alignment = (alignment + cachelineMask) & ~cachelineMask;
}
// Adjust the size to be multiple of alignment, add extra alignment
// to size if alignment is more than cacheline size
var mask = alignment - 1L;
var size = (block.Bytes + extra * alignment + mask) & ~mask;
var begin = Interlocked.Add(ref m_current, size) - size;
begin = (begin + mask) & ~mask; // align the offset here
if (begin + block.Bytes > m_bytes)
return AllocatorManager.kErrorBufferOverflow;
block.Range.Pointer = (IntPtr)(m_pointer + begin);
block.AllocatedItems = block.Range.Items;
Interlocked.Increment(ref m_allocations);
return AllocatorManager.kErrorNone;
}
public bool Contains(IntPtr ptr)
{
unsafe
{
void* pointer = (void*)ptr;
return (pointer >= m_pointer) && (pointer < m_pointer + m_current);
}
}
};
Spinner m_spinner;
AllocatorManager.AllocatorHandle m_handle;
UnmanagedArray<MemoryBlock> m_block;
int m_best; // block we expect is best to allocate from next
int m_last; // highest-index block that has memory to allocate from
int m_used; // highest-index block that we actually allocated from, since last rewind
bool m_enableBlockFree; // flag indicating if allocator enables individual block free
/// <summary>
/// Initializes the allocator. Must be called before first use.
/// </summary>
/// <param name="initialSizeInBytes">The initial capacity of the allocator, in bytes</param>
public void Initialize(int initialSizeInBytes, bool enableBlockFree = false)
{
m_spinner = default;
m_block = new UnmanagedArray<MemoryBlock>(64, Allocator.Persistent);
m_block[0] = new MemoryBlock(initialSizeInBytes);
m_last = m_used = m_best = 0;
m_enableBlockFree = enableBlockFree;
}
/// <summary>
/// Property to get and set enable block free flag, a flag indicating whether allocator enables individual block free.
/// </summary>
public bool EnableBlockFree
{
get => m_enableBlockFree;
set => m_enableBlockFree = value;
}
/// <summary>
/// Retrieves the number of memory blocks that the allocator has requested from the system.
/// </summary>
public int BlocksAllocated => (int)(m_last + 1);
/// <summary>
/// Retrieves the size of the initial memory block, as requested in the Initialize function.
/// </summary>
public int InitialSizeInBytes => (int)(m_block[0].m_bytes);
/// <summary>
/// Rewind the allocator; invalidate all allocations made from it, and potentially also free memory blocks
/// it has allocated from the system.
/// </summary>
public void Rewind()
{
if (JobsUtility.IsExecutingJob)
throw new InvalidOperationException("You cannot Rewind a RewindableAllocator from a Job.");
m_handle.Rewind(); // bump the allocator handle version, invalidate all dependents
while (m_last > m_used) // *delete* all blocks we didn't even allocate from this time around.
m_block[m_last--].Dispose();
while (m_used > 0) // simply *rewind* all blocks we used in this update, to avoid allocating again, every update.
m_block[m_used--].Rewind();
m_block[0].Rewind();
}
/// <summary>
/// Dispose the allocator. This must be called to free the memory blocks that were allocated from the system.
/// </summary>
public void Dispose()
{
if (JobsUtility.IsExecutingJob)
throw new InvalidOperationException("You cannot Dispose a RewindableAllocator from a Job.");
m_used = 0; // so that we delete all blocks in Rewind() on the next line
Rewind();
m_block[0].Dispose();
m_block.Dispose();
m_last = m_used = m_best = 0;
}
/// <summary>
/// All allocators must implement this property, in order to be installed in the custom allocator table.
/// </summary>
[NotBurstCompatible]
public AllocatorManager.TryFunction Function => Try;
/// <summary>
/// Try to allocate, free, or reallocate a block of memory. This is an internal function, and
/// is not generally called by the user.
/// </summary>
/// <param name="block">The memory block to allocate, free, or reallocate</param>
public int Try(ref AllocatorManager.Block block)
{
if (block.Range.Pointer == IntPtr.Zero)
{
// first, try to allocate from the block that succeeded last time, which we expect is likely to succeed again.
var error = m_block[m_best].TryAllocate(ref block);
if (error == AllocatorManager.kErrorNone)
return error;
// if that fails, check all the blocks to see if any of them have enough memory
m_spinner.Lock();
int best;
for (best = 0; best <= m_last; ++best)
{
error = m_block[best].TryAllocate(ref block);
if (error == AllocatorManager.kErrorNone)
{
m_used = best > m_used ? best : m_used;
m_best = best;
m_spinner.Unlock();
return error;
}
}
// if that fails, allocate another block that's guaranteed big enough, and allocate from it.
var bytes = math.max(m_block[0].m_bytes << best, math.ceilpow2(block.Bytes)); // if user suddenly asks for 1GB, skip smaller sizes
m_block[best] = new MemoryBlock(bytes);
error = m_block[best].TryAllocate(ref block);
m_best = best;
m_used = best;
m_last = best;
m_spinner.Unlock();
return error;
}
// To free memory, no-op unless allocator enables individual block to be freed
if (block.Range.Items == 0)
{
if (m_enableBlockFree)
{
m_spinner.Lock();
if (m_block[m_best].Contains(block.Range.Pointer))
if (0 == Interlocked.Decrement(ref m_block[m_best].m_allocations))
m_block[m_best].Rewind();
m_spinner.Unlock();
}
return 0; // we could check to see if the pointer belongs to us, if we want to be strict about it.
}
return -1;
}
[BurstCompile]
[MonoPInvokeCallback(typeof(AllocatorManager.TryFunction))]
internal static int Try(IntPtr state, ref AllocatorManager.Block block)
{
unsafe { return ((RewindableAllocator*)state)->Try(ref block); }
}
/// <summary>
/// Retrieve the AllocatorHandle associated with this allocator. The handle is used as an index into a
/// global table, for times when a reference to the allocator object isn't available.
/// </summary>
/// <value>The AllocatorHandle retrieved.</value>
public AllocatorManager.AllocatorHandle Handle { get { return m_handle; } set { m_handle = value; } }
/// <summary>
/// Retrieve the Allocator associated with this allocator.
/// </summary>
/// <value>The Allocator retrieved.</value>
public Allocator ToAllocator { get { return m_handle.ToAllocator; } }
/// <summary>
/// Check whether this AllocatorHandle is a custom allocator.
/// </summary>
/// <value>True if this AllocatorHandle is a custom allocator.</value>
public bool IsCustomAllocator { get { return m_handle.IsCustomAllocator; } }
/// <summary>
/// Allocate a NativeArray of type T from memory that is guaranteed to remain valid until the end of the
/// next Update of this World. There is no need to Dispose the NativeArray so allocated. It is not possible
/// to free the memory by Disposing it - it is automatically freed after the end of the next Update for this
/// World.
/// </summary>
/// <typeparam name="T">The element type of the NativeArray to allocate.</typeparam>
/// <param name="length">The length of the NativeArray to allocate, measured in elements.</param>
/// <returns>The NativeArray allocated by this function.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(int) })]
public NativeArray<T> AllocateNativeArray<T>(int length) where T : struct
{
var container = new NativeArray<T>();
unsafe
{
container.m_Buffer = this.AllocateStruct(default(T), length);
}
container.m_Length = length;
container.m_AllocatorLabel = Allocator.None;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
container.m_MinIndex = 0;
container.m_MaxIndex = length - 1;
container.m_Safety = CollectionHelper.CreateSafetyHandle(ToAllocator);
#if REMOVE_DISPOSE_SENTINEL
#else
container.m_DisposeSentinel = null;
#endif
CollectionHelper.SetStaticSafetyId<NativeArray<T>>(ref container.m_Safety, ref NativeArrayExtensions.NativeArrayStaticId<T>.s_staticSafetyId.Data);
Handle.AddSafetyHandle(container.m_Safety);
#endif
return container;
}
/// <summary>
/// Allocate a NativeList of type T from memory that is guaranteed to remain valid until the end of the
/// next Update of this World. There is no need to Dispose the NativeList so allocated. It is not possible
/// to free the memory by Disposing it - it is automatically freed after the end of the next Update for this
/// World. The NativeList must be initialized with its maximum capacity; if it were to dynamically resize,
/// up to 1/2 of the total final capacity would be wasted, because the memory can't be dynamically freed.
/// </summary>
/// <typeparam name="T">The element type of the NativeList to allocate.</typeparam>
/// <param name="capacity">The capacity of the NativeList to allocate, measured in elements.</param>
/// <returns>The NativeList allocated by this function.</returns>
[BurstCompatible(GenericTypeArguments = new[] { typeof(int) })]
public NativeList<T> AllocateNativeList<T>(int capacity) where T : unmanaged
{
var container = new NativeList<T>();
unsafe
{
container.m_ListData = this.Allocate(default(UnsafeList<T>), 1);
container.m_ListData->Ptr = this.Allocate(default(T), capacity);
container.m_ListData->m_capacity = capacity;
container.m_ListData->m_length = 0;
container.m_ListData->Allocator = Allocator.None;
}
container.m_DeprecatedAllocator = Allocator.None;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
container.m_Safety = CollectionHelper.CreateSafetyHandle(ToAllocator);
#if REMOVE_DISPOSE_SENTINEL
#else
container.m_DisposeSentinel = null;
#endif
CollectionHelper.SetStaticSafetyId<NativeList<T>>(ref container.m_Safety, ref NativeList<T>.s_staticSafetyId.Data);
AtomicSafetyHandle.SetBumpSecondaryVersionOnScheduleWrite(container.m_Safety, true);
Handle.AddSafetyHandle(container.m_Safety);
#endif
return container;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fb1479c160300a447b5b0ba91f47f230
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,324 @@
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Collections
{
/// <summary>
/// Provides methods for copying and encoding Unicode text.
/// </summary>
[BurstCompatible]
public static unsafe class UTF8ArrayUnsafeUtility
{
/// <summary>
/// Copies a buffer of UCS-2 text. The copy is encoded as UTF-8.
/// </summary>
/// <remarks>Assumes the source data is valid UCS-2.</remarks>
/// <param name="src">The source buffer for reading UCS-2.</param>
/// <param name="srcLength">The number of chars to read from the source.</param>
/// <param name="dest">The destination buffer for writing UTF-8.</param>
/// <param name="destLength">Outputs the number of bytes written to the destination.</param>
/// <param name="destUTF8MaxLengthInBytes">The max number of bytes that will be written to the destination buffer.</param>
/// <returns><see cref="CopyError.None"/> if the copy fully completes. Otherwise, returns <see cref="CopyError.Truncation"/>.</returns>
public static CopyError Copy(byte *dest, out int destLength, int destUTF8MaxLengthInBytes, char *src, int srcLength)
{
var error = Unicode.Utf16ToUtf8(src, srcLength, dest, out destLength, destUTF8MaxLengthInBytes);
if (error == ConversionError.None)
return CopyError.None;
return CopyError.Truncation;
}
/// <summary>
/// Copies a buffer of UCS-2 text. The copy is encoded as UTF-8.
/// </summary>
/// <remarks>Assumes the source data is valid UCS-2.</remarks>
/// <param name="src">The source buffer for reading UCS-2.</param>
/// <param name="srcLength">The number of chars to read from the source.</param>
/// <param name="dest">The destination buffer for writing UTF-8.</param>
/// <param name="destLength">Outputs the number of bytes written to the destination.</param>
/// <param name="destUTF8MaxLengthInBytes">The max number of bytes that will be written to the destination buffer.</param>
/// <returns><see cref="CopyError.None"/> if the copy fully completes. Otherwise, returns <see cref="CopyError.Truncation"/>.</returns>
public static CopyError Copy(byte *dest, out ushort destLength, ushort destUTF8MaxLengthInBytes, char *src, int srcLength)
{
var error = Unicode.Utf16ToUtf8(src, srcLength, dest, out var temp, destUTF8MaxLengthInBytes);
destLength = (ushort)temp;
if (error == ConversionError.None)
return CopyError.None;
return CopyError.Truncation;
}
/// <summary>
/// Copies a buffer of UCS-8 text.
/// </summary>
/// <remarks>Assumes the source data is valid UTF-8.</remarks>
/// <param name="src">The source buffer.</param>
/// <param name="srcLength">The number of chars to read from the source.</param>
/// <param name="dest">The destination buffer.</param>
/// <param name="destLength">Outputs the number of bytes written to the destination.</param>
/// <param name="destUTF8MaxLengthInBytes">The max number of bytes that will be written to the destination buffer.</param>
/// <returns><see cref="CopyError.None"/> if the copy fully completes. Otherwise, returns <see cref="CopyError.Truncation"/>.</returns>
public static CopyError Copy(byte *dest, out int destLength, int destUTF8MaxLengthInBytes, byte *src, int srcLength)
{
var error = Unicode.Utf8ToUtf8(src, srcLength, dest, out var temp, destUTF8MaxLengthInBytes);
destLength = temp;
if (error == ConversionError.None)
return CopyError.None;
return CopyError.Truncation;
}
/// <summary>
/// Copies a buffer of UCS-8 text.
/// </summary>
/// <remarks>Assumes the source data is valid UTF-8.</remarks>
/// <param name="src">The source buffer.</param>
/// <param name="srcLength">The number of chars to read from the source.</param>
/// <param name="dest">The destination buffer.</param>
/// <param name="destLength">Outputs the number of bytes written to the destination.</param>
/// <param name="destUTF8MaxLengthInBytes">The max number of bytes that will be written to the destination buffer.</param>
/// <returns><see cref="CopyError.None"/> if the copy fully completes. Otherwise, returns <see cref="CopyError.Truncation"/>.</returns>
public static CopyError Copy(byte *dest, out ushort destLength, ushort destUTF8MaxLengthInBytes, byte *src, ushort srcLength)
{
var error = Unicode.Utf8ToUtf8(src, srcLength, dest, out var temp, destUTF8MaxLengthInBytes);
destLength = (ushort)temp;
if (error == ConversionError.None)
return CopyError.None;
return CopyError.Truncation;
}
/// <summary>
/// Copies a buffer of UTF-8 text. The copy is encoded as UCS-2.
/// </summary>
/// <remarks>Assumes the source data is valid UTF-8.</remarks>
/// <param name="src">The source buffer for reading UTF-8.</param>
/// <param name="srcLength">The number of bytes to read from the source.</param>
/// <param name="dest">The destination buffer for writing UCS-2.</param>
/// <param name="destLength">Outputs the number of chars written to the destination.</param>
/// <param name="destUCS2MaxLengthInChars">The max number of chars that will be written to the destination buffer.</param>
/// <returns><see cref="CopyError.None"/> if the copy fully completes. Otherwise, returns <see cref="CopyError.Truncation"/>.</returns>
public static CopyError Copy(char *dest, out int destLength, int destUCS2MaxLengthInChars, byte *src, int srcLength)
{
if (ConversionError.None == Unicode.Utf8ToUtf16(src, srcLength, dest, out destLength, destUCS2MaxLengthInChars))
return CopyError.None;
return CopyError.Truncation;
}
/// <summary>
/// Copies a buffer of UTF-8 text. The copy is encoded as UCS-2.
/// </summary>
/// <remarks>Assumes the source data is valid UTF-8.</remarks>
/// <param name="src">The source buffer for reading UTF-8.</param>
/// <param name="srcLength">The number of bytes to read from the source.</param>
/// <param name="dest">The destination buffer for writing UCS-2.</param>
/// <param name="destLength">Outputs the number of chars written to the destination.</param>
/// <param name="destUCS2MaxLengthInChars">The max number of chars that will be written to the destination buffer.</param>
/// <returns><see cref="CopyError.None"/> if the copy fully completes. Otherwise, returns <see cref="CopyError.Truncation"/>.</returns>
public static CopyError Copy(char *dest, out ushort destLength, ushort destUCS2MaxLengthInChars, byte *src, ushort srcLength)
{
var error = Unicode.Utf8ToUtf16(src, srcLength, dest, out var temp, destUCS2MaxLengthInChars);
destLength = (ushort)temp;
if (error == ConversionError.None)
return CopyError.None;
return CopyError.Truncation;
}
/// <summary>
/// Appends UTF-8 text to a buffer.
/// </summary>
/// <remarks>Assumes the source data is valid UTF-8.
///
/// No data will be copied if the destination has insufficient capacity for the full append, *i.e.* if `srcLength > (destCapacity - destLength)`.
/// </remarks>
/// <param name="src">The source buffer.</param>
/// <param name="srcLength">The number of bytes to read from the source.</param>
/// <param name="dest">The destination buffer.</param>
/// <param name="destLength">Reference to the destination buffer's length in bytes *before* the append. Will be assigned the new length *after* the append.</param>
/// <param name="destCapacity">The destination buffer capacity in bytes.</param>
/// <returns><see cref="FormatError.None"/> if the append fully completes. Otherwise, returns <see cref="FormatError.Overflow"/>.</returns>
public static FormatError AppendUTF8Bytes(byte* dest, ref int destLength, int destCapacity, byte* src, int srcLength)
{
if (destLength + srcLength > destCapacity)
return FormatError.Overflow;
UnsafeUtility.MemCpy(dest + destLength, src, srcLength);
destLength += srcLength;
return FormatError.None;
}
/// <summary>
/// Appends UTF-8 text to a buffer.
/// </summary>
/// <remarks>Assumes the source data is valid UTF-8.</remarks>
/// <param name="src">The source buffer.</param>
/// <param name="srcLength">The number of bytes to read from the source.</param>
/// <param name="dest">The destination buffer.</param>
/// <param name="destLength">Reference to the destination buffer's length in bytes *before* the append. Will be assigned the number of bytes appended.</param>
/// <param name="destUTF8MaxLengthInBytes">The destination buffer's length in bytes. Data will not be appended past this length.</param>
/// <returns><see cref="CopyError.None"/> if the append fully completes. Otherwise, returns <see cref="CopyError.Truncation"/>.</returns>
public static CopyError Append(byte *dest, ref ushort destLength, ushort destUTF8MaxLengthInBytes, byte *src, ushort srcLength)
{
var error = Unicode.Utf8ToUtf8(src, srcLength, dest + destLength, out var temp, destUTF8MaxLengthInBytes - destLength);
destLength += (ushort)temp;
if (error == ConversionError.None)
return CopyError.None;
return CopyError.Truncation;
}
/// <summary>
/// Appends UCS-2 text to a buffer, encoded as UTF-8.
/// </summary>
/// <remarks>Assumes the source data is valid UCS-2.</remarks>
/// <param name="src">The source buffer.</param>
/// <param name="srcLength">The number of chars to read from the source.</param>
/// <param name="dest">The destination buffer.</param>
/// <param name="destLength">Reference to the destination buffer's length in bytes *before* the append. Will be assigned the number of bytes appended.</param>
/// <param name="destUTF8MaxLengthInBytes">The destination buffer's length in bytes. Data will not be appended past this length.</param>
/// <returns><see cref="CopyError.None"/> if the append fully completes. Otherwise, returns <see cref="CopyError.Truncation"/>.</returns>
public static CopyError Append(byte *dest, ref ushort destLength, ushort destUTF8MaxLengthInBytes, char *src, int srcLength)
{
var error = Unicode.Utf16ToUtf8(src, srcLength, dest + destLength, out var temp, destUTF8MaxLengthInBytes - destLength);
destLength += (ushort)temp;
if (error == ConversionError.None)
return CopyError.None;
return CopyError.Truncation;
}
/// <summary>
/// Appends UTF-8 text to a buffer, encoded as UCS-2.
/// </summary>
/// <remarks>Assumes the source data is valid UTF-8.</remarks>
/// <param name="src">The source buffer.</param>
/// <param name="srcLength">The number of bytes to read from the source.</param>
/// <param name="dest">The destination buffer.</param>
/// <param name="destLength">Reference to the destination buffer's length in chars *before* the append. Will be assigned the number of chars appended.</param>
/// <param name="destUCS2MaxLengthInChars">The destination buffer's length in chars. Data will not be appended past this length.</param>
/// <returns><see cref="CopyError.None"/> if the append fully completes. Otherwise, returns <see cref="CopyError.Truncation"/>.</returns>
public static CopyError Append(char *dest, ref ushort destLength, ushort destUCS2MaxLengthInChars, byte *src, ushort srcLength)
{
var error = Unicode.Utf8ToUtf16(src, srcLength, dest + destLength, out var temp, destUCS2MaxLengthInChars - destLength);
destLength += (ushort)temp;
if (error == ConversionError.None)
return CopyError.None;
return CopyError.Truncation;
}
internal struct Comparison
{
public bool terminates;
public int result;
public Comparison(Unicode.Rune runeA, ConversionError errorA, Unicode.Rune runeB, ConversionError errorB)
{
if(errorA != ConversionError.None)
runeA.value = 0;
if(errorB != ConversionError.None)
runeB.value = 0;
if(runeA.value != runeB.value)
{
result = runeA.value - runeB.value;
terminates = true;
}
else
{
result = 0;
terminates = (runeA.value == 0 && runeB.value == 0);
}
}
}
/// <summary>Compares two UTF-8 buffers for relative equality.</summary>
/// <param name="utf8BufferA">The first buffer of UTF-8 text.</param>
/// <param name="utf8LengthInBytesA">The length in bytes of the first UTF-8 buffer.</param>
/// <param name="utf8BufferB">The second buffer of UTF-8 text.</param>
/// <param name="utf8LengthInBytesB">The length in bytes of the second UTF-8 buffer.</param>
/// <returns>
/// Less than zero if first different code point is less in the first UTF-8 buffer.
/// Zero if the strings are identical.
/// More than zero if first different code point is less in the second UTF-8 buffer.
/// </returns>
public static int StrCmp(byte* utf8BufferA, int utf8LengthInBytesA, byte* utf8BufferB, int utf8LengthInBytesB)
{
int byteIndexA = 0;
int byteIndexB = 0;
while(true)
{
var utf8ErrorA = Unicode.Utf8ToUcs(out var utf8RuneA, utf8BufferA,ref byteIndexA, utf8LengthInBytesA);
var utf8ErrorB = Unicode.Utf8ToUcs(out var utf8RuneB, utf8BufferB, ref byteIndexB, utf8LengthInBytesB);
var comparison = new Comparison(utf8RuneA, utf8ErrorA, utf8RuneB, utf8ErrorB);
if(comparison.terminates)
return comparison.result;
}
}
/// <summary>Compares two UTF-16 buffers for relative equality.</summary>
/// <param name="utf16BufferA">The first buffer of UTF-16 text.</param>
/// <param name="utf16LengthInCharsA">The length in chars of the first UTF-16 buffer.</param>
/// <param name="utf16BufferB">The second buffer of UTF-16 text.</param>
/// <param name="utf16LengthInCharsB">The length in chars of the second UTF-16 buffer.</param>
/// <returns>
/// Less than zero if first different code point is less in the first UTF-16 buffer.
/// Zero if the strings are identical.
/// More than zero if first different code point is less in the second UTF-16 buffer.
/// </returns>
public static int StrCmp(char* utf16BufferA, int utf16LengthInCharsA, char* utf16BufferB, int utf16LengthInCharsB)
{
int charIndexA = 0;
int charIndexB = 0;
while(true)
{
var utf16ErrorA = Unicode.Utf16ToUcs(out var utf16RuneA, utf16BufferA,ref charIndexA, utf16LengthInCharsA);
var utf16ErrorB = Unicode.Utf16ToUcs(out var utf16RuneB, utf16BufferB, ref charIndexB, utf16LengthInCharsB);
var comparison = new Comparison(utf16RuneA, utf16ErrorA, utf16RuneB, utf16ErrorB);
if(comparison.terminates)
return comparison.result;
}
}
/// <summary>Returns true if two UTF-8 buffers have the same length and content.</summary>
/// <param name="aBytes">The first buffer of UTF-8 text.</param>
/// <param name="aLength">The length in bytes of the first buffer.</param>
/// <param name="bBytes">The second buffer of UTF-8 text.</param>
/// <param name="bLength">The length in bytes of the second buffer.</param>
/// <returns>True if the content of both strings is identical.</returns>
public static bool EqualsUTF8Bytes(byte* aBytes, int aLength, byte* bBytes, int bLength)
{
return StrCmp(aBytes, aLength, bBytes, bLength) == 0;
}
/// <summary>Compares a UTF-8 buffer and a UTF-16 buffer for relative equality.</summary>
/// <param name="utf8Buffer">The buffer of UTF-8 text.</param>
/// <param name="utf8LengthInBytes">The length in bytes of the UTF-8 buffer.</param>
/// <param name="utf16Buffer">The buffer of UTF-16 text.</param>
/// <param name="utf16LengthInChars">The length in chars of the UTF-16 buffer.</param>
/// <returns>
/// Less than zero if first different code point is less in UTF-8 buffer.
/// Zero if the strings are identical.
/// More than zero if first different code point is less in UTF-16 buffer.
/// </returns>
public static int StrCmp(byte* utf8Buffer, int utf8LengthInBytes, char* utf16Buffer, int utf16LengthInChars)
{
int byteIndex = 0;
int charIndex = 0;
while(true)
{
var utf8Error = Unicode.Utf8ToUcs(out var utf8Rune, utf8Buffer,ref byteIndex, utf8LengthInBytes);
var utf16Error = Unicode.Utf16ToUcs(out var utf16Rune, utf16Buffer, ref charIndex, utf16LengthInChars);
var comparison = new Comparison(utf8Rune, utf8Error, utf16Rune, utf16Error);
if(comparison.terminates)
return comparison.result;
}
}
/// <summary>Compares a UTF-16 buffer and a UTF-8 buffer for relative equality.</summary>
/// <param name="utf16Buffer">The buffer of UTF-16 text.</param>
/// <param name="utf16LengthInChars">The length in chars of the UTF-16 buffer.</param>
/// <param name="utf8Buffer">The buffer of UTF-8 text.</param>
/// <param name="utf8LengthInBytes">The length in bytes of the UTF-8 buffer.</param>
/// <returns>
/// Less than zero if first different code point is less in UTF-16 buffer.
/// Zero if the strings are identical.
/// More than zero if first different code point is less in UTF-8 buffer.
/// </returns>
public static int StrCmp(char* utf16Buffer, int utf16LengthInChars, byte* utf8Buffer, int utf8LengthInBytes)
{
return -StrCmp(utf8Buffer, utf8LengthInBytes, utf16Buffer, utf16LengthInChars);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f9c067f2cfe24ce98e2deecdb0ed12a3
timeCreated: 1591061304

View file

@ -0,0 +1,536 @@
using System;
using System.Diagnostics;
using UnityEngine.Assertions;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Collections
{
/// <summary>
/// Kinds of format errors.
/// </summary>
public enum FormatError
{
/// <summary>
/// No error.
/// </summary>
None,
/// <summary>
/// The target storage does not have sufficient capacity.
/// </summary>
Overflow,
}
/// <summary>
/// Kinds of parse errors.
/// </summary>
public enum ParseError
{
/// <summary>
/// No parse error.
/// </summary>
None,
/// <summary>
/// The text parsed does not form a number.
/// </summary>
Syntax,
/// <summary>
/// The number exceeds the range of the target type.
/// </summary>
Overflow,
/// <summary>
/// The number exceeds the precision of the target type.
/// </summary>
Underflow,
}
/// <summary>
/// Kinds of copy errors.
/// </summary>
public enum CopyError
{
/// <summary>
/// No copy error.
/// </summary>
None,
/// <summary>
/// The target storage does not have sufficient capacity.
/// </summary>
Truncation,
}
/// <summary>
/// Kinds of conversion errors.
/// </summary>
public enum ConversionError
{
/// <summary>
/// No conversion error.
/// </summary>
None,
/// <summary>
/// The target storage does not have sufficient capacity.
/// </summary>
Overflow,
/// <summary>
/// The bytes do not form a valid character.
/// </summary>
Encoding,
/// <summary>
/// The rune is not a valid code point.
/// </summary>
CodePoint,
}
/// <summary>
/// Provides utility methods for UTF-8, UTF-16, UCS-4 (a.k.a. UTF-32), and WTF-8.
/// </summary>
[BurstCompatible]
public unsafe struct Unicode
{
/// <summary>
/// Representation of a Unicode character as a code point.
/// </summary>
[BurstCompatible]
public struct Rune
{
/// <summary>
/// The code point.
/// </summary>
/// <value>The code point.</value>
public int value;
/// <summary>
/// Initializes and returns an instance of Rune.
/// </summary>
/// <remarks>You are responsible for the code point being valid.</remarks>
/// <param name="codepoint">The code point.</param>
public Rune(int codepoint)
{
value = codepoint;
}
/// <summary>
/// Returns a rune.
/// </summary>
/// <remarks>Because a char is 16-bit, it can only represent the first 2^16 code points, not all 1.1 million.</remarks>
/// <param name="codepoint">A code point.</param>
/// <returns>A rune.</returns>
public static explicit operator Rune(char codepoint) => new Rune { value = codepoint };
/// <summary>
/// Returns true if a rune is a numerical digit character.
/// </summary>
/// <param name="r">The rune.</param>
/// <returns>True if the rune is a numerical digit character.</returns>
public static bool IsDigit(Rune r)
{
return r.value >= '0' && r.value <= '9';
}
/// <summary>
/// Returns the number of bytes required to encode this rune as UTF-8.
/// </summary>
/// <returns>The number of bytes required to encode this rune as UTF-8. If the rune's codepoint
/// is invalid, returns 4 (the maximum possible encoding length).</returns>
public int LengthInUtf8Bytes()
{
if (value < 0)
return 4; // invalid codepoint
if (value <= 0x7F)
return 1;
if (value <= 0x7FF)
return 2;
if (value <= 0xFFFF)
return 3;
if (value <= 0x1FFFFF)
return 4;
// invalid codepoint, max size.
return 4;
}
}
/// <summary>The maximum value of a valid UNICODE code point</summary>
public const int kMaximumValidCodePoint = 0x10FFFF;
/// <summary>
/// Returns true if a code point is valid.
/// </summary>
/// <param name="codepoint">A code point.</param>
/// <returns>True if a code point is valid.</returns>
public static bool IsValidCodePoint(int codepoint)
{
if (codepoint > kMaximumValidCodePoint) // maximum valid code point
return false;
// if (codepoint >= 0xD800 && codepoint <= 0xDFFF) // surrogate pair
// return false;
if (codepoint < 0) // negative?
return false;
return true;
}
/// <summary>
/// Returns true if the byte is not the last byte of a UTF-8 character.
/// </summary>
/// <param name="b">The byte.</param>
/// <returns>True if the byte is not the last byte of a UTF-8 character.</returns>
public static bool NotTrailer(byte b)
{
return (b & 0xC0) != 0x80;
}
/// <summary>
/// The Unicode character <20>.
/// </summary>
/// <remarks>This character is used to stand-in for characters that can't be rendered.</remarks>
/// <value>The Unicode character <20>.</value>
public static Rune ReplacementCharacter => new Rune { value = 0xFFFD };
/// <summary>
/// The null rune value.
/// </summary>
/// <remarks>In this package, the "bad rune" is used as a null character. It represents no valid code point.</remarks>
/// <value>The null rune value.</value>
public static Rune BadRune => new Rune { value = 0 };
/// <summary>
/// Reads a UTF-8 encoded character from a buffer.
/// </summary>
/// <param name="rune">Outputs the character read. If the read fails, outputs <see cref="ReplacementCharacter"/>.</param>
/// <param name="buffer">The buffer of bytes to read.</param>
/// <param name="index">Reference to a byte index into the buffer. If the read succeeds, index is incremented by the
/// size in bytes of the character read. If the read fails, index is incremented by 1.</param>
/// <param name="capacity">The size in bytes of the buffer. Used to check that the read is in bounds.</param>
/// <returns><see cref="ConversionError.None"/> if the read succeeds. Otherwise, returns <see cref="ConversionError.Overflow"/> or <see cref="ConversionError.Encoding"/>.</returns>
public static ConversionError Utf8ToUcs(out Rune rune, byte* buffer, ref int index, int capacity)
{
int code = 0;
rune = ReplacementCharacter;
if (index + 1 > capacity)
{
return ConversionError.Overflow;
}
if ((buffer[index] & 0b10000000) == 0b00000000) // if high bit is 0, 1 byte
{
rune.value = buffer[index + 0];
index += 1;
return ConversionError.None;
}
if ((buffer[index] & 0b11100000) == 0b11000000) // if high 3 bits are 110, 2 bytes
{
if (index + 2 > capacity)
{
index += 1;
return ConversionError.Overflow;
}
code = (buffer[index + 0] & 0b00011111);
code = (code << 6) | (buffer[index + 1] & 0b00111111);
if (code < (1 << 7) || NotTrailer(buffer[index + 1]))
{
index += 1;
return ConversionError.Encoding;
}
rune.value = code;
index += 2;
return ConversionError.None;
}
if ((buffer[index] & 0b11110000) == 0b11100000) // if high 4 bits are 1110, 3 bytes
{
if (index + 3 > capacity)
{
index += 1;
return ConversionError.Overflow;
}
code = (buffer[index + 0] & 0b00001111);
code = (code << 6) | (buffer[index + 1] & 0b00111111);
code = (code << 6) | (buffer[index + 2] & 0b00111111);
if (code < (1 << 11) || !IsValidCodePoint(code) || NotTrailer(buffer[index + 1]) || NotTrailer(buffer[index + 2]))
{
index += 1;
return ConversionError.Encoding;
}
rune.value = code;
index += 3;
return ConversionError.None;
}
if ((buffer[index] & 0b11111000) == 0b11110000) // if high 5 bits are 11110, 4 bytes
{
if (index + 4 > capacity)
{
index += 1;
return ConversionError.Overflow;
}
code = (buffer[index + 0] & 0b00000111);
code = (code << 6) | (buffer[index + 1] & 0b00111111);
code = (code << 6) | (buffer[index + 2] & 0b00111111);
code = (code << 6) | (buffer[index + 3] & 0b00111111);
if (code < (1 << 16) || !IsValidCodePoint(code) || NotTrailer(buffer[index + 1]) || NotTrailer(buffer[index + 2]) || NotTrailer(buffer[index + 3]))
{
index += 1;
return ConversionError.Encoding;
}
rune.value = code;
index += 4;
return ConversionError.None;
}
index += 1;
return ConversionError.Encoding;
}
/// <summary>
/// Returns true if a char is a Unicode leading surrogate.
/// </summary>
/// <param name="c">The char.</param>
/// <returns>True if the char is a Unicode leading surrogate.</returns>
static bool IsLeadingSurrogate(char c)
{
return c >= 0xD800 && c <= 0xDBFF;
}
/// <summary>
/// Returns true if a char is a Unicode trailing surrogate.
/// </summary>
/// <param name="c">The char.</param>
/// <returns>True if the char is a Unicode trailing surrogate.</returns>
static bool IsTrailingSurrogate(char c)
{
return c >= 0xDC00 && c <= 0xDFFF;
}
/// <summary>
/// Reads a UTF-16 encoded character from a buffer.
/// </summary>
/// <param name="rune">Outputs the character read. If the read fails, rune is not set.</param>
/// <param name="buffer">The buffer of chars to read.</param>
/// <param name="index">Reference to a char index into the buffer. If the read succeeds, index is incremented by the
/// size in chars of the character read. If the read fails, index is not incremented.</param>
/// <param name="capacity">The size in chars of the buffer. Used to check that the read is in bounds.</param>
/// <returns><see cref="ConversionError.None"/> if the read succeeds. Otherwise, returns <see cref="ConversionError.Overflow"/>.</returns>
public static ConversionError Utf16ToUcs(out Rune rune, char* buffer, ref int index, int capacity)
{
int code = 0;
rune = ReplacementCharacter;
if (index + 1 > capacity)
return ConversionError.Overflow;
if (!IsLeadingSurrogate(buffer[index]) || (index + 2 > capacity))
{
rune.value = buffer[index];
index += 1;
return ConversionError.None;
}
code = (buffer[index + 0] & 0x03FF);
char next = buffer[index + 1];
if (!IsTrailingSurrogate(next))
{
rune.value = buffer[index];
index += 1;
return ConversionError.None;
}
code = (code << 10) | (buffer[index + 1] & 0x03FF);
code += 0x10000;
rune.value = code;
index += 2;
return ConversionError.None;
}
/// <summary>
/// Writes a rune to a buffer as a UTF-8 encoded character.
/// </summary>
/// <param name="rune">The rune to encode.</param>
/// <param name="buffer">The buffer to write to.</param>
/// <param name="index">Reference to a byte index into the buffer. If the write succeeds, index is incremented by the
/// size in bytes of the character written. If the write fails, index is not incremented.</param>
/// <param name="capacity">The size in bytes of the buffer. Used to check that the write is in bounds.</param>
/// <returns><see cref="ConversionError.None"/> if the write succeeds. Otherwise, returns <see cref="ConversionError.CodePoint"/>, <see cref="ConversionError.Overflow"/>, or <see cref="ConversionError.Encoding"/>.</returns>
public static ConversionError UcsToUtf8(byte* buffer, ref int index, int capacity, Rune rune)
{
if (!IsValidCodePoint(rune.value))
{
return ConversionError.CodePoint;
}
if (index + 1 > capacity)
{
return ConversionError.Overflow;
}
if (rune.value <= 0x7F)
{
buffer[index++] = (byte)rune.value;
return ConversionError.None;
}
if (rune.value <= 0x7FF)
{
if (index + 2 > capacity)
{
return ConversionError.Overflow;
}
buffer[index++] = (byte)(0xC0 | (rune.value >> 6));
buffer[index++] = (byte)(0x80 | ((rune.value >> 0) & 0x3F));
return ConversionError.None;
}
if (rune.value <= 0xFFFF)
{
if (index + 3 > capacity)
{
return ConversionError.Overflow;
}
buffer[index++] = (byte)(0xE0 | (rune.value >> 12));
buffer[index++] = (byte)(0x80 | ((rune.value >> 6) & 0x3F));
buffer[index++] = (byte)(0x80 | ((rune.value >> 0) & 0x3F));
return ConversionError.None;
}
if (rune.value <= 0x1FFFFF)
{
if (index + 4 > capacity)
{
return ConversionError.Overflow;
}
buffer[index++] = (byte)(0xF0 | (rune.value >> 18));
buffer[index++] = (byte)(0x80 | ((rune.value >> 12) & 0x3F));
buffer[index++] = (byte)(0x80 | ((rune.value >> 6) & 0x3F));
buffer[index++] = (byte)(0x80 | ((rune.value >> 0) & 0x3F));
return ConversionError.None;
}
return ConversionError.Encoding;
}
/// <summary>
/// Writes a rune to a buffer as a UTF-16 encoded character.
/// </summary>
/// <param name="rune">The rune to encode.</param>
/// <param name="buffer">The buffer of chars to write to.</param>
/// <param name="index">Reference to a char index into the buffer. If the write succeeds, index is incremented by the
/// size in chars of the character written. If the write fails, index is not incremented.</param>
/// <param name="capacity">The size in chars of the buffer. Used to check that the write is in bounds.</param>
/// <returns><see cref="ConversionError.None"/> if the write succeeds. Otherwise, returns <see cref="ConversionError.CodePoint"/>, <see cref="ConversionError.Overflow"/>, or <see cref="ConversionError.Encoding"/>.</returns>
public static ConversionError UcsToUtf16(char* buffer, ref int index, int capacity, Rune rune)
{
if (!IsValidCodePoint(rune.value))
{
return ConversionError.CodePoint;
}
if (index + 1 > capacity)
{
return ConversionError.Overflow;
}
if (rune.value >= 0x10000)
{
if (index + 2 > capacity)
{
return ConversionError.Overflow;
}
int code = rune.value - 0x10000;
if (code >= (1 << 20))
{
return ConversionError.Encoding;
}
buffer[index++] = (char)(0xD800 | (code >> 10));
buffer[index++] = (char)(0xDC00 | (code & 0x3FF));
return ConversionError.None;
}
buffer[index++] = (char)rune.value;
return ConversionError.None;
}
/// <summary>
/// Copies UTF-16 characters from one buffer to another buffer as UTF-8.
/// </summary>
/// <remarks>Assumes the source data is valid UTF-16.</remarks>
/// <param name="utf16Buffer">The source buffer.</param>
/// <param name="utf16Length">The number of chars to read from the source.</param>
/// <param name="utf8Buffer">The destination buffer.</param>
/// <param name="utf8Length">Outputs the number of bytes written to the destination.</param>
/// <param name="utf8Capacity">The size in bytes of the destination buffer.</param>
/// <returns><see cref="ConversionError.None"/> if the copy fully completes. Otherwise, returns <see cref="ConversionError.Overflow"/>.</returns>
public static ConversionError Utf16ToUtf8(char* utf16Buffer, int utf16Length, byte* utf8Buffer, out int utf8Length, int utf8Capacity)
{
utf8Length = 0;
for (var utf16Offset = 0; utf16Offset < utf16Length;)
{
Utf16ToUcs(out var ucs, utf16Buffer, ref utf16Offset, utf16Length);
if (UcsToUtf8(utf8Buffer, ref utf8Length, utf8Capacity, ucs) == ConversionError.Overflow)
return ConversionError.Overflow;
}
return ConversionError.None;
}
/// <summary>
/// Copies UTF-8 characters from one buffer to another.
/// </summary>
/// <remarks>Assumes the source data is valid UTF-8.</remarks>
/// <param name="srcBuffer">The source buffer.</param>
/// <param name="srcLength">The number of bytes to read from the source.</param>
/// <param name="destBuffer">The destination buffer.</param>
/// <param name="destLength">Outputs the number of bytes written to the destination.</param>
/// <param name="destCapacity">The size in bytes of the destination buffer.</param>
/// <returns><see cref="ConversionError.None"/> if the copy fully completes. Otherwise, returns <see cref="ConversionError.Overflow"/>.</returns>
public static ConversionError Utf8ToUtf8(byte* srcBuffer, int srcLength, byte* destBuffer, out int destLength, int destCapacity)
{
if (destCapacity >= srcLength)
{
UnsafeUtility.MemCpy(destBuffer, srcBuffer, srcLength);
destLength = srcLength;
return ConversionError.None;
}
// TODO even in this case, it's possible to MemCpy all but the last 3 bytes that fit, and then by looking at only
// TODO the high bits of the last 3 bytes that fit, decide how many of the 3 to append. but that requires a
// TODO little UNICODE presence of mind that nobody has today.
destLength = 0;
for (var srcOffset = 0; srcOffset < srcLength;)
{
Utf8ToUcs(out var ucs, srcBuffer, ref srcOffset, srcLength);
if (UcsToUtf8(destBuffer, ref destLength, destCapacity, ucs) == ConversionError.Overflow)
return ConversionError.Overflow;
}
return ConversionError.None;
}
/// <summary>
/// Copies UTF-8 characters from one buffer to another as UTF-16.
/// </summary>
/// <remarks>Assumes the source data is valid UTF-8.</remarks>
/// <param name="utf8Buffer">The source buffer.</param>
/// <param name="utf8Length">The number of bytes to read from the source.</param>
/// <param name="utf16Buffer">The destination buffer.</param>
/// <param name="utf16Length">Outputs the number of chars written to the destination.</param>
/// <param name="utf16Capacity">The size in chars of the destination buffer.</param>
/// <returns><see cref="ConversionError.None"/> if the copy fully completes. Otherwise, <see cref="ConversionError.Overflow"/>.</returns>
public static ConversionError Utf8ToUtf16(byte* utf8Buffer, int utf8Length, char* utf16Buffer, out int utf16Length, int utf16Capacity)
{
utf16Length = 0;
for (var utf8Offset
= 0; utf8Offset < utf8Length;)
{
Utf8ToUcs(out var ucs, utf8Buffer, ref utf8Offset, utf8Length);
if (UcsToUtf16(utf16Buffer, ref utf16Length, utf16Capacity, ucs) == ConversionError.Overflow)
return ConversionError.Overflow;
}
return ConversionError.None;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bc38a1e61f172489a8eb4983c247c3c3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,29 @@
{
"name": "Unity.Collections",
"references": [
"Unity.Burst",
"Unity.Mathematics",
"Unity.Properties",
"Unity.Collections.LowLevel.ILSupport"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [
{
"name": "com.unity.package-validation-suite",
"expression": "0.4.0-preview.6",
"define": "UNITY_SKIP_UPDATES_WITH_VALIDATION_SUITE"
},
{
"name": "com.unity.properties",
"expression": "1.2.0-preview",
"define": "UNITY_PROPERTIES_EXISTS"
}
],
"noEngineReferences": false
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e0cd26848372d4e5c891c569017e11f1
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,478 @@
using System;
using System.Diagnostics;
using Unity.Jobs;
using Unity.Mathematics;
namespace Unity.Collections.LowLevel.Unsafe
{
/// <summary>
/// An unmanaged, untyped, heterogeneous buffer.
/// </summary>
/// <remarks>
/// The values written to an individual append buffer can be of different types.
/// </remarks>
[BurstCompatible]
public unsafe struct UnsafeAppendBuffer
: INativeDisposable
{
/// <summary>
/// The internal buffer where the content is stored.
/// </summary>
/// <value>The internal buffer where the content is stored.</value>
[NativeDisableUnsafePtrRestriction]
public byte* Ptr;
/// <summary>
/// The size in bytes of the currently-used portion of the internal buffer.
/// </summary>
/// <value>The size in bytes of the currently-used portion of the internal buffer.</value>
public int Length;
/// <summary>
/// The size in bytes of the internal buffer.
/// </summary>
/// <value>The size in bytes of the internal buffer.</value>
public int Capacity;
/// <summary>
/// The allocator used to create the internal buffer.
/// </summary>
/// <value>The allocator used to create the internal buffer.</value>
public AllocatorManager.AllocatorHandle Allocator;
/// <summary>
/// The byte alignment used when allocating the internal buffer.
/// </summary>
/// <value>The byte alignment used when allocating the internal buffer. Is always a non-zero power of 2.</value>
public readonly int Alignment;
/// <summary>
/// Initializes and returns an instance of UnsafeAppendBuffer.
/// </summary>
/// <param name="initialCapacity">The initial allocation size in bytes of the internal buffer.</param>
/// <param name="alignment">The byte alignment of the allocation. Must be a non-zero power of 2.</param>
/// <param name="allocator">The allocator to use.</param>
public UnsafeAppendBuffer(int initialCapacity, int alignment, AllocatorManager.AllocatorHandle allocator)
{
CheckAlignment(alignment);
Alignment = alignment;
Allocator = allocator;
Ptr = null;
Length = 0;
Capacity = 0;
SetCapacity(initialCapacity);
}
/// <summary>
/// Initializes and returns an instance of UnsafeAppendBuffer that aliases an existing buffer.
/// </summary>
/// <remarks>The capacity will be set to `length`, and <see cref="Length"/> will be set to 0.
/// </remarks>
/// <param name="ptr">The buffer to alias.</param>
/// <param name="length">The length in bytes of the buffer.</param>
public UnsafeAppendBuffer(void* ptr, int length)
{
Alignment = 0;
Allocator = AllocatorManager.None;
Ptr = (byte*)ptr;
Length = 0;
Capacity = length;
}
/// <summary>
/// Whether the append buffer is empty.
/// </summary>
/// <value>True if the append buffer is empty.</value>
public bool IsEmpty => Length == 0;
/// <summary>
/// Whether this append buffer has been allocated (and not yet deallocated).
/// </summary>
/// <value>True if this append buffer has been allocated (and not yet deallocated).</value>
public bool IsCreated => Ptr != null;
/// <summary>
/// Releases all resources (memory and safety handles).
/// </summary>
public void Dispose()
{
if (CollectionHelper.ShouldDeallocate(Allocator))
{
Memory.Unmanaged.Free(Ptr, Allocator);
Allocator = AllocatorManager.Invalid;
}
Ptr = null;
Length = 0;
Capacity = 0;
}
/// <summary>
/// Creates and schedules a job that will dispose this append buffer.
/// </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 append buffer. The new job depends upon inputDeps.</returns>
[NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
public JobHandle Dispose(JobHandle inputDeps)
{
if (CollectionHelper.ShouldDeallocate(Allocator))
{
var jobHandle = new UnsafeDisposeJob { Ptr = Ptr, Allocator = Allocator }.Schedule(inputDeps);
Ptr = null;
Allocator = AllocatorManager.Invalid;
return jobHandle;
}
Ptr = null;
return inputDeps;
}
/// <summary>
/// Sets the length to 0.
/// </summary>
/// <remarks>Does not change the capacity.</remarks>
public void Reset()
{
Length = 0;
}
/// <summary>
/// Sets the size in bytes of the internal buffer.
/// </summary>
/// <remarks>Does nothing if the new capacity is less than or equal to the current capacity.</remarks>
/// <param name="capacity">A new capacity in bytes.</param>
public void SetCapacity(int capacity)
{
if (capacity <= Capacity)
{
return;
}
capacity = math.max(64, math.ceilpow2(capacity));
var newPtr = (byte*)Memory.Unmanaged.Allocate(capacity, Alignment, Allocator);
if (Ptr != null)
{
UnsafeUtility.MemCpy(newPtr, Ptr, Length);
Memory.Unmanaged.Free(Ptr, Allocator);
}
Ptr = newPtr;
Capacity = capacity;
}
/// <summary>
/// Sets the length in bytes.
/// </summary>
/// <remarks>If the new length exceeds the capacity, capacity is expanded to the new length.</remarks>
/// <param name="length">The new length.</param>
public void ResizeUninitialized(int length)
{
SetCapacity(length);
Length = length;
}
/// <summary>
/// Appends an element to the end of this append buffer.
/// </summary>
/// <typeparam name="T">The type of the element.</typeparam>
/// <param name="value">The value to be appended.</param>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public void Add<T>(T value) where T : struct
{
var structSize = UnsafeUtility.SizeOf<T>();
SetCapacity(Length + structSize);
UnsafeUtility.CopyStructureToPtr(ref value, Ptr + Length);
Length += structSize;
}
/// <summary>
/// Appends an element to the end of this append buffer.
/// </summary>
/// <remarks>The value itself is stored, not the pointer.</remarks>
/// <param name="ptr">A pointer to the value to be appended.</param>
/// <param name="structSize">The size in bytes of the value to be appended.</param>
public void Add(void* ptr, int structSize)
{
SetCapacity(Length + structSize);
UnsafeUtility.MemCpy(Ptr + Length, ptr, structSize);
Length += structSize;
}
/// <summary>
/// Appends the elements of a buffer to the end of this append buffer.
/// </summary>
/// <typeparam name="T">The type of the buffer's elements.</typeparam>
/// <remarks>The values themselves are stored, not their pointers.</remarks>
/// <param name="ptr">A pointer to the buffer whose values will be appended.</param>
/// <param name="length">The number of elements to append.</param>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public void AddArray<T>(void* ptr, int length) where T : struct
{
Add(length);
if (length != 0)
Add(ptr, length * UnsafeUtility.SizeOf<T>());
}
/// <summary>
/// Appends all elements of an array to the end of this append buffer.
/// </summary>
/// <typeparam name="T">The type of the elements.</typeparam>
/// <param name="value">The array whose elements will all be appended.</param>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public void Add<T>(NativeArray<T> value) where T : struct
{
Add(value.Length);
Add(NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(value), UnsafeUtility.SizeOf<T>() * value.Length);
}
/// <summary>
/// Appends the content of a string as UTF-16 to the end of this append buffer.
/// </summary>
/// <remarks>Because some Unicode characters require two chars in UTF-16, each character is written as one or two chars (two or four bytes).
///
/// The length of the string is itself appended before adding the first character. If the string is null, appends the int `-1` but no character data.
///
/// A null terminator is not appended after the character data.</remarks>
/// <param name="value">The string to append.</param>
[NotBurstCompatible /* Deprecated */]
[Obsolete("Please use `AddNBC` from `Unity.Collections.LowLevel.Unsafe.NotBurstCompatible` namespace instead. (RemovedAfter 2021-06-22)", false)]
public void Add(string value) => NotBurstCompatible.Extensions.AddNBC(ref this, value);
/// <summary>
/// Removes and returns the last element of this append buffer.
/// </summary>
/// <typeparam name="T">The type of the element to remove.</typeparam>
/// <remarks>It is your responsibility to specify the correct type. Do not pop when the append buffer is empty.</remarks>
/// <returns>The element removed from the end of this append buffer.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public T Pop<T>() where T : struct
{
int structSize = UnsafeUtility.SizeOf<T>();
long ptr = (long)Ptr;
long size = Length;
long addr = ptr + size - structSize;
var data = UnsafeUtility.ReadArrayElement<T>((void*)addr, 0);
Length -= structSize;
return data;
}
/// <summary>
/// Removes and copies the last element of this append buffer.
/// </summary>
/// <remarks>It is your responsibility to specify the correct `structSize`. Do not pop when the append buffer is empty.</remarks>
/// <param name="ptr">The location to which the removed element will be copied.</param>
/// <param name="structSize">The size of the element to remove and copy.</param>
public void Pop(void* ptr, int structSize)
{
long data = (long)Ptr;
long size = Length;
long addr = data + size - structSize;
UnsafeUtility.MemCpy(ptr, (void*)addr, structSize);
Length -= structSize;
}
/// <summary>
/// Copies this append buffer to a managed array of bytes.
/// </summary>
/// <returns>A managed array of bytes.</returns>
[NotBurstCompatible /* Deprecated */]
[Obsolete("Please use `ToBytesNBC` from `Unity.Collections.LowLevel.Unsafe.NotBurstCompatible` namespace instead. (RemovedAfter 2021-06-22)", false)]
public byte[] ToBytes() => NotBurstCompatible.Extensions.ToBytesNBC(ref this);
/// <summary>
/// Returns a reader for this append buffer.
/// </summary>
/// <returns>A reader for the append buffer.</returns>
public Reader AsReader()
{
return new Reader(ref this);
}
/// <summary>
/// A reader for UnsafeAppendBuffer.
/// </summary>
[BurstCompatible]
public unsafe struct Reader
{
/// <summary>
/// The internal buffer where the content is stored.
/// </summary>
/// <value>The internal buffer where the content is stored.</value>
public readonly byte* Ptr;
/// <summary>
/// The length in bytes of the append buffer's content.
/// </summary>
/// <value>The length in bytes of the append buffer's content.</value>
public readonly int Size;
/// <summary>
/// The location of the next read (expressed as a byte offset from the start).
/// </summary>
/// <value>The location of the next read (expressed as a byte offset from the start).</value>
public int Offset;
/// <summary>
/// Initializes and returns an instance of UnsafeAppendBuffer.Reader.
/// </summary>
/// <param name="buffer">A reference to the append buffer to read.</param>
public Reader(ref UnsafeAppendBuffer buffer)
{
Ptr = buffer.Ptr;
Size = buffer.Length;
Offset = 0;
}
/// <summary>
/// Initializes and returns an instance of UnsafeAppendBuffer.Reader that reads from a buffer.
/// </summary>
/// <remarks>The buffer will be read *as if* it is an UnsafeAppendBuffer whether it was originally allocated as one or not.</remarks>
/// <param name="ptr">The buffer to read as an UnsafeAppendBuffer.</param>
/// <param name="length">The length in bytes of the </param>
public Reader(void* ptr, int length)
{
Ptr = (byte*)ptr;
Size = length;
Offset = 0;
}
/// <summary>
/// Whether the offset has advanced past the last of the append buffer's content.
/// </summary>
/// <value>Whether the offset has advanced past the last of the append buffer's content.</value>
public bool EndOfBuffer => Offset == Size;
/// <summary>
/// Reads an element from the append buffer.
/// </summary>
/// <remarks>Advances the reader's offset by the size of T.</remarks>
/// <typeparam name="T">The type of element to read.</typeparam>
/// <param name="value">Output for the element read.</param>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public void ReadNext<T>(out T value) where T : struct
{
var structSize = UnsafeUtility.SizeOf<T>();
CheckBounds(structSize);
UnsafeUtility.CopyPtrToStructure<T>(Ptr + Offset, out value);
Offset += structSize;
}
/// <summary>
/// Reads an element from the append buffer.
/// </summary>
/// <remarks>Advances the reader's offset by the size of T.</remarks>
/// <typeparam name="T">The type of element to read.</typeparam>
/// <returns>The element read.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public T ReadNext<T>() where T : struct
{
var structSize = UnsafeUtility.SizeOf<T>();
CheckBounds(structSize);
T value = UnsafeUtility.ReadArrayElement<T>(Ptr + Offset, 0);
Offset += structSize;
return value;
}
/// <summary>
/// Reads an element from the append buffer.
/// </summary>
/// <remarks>Advances the reader's offset by `structSize`.</remarks>
/// <param name="structSize">The size of the element to read.</param>
/// <returns>A pointer to where the read element resides in the append buffer.</returns>
public void* ReadNext(int structSize)
{
CheckBounds(structSize);
var value = (void*)((IntPtr)Ptr + Offset);
Offset += structSize;
return value;
}
/// <summary>
/// Reads an element from the append buffer.
/// </summary>
/// <remarks>Advances the reader's offset by the size of T.</remarks>
/// <typeparam name="T">The type of element to read.</typeparam>
/// <param name="value">Outputs a new array with length of 1. The read element is copied to the single index of this array.</param>
/// <param name="allocator">The allocator to use.</param>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public void ReadNext<T>(out NativeArray<T> value, AllocatorManager.AllocatorHandle allocator) where T : struct
{
var length = ReadNext<int>();
value = CollectionHelper.CreateNativeArray<T>(length, allocator);
var size = length * UnsafeUtility.SizeOf<T>();
if (size > 0)
{
var ptr = ReadNext(size);
UnsafeUtility.MemCpy(NativeArrayUnsafeUtility.GetUnsafePtr(value), ptr, size);
}
}
/// <summary>
/// Reads an array from the append buffer.
/// </summary>
/// <remarks>An array stored in the append buffer starts with an int specifying the number of values in the array.
/// The first element of an array immediately follows this int.
///
/// Advances the reader's offset by the size of the array (plus an int).</remarks>
/// <typeparam name="T">The type of elements in the array to read.</typeparam>
/// <param name="length">Output which is the number of elements in the read array.</param>
/// <returns>A pointer to where the first element of the read array resides in the append buffer.</returns>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public void* ReadNextArray<T>(out int length) where T : struct
{
length = ReadNext<int>();
return (length == 0) ? null : ReadNext(length * UnsafeUtility.SizeOf<T>());
}
#if !NET_DOTS
/// <summary>
/// Reads a UTF-16 string from the append buffer.
/// </summary>
/// <remarks>Because some Unicode characters require two chars in UTF-16, each character is either one or two chars (two or four bytes).
///
/// Assumes the string does not have a null terminator.
///
/// Advances the reader's offset by the size of the string (in bytes).</remarks>
/// <param name="value">Outputs the string read from the append buffer.</param>
[NotBurstCompatible /* Deprecated */]
[Obsolete("Please use `ReadNextNBC` from `Unity.Collections.LowLevel.Unsafe.NotBurstCompatible` namespace instead. (RemovedAfter 2021-06-22)", false)]
public void ReadNext(out string value) => NotBurstCompatible.Extensions.ReadNextNBC(ref this, out value);
#endif
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckBounds(int structSize)
{
if (Offset + structSize > Size)
{
throw new ArgumentException($"Requested value outside bounds of UnsafeAppendOnlyBuffer. Remaining bytes: {Size - Offset} Requested: {structSize}");
}
}
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
static void CheckAlignment(int alignment)
{
var zeroAlignment = alignment == 0;
var powTwoAlignment = ((alignment - 1) & alignment) == 0;
var validAlignment = (!zeroAlignment) && powTwoAlignment;
if (!validAlignment)
{
throw new ArgumentException($"Specified alignment must be non-zero positive power of two. Requested: {alignment}");
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 469c3f7cf80934f0fb79e0ab07665b81
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,187 @@
using System.Threading;
using Unity.Mathematics;
namespace Unity.Collections.LowLevel.Unsafe
{
/// <summary>
/// A 32-bit atomic counter.
/// </summary>
/// <remarks>Rather than have its own int, a counter *points* to an int. This arrangement lets counters in different jobs share reference to the same underlying int.</remarks>
[BurstCompatible]
public unsafe struct UnsafeAtomicCounter32
{
/// <summary>
/// The int that is modified by this counter.
/// </summary>
/// <value>The int that is modified by this counter.</value>
public int* Counter;
/// <summary>
/// Initializes and returns an instance of UnsafeAtomicCounter32.
/// </summary>
/// <param name="ptr">A pointer to the int to be modified by this counter.</param>
public UnsafeAtomicCounter32(void* ptr)
{
Counter = (int*)ptr;
}
/// <summary>
/// Non-atomically sets this counter to a value.
/// </summary>
/// <param name="value">The value to set. Defaults to 0</param>
public void Reset(int value = 0)
{
*Counter = value;
}
/// <summary>
/// Atomically adds a value to this counter.
/// </summary>
/// <param name="value">The value to add.</param>
/// <returns>The original value before the add.</returns>
public int Add(int value)
{
return Interlocked.Add(ref UnsafeUtility.AsRef<int>(Counter), value) - value;
}
/// <summary>
/// Atomically subtracts a value from this counter.
/// </summary>
/// <param name="value">The value to subtract.</param>
/// <returns>The original value before the subtract.</returns>
public int Sub(int value) => Add(-value);
/// <summary>
/// Atomically adds a value to this counter. The result will not be greater than a maximum value.
/// </summary>
/// <param name="value">The value to add to this counter.</param>
/// <param name="max">The maximum which the result will not be greater than.</param>
/// <returns>The original value before the add.</returns>
public int AddSat(int value, int max = int.MaxValue)
{
int oldVal;
int newVal = *Counter;
do
{
oldVal = newVal;
newVal = newVal >= max ? max : math.min(max, newVal + value);
newVal = Interlocked.CompareExchange(ref UnsafeUtility.AsRef<int>(Counter), newVal, oldVal);
}
while (oldVal != newVal && oldVal != max);
return oldVal;
}
/// <summary>
/// Atomically subtracts a value from this counter. The result will not be less than a minimum value.
/// </summary>
/// <param name="value">The value to subtract from this counter.</param>
/// <param name="min">The minimum which the result will not be less than.</param>
/// <returns>The original value before the subtract.</returns>
public int SubSat(int value, int min = int.MinValue)
{
int oldVal;
int newVal = *Counter;
do
{
oldVal = newVal;
newVal = newVal <= min ? min : math.max(min, newVal - value);
newVal = Interlocked.CompareExchange(ref UnsafeUtility.AsRef<int>(Counter), newVal, oldVal);
}
while (oldVal != newVal && oldVal != min);
return oldVal;
}
}
/// <summary>
/// A 64-bit atomic counter.
/// </summary>
/// <remarks>Rather than have its own long, a counter *points* to a long. This arrangement lets counters in different jobs share reference to the same underlying long.</remarks>
[BurstCompatible]
public unsafe struct UnsafeAtomicCounter64
{
/// <summary>
/// The long that is modified by this counter.
/// </summary>
/// <value>The long that is modified by this counter.</value>
public long* Counter;
/// <summary>
/// Initializes and returns an instance of UnsafeAtomicCounter64.
/// </summary>
/// <param name="ptr">A pointer to the long to be modified by this counter.</param>
public UnsafeAtomicCounter64(void* ptr)
{
Counter = (long*)ptr;
}
/// <summary>
/// Non-atomically sets this counter to a value.
/// </summary>
/// <param name="value">The value to set. Defaults to 0</param>
public void Reset(long value = 0)
{
*Counter = value;
}
/// <summary>
/// Atomically adds a value to this counter.
/// </summary>
/// <param name="value">The value to add.</param>
/// <returns>The original value before the add.</returns>
public long Add(long value)
{
return Interlocked.Add(ref UnsafeUtility.AsRef<long>(Counter), value) - value;
}
/// <summary>
/// Atomically subtracts a value from this counter.
/// </summary>
/// <param name="value">The value to subtract.</param>
/// <returns>The original value before the subtract.</returns>
public long Sub(long value) => Add(-value);
/// <summary>
/// Atomically adds a value to this counter. The result will not be greater than a maximum value.
/// </summary>
/// <param name="value">The value to add to this counter.</param>
/// <param name="max">The maximum which the result will not be greater than.</param>
/// <returns>The original value before the add.</returns>
public long AddSat(long value, long max = long.MaxValue)
{
long oldVal;
long newVal = *Counter;
do
{
oldVal = newVal;
newVal = newVal >= max ? max : math.min(max, newVal + value);
newVal = Interlocked.CompareExchange(ref UnsafeUtility.AsRef<long>(Counter), newVal, oldVal);
}
while (oldVal != newVal && oldVal != max);
return oldVal;
}
/// <summary>
/// Atomically subtracts a value from this counter. The result will not be less than a minimum value.
/// </summary>
/// <param name="value">The value to subtract from this counter.</param>
/// <param name="min">The minimum which the result will not be less than.</param>
/// <returns>The original value before the subtract.</returns>
public long SubSat(long value, long min = long.MinValue)
{
long oldVal;
long newVal = *Counter;
do
{
oldVal = newVal;
newVal = newVal <= min ? min : math.max(min, newVal - value);
newVal = Interlocked.CompareExchange(ref UnsafeUtility.AsRef<long>(Counter), newVal, oldVal);
}
while (oldVal != newVal && oldVal != min);
return oldVal;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5811fa70eb1e3da40b802cd058899005
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,679 @@
using System;
using System.Diagnostics;
using Unity.Jobs;
using Unity.Mathematics;
namespace Unity.Collections.LowLevel.Unsafe
{
/// <summary>
/// An arbitrarily-sized array of bits.
/// </summary>
/// <remarks>
/// The number of allocated bytes is always a multiple of 8. For example, a 65-bit array could fit in 9 bytes, but its allocation is actually 16 bytes.
/// </remarks>
[DebuggerDisplay("Length = {Length}, IsCreated = {IsCreated}")]
[DebuggerTypeProxy(typeof(UnsafeBitArrayDebugView))]
[BurstCompatible]
public unsafe struct UnsafeBitArray
: INativeDisposable
{
/// <summary>
/// Pointer to the data.
/// </summary>
/// <value>Pointer to the data.</value>
[NativeDisableUnsafePtrRestriction]
public ulong* Ptr;
/// <summary>
/// The number of bits.
/// </summary>
/// <value>The number of bits.</value>
public int Length;
/// <summary>
/// The allocator to use.
/// </summary>
/// <value>The allocator to use.</value>
public AllocatorManager.AllocatorHandle Allocator;
/// <summary>
/// Initializes and returns an instance of UnsafeBitArray which aliases an existing buffer.
/// </summary>
/// <param name="ptr">An existing buffer.</param>
/// <param name="allocator">The allocator that was used to allocate the bytes. Needed to dispose this array.</param>
/// <param name="sizeInBytes">The number of bytes. The length will be `sizeInBytes * 8`.</param>
public unsafe UnsafeBitArray(void* ptr, int sizeInBytes, AllocatorManager.AllocatorHandle allocator = new AllocatorManager.AllocatorHandle())
{
CheckSizeMultipleOf8(sizeInBytes);
Ptr = (ulong*)ptr;
Length = sizeInBytes * 8;
Allocator = allocator;
}
/// <summary>
/// Initializes and returns an instance of UnsafeBitArray.
/// </summary>
/// <param name="numBits">Number of bits.</param>
/// <param name="allocator">The allocator to use.</param>
/// <param name="options">Whether newly allocated bytes should be zeroed out.</param>
public UnsafeBitArray(int numBits, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory)
{
CollectionHelper.CheckAllocator(allocator);
Allocator = allocator;
var sizeInBytes = Bitwise.AlignUp(numBits, 64) / 8;
Ptr = (ulong*)Memory.Unmanaged.Allocate(sizeInBytes, 16, allocator);
Length = numBits;
if (options == NativeArrayOptions.ClearMemory)
{
UnsafeUtility.MemClear(Ptr, sizeInBytes);
}
}
/// <summary>
/// Whether this array has been allocated (and not yet deallocated).
/// </summary>
/// <value>True if this array has been allocated (and not yet deallocated).</value>
public bool IsCreated => Ptr != null;
/// <summary>
/// Releases all resources (memory and safety handles).
/// </summary>
public void Dispose()
{
if (CollectionHelper.ShouldDeallocate(Allocator))
{
Memory.Unmanaged.Free(Ptr, Allocator);
Allocator = AllocatorManager.Invalid;
}
Ptr = null;
Length = 0;
}
/// <summary>
/// Creates and schedules a job that will dispose this array.
/// </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 array. The new job depends upon inputDeps.</returns>
[NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
public JobHandle Dispose(JobHandle inputDeps)
{
if (CollectionHelper.ShouldDeallocate(Allocator))
{
var jobHandle = new UnsafeDisposeJob { Ptr = Ptr, Allocator = Allocator }.Schedule(inputDeps);
Ptr = null;
Allocator = AllocatorManager.Invalid;
return jobHandle;
}
Ptr = null;
return inputDeps;
}
/// <summary>
/// Sets all the bits to 0.
/// </summary>
public void Clear()
{
var sizeInBytes = Bitwise.AlignUp(Length, 64) / 8;
UnsafeUtility.MemClear(Ptr, sizeInBytes);
}
/// <summary>
/// Sets the bit at an index to 0 or 1.
/// </summary>
/// <param name="pos">Index of the bit to set.</param>
/// <param name="value">True for 1, false for 0.</param>
public void Set(int pos, bool value)
{
CheckArgs(pos, 1);
var idx = pos >> 6;
var shift = pos & 0x3f;
var mask = 1ul << shift;
var bits = (Ptr[idx] & ~mask) | ((ulong)-Bitwise.FromBool(value) & mask);
Ptr[idx] = bits;
}
/// <summary>
/// Sets a range of bits to 0 or 1.
/// </summary>
/// <remarks>
/// The range runs from index `pos` up to (but not including) `pos + numBits`.
/// No exception is thrown if `pos + numBits` exceeds the length.
/// </remarks>
/// <param name="pos">Index of the first bit to set.</param>
/// <param name="value">True for 1, false for 0.</param>
/// <param name="numBits">Number of bits to set.</param>
/// <exception cref="ArgumentException">Thrown if pos is out of bounds or if numBits is less than 1.</exception>
public void SetBits(int pos, bool value, int numBits)
{
CheckArgs(pos, numBits);
var end = math.min(pos + numBits, Length);
var idxB = pos >> 6;
var shiftB = pos & 0x3f;
var idxE = (end - 1) >> 6;
var shiftE = end & 0x3f;
var maskB = 0xfffffffffffffffful << shiftB;
var maskE = 0xfffffffffffffffful >> (64 - shiftE);
var orBits = (ulong)-Bitwise.FromBool(value);
var orBitsB = maskB & orBits;
var orBitsE = maskE & orBits;
var cmaskB = ~maskB;
var cmaskE = ~maskE;
if (idxB == idxE)
{
var maskBE = maskB & maskE;
var cmaskBE = ~maskBE;
var orBitsBE = orBitsB & orBitsE;
Ptr[idxB] = (Ptr[idxB] & cmaskBE) | orBitsBE;
return;
}
Ptr[idxB] = (Ptr[idxB] & cmaskB) | orBitsB;
for (var idx = idxB + 1; idx < idxE; ++idx)
{
Ptr[idx] = orBits;
}
Ptr[idxE] = (Ptr[idxE] & cmaskE) | orBitsE;
}
/// <summary>
/// Copies bits of a ulong to bits in this array.
/// </summary>
/// <remarks>
/// The destination bits in this array run from index `pos` up to (but not including) `pos + numBits`.
/// No exception is thrown if `pos + numBits` exceeds the length.
///
/// The lowest bit of the ulong is copied to the first destination bit; the second-lowest bit of the ulong is
/// copied to the second destination bit; and so forth.
/// </remarks>
/// <param name="pos">Index of the first bit to set.</param>
/// <param name="value">Unsigned long from which to copy bits.</param>
/// <param name="numBits">Number of bits to set (must be between 1 and 64).</param>
/// <exception cref="ArgumentException">Thrown if pos is out of bounds or if numBits is not between 1 and 64.</exception>
public void SetBits(int pos, ulong value, int numBits = 1)
{
CheckArgsUlong(pos, numBits);
var idxB = pos >> 6;
var shiftB = pos & 0x3f;
if (shiftB + numBits <= 64)
{
var mask = 0xfffffffffffffffful >> (64 - numBits);
Ptr[idxB] = Bitwise.ReplaceBits(Ptr[idxB], shiftB, mask, value);
return;
}
var end = math.min(pos + numBits, Length);
var idxE = (end - 1) >> 6;
var shiftE = end & 0x3f;
var maskB = 0xfffffffffffffffful >> shiftB;
Ptr[idxB] = Bitwise.ReplaceBits(Ptr[idxB], shiftB, maskB, value);
var valueE = value >> (64 - shiftB);
var maskE = 0xfffffffffffffffful >> (64 - shiftE);
Ptr[idxE] = Bitwise.ReplaceBits(Ptr[idxE], 0, maskE, valueE);
}
/// <summary>
/// Returns a ulong which has bits copied from this array.
/// </summary>
/// <remarks>
/// The source bits in this array run from index `pos` up to (but not including) `pos + numBits`.
/// No exception is thrown if `pos + numBits` exceeds the length.
///
/// The first source bit is copied to the lowest bit of the ulong; the second source bit is copied to the second-lowest bit of the ulong; and so forth. Any remaining bits in the ulong will be 0.
/// </remarks>
/// <param name="pos">Index of the first bit to get.</param>
/// <param name="numBits">Number of bits to get (must be between 1 and 64).</param>
/// <exception cref="ArgumentException">Thrown if pos is out of bounds or if numBits is not between 1 and 64.</exception>
/// <returns>A ulong which has bits copied from this array.</returns>
public ulong GetBits(int pos, int numBits = 1)
{
CheckArgsUlong(pos, numBits);
var idxB = pos >> 6;
var shiftB = pos & 0x3f;
if (shiftB + numBits <= 64)
{
var mask = 0xfffffffffffffffful >> (64 - numBits);
return Bitwise.ExtractBits(Ptr[idxB], shiftB, mask);
}
var end = math.min(pos + numBits, Length);
var idxE = (end - 1) >> 6;
var shiftE = end & 0x3f;
var maskB = 0xfffffffffffffffful >> shiftB;
ulong valueB = Bitwise.ExtractBits(Ptr[idxB], shiftB, maskB);
var maskE = 0xfffffffffffffffful >> (64 - shiftE);
ulong valueE = Bitwise.ExtractBits(Ptr[idxE], 0, maskE);
return (valueE << (64 - shiftB)) | valueB;
}
/// <summary>
/// Returns true if the bit at an index is 1.
/// </summary>
/// <param name="pos">Index of the bit to test.</param>
/// <returns>True if the bit at the index is 1.</returns>
/// <exception cref="ArgumentException">Thrown if `pos` is out of bounds.</exception>
public bool IsSet(int pos)
{
CheckArgs(pos, 1);
var idx = pos >> 6;
var shift = pos & 0x3f;
var mask = 1ul << shift;
return 0ul != (Ptr[idx] & mask);
}
internal void CopyUlong(int dstPos, ref UnsafeBitArray srcBitArray, int srcPos, int numBits) => SetBits(dstPos, srcBitArray.GetBits(srcPos, numBits), numBits);
/// <summary>
/// Copies a range of bits from this array to another range in this array.
/// </summary>
/// <remarks>
/// The bits to copy run from index `srcPos` up to (but not including) `srcPos + numBits`.
/// The bits to set run from index `dstPos` up to (but not including) `dstPos + numBits`.
///
/// The ranges may overlap, but the result in the overlapping region is undefined.
/// </remarks>
/// <param name="dstPos">Index of the first bit to set.</param>
/// <param name="srcPos">Index of the first bit to copy.</param>
/// <param name="numBits">Number of bits to copy.</param>
/// <exception cref="ArgumentException">Thrown if either `dstPos + numBits` or `srcPos + numBits` exceed the length of this array.</exception>
public void Copy(int dstPos, int srcPos, int numBits)
{
if (dstPos == srcPos)
{
return;
}
Copy(dstPos, ref this, srcPos, numBits);
}
/// <summary>
/// Copies a range of bits from an array to a range of bits in this array.
/// </summary>
/// <remarks>
/// The bits to copy in the source array run from index srcPos up to (but not including) `srcPos + numBits`.
/// The bits to set in the destination array run from index dstPos up to (but not including) `dstPos + numBits`.
///
/// It's fine if source and destination array are one and the same, even if the ranges overlap, but the result in the overlapping region is undefined.
/// </remarks>
/// <param name="dstPos">Index of the first bit to set.</param>
/// <param name="srcBitArray">The source array.</param>
/// <param name="srcPos">Index of the first bit to copy.</param>
/// <param name="numBits">The number of bits to copy.</param>
/// <exception cref="ArgumentException">Thrown if either `dstPos + numBits` or `srcBitArray + numBits` exceed the length of this array.</exception>
public void Copy(int dstPos, ref UnsafeBitArray srcBitArray, int srcPos, int numBits)
{
if (numBits == 0)
{
return;
}
CheckArgsCopy(ref this, dstPos, ref srcBitArray, srcPos, numBits);
if (numBits <= 64) // 1x CopyUlong
{
CopyUlong(dstPos, ref srcBitArray, srcPos, numBits);
}
else if (numBits <= 128) // 2x CopyUlong
{
CopyUlong(dstPos, ref srcBitArray, srcPos, 64);
numBits -= 64;
if (numBits > 0)
{
CopyUlong(dstPos + 64, ref srcBitArray, srcPos + 64, numBits);
}
}
else if ((dstPos & 7) == (srcPos & 7)) // aligned copy
{
var dstPosInBytes = CollectionHelper.Align(dstPos, 8) >> 3;
var srcPosInBytes = CollectionHelper.Align(srcPos, 8) >> 3;
var numPreBits = dstPosInBytes * 8 - dstPos;
if (numPreBits > 0)
{
CopyUlong(dstPos, ref srcBitArray, srcPos, numPreBits);
}
var numBitsLeft = numBits - numPreBits;
var numBytes = numBitsLeft / 8;
if (numBytes > 0)
{
unsafe
{
UnsafeUtility.MemMove((byte*)Ptr + dstPosInBytes, (byte*)srcBitArray.Ptr + srcPosInBytes, numBytes);
}
}
var numPostBits = numBitsLeft & 7;
if (numPostBits > 0)
{
CopyUlong((dstPosInBytes + numBytes) * 8, ref srcBitArray, (srcPosInBytes + numBytes) * 8, numPostBits);
}
}
else // unaligned copy
{
var dstPosAligned = CollectionHelper.Align(dstPos, 64);
var numPreBits = dstPosAligned - dstPos;
if (numPreBits > 0)
{
CopyUlong(dstPos, ref srcBitArray, srcPos, numPreBits);
numBits -= numPreBits;
dstPos += numPreBits;
srcPos += numPreBits;
}
for (; numBits >= 64; numBits -= 64, dstPos += 64, srcPos += 64)
{
Ptr[dstPos >> 6] = srcBitArray.GetBits(srcPos, 64);
}
if (numBits > 0)
{
CopyUlong(dstPos, ref srcBitArray, srcPos, numBits);
}
}
}
/// <summary>
/// Returns the index of the first occurrence in this array of *N* contiguous 0 bits.
/// </summary>
/// <remarks>The search is linear.</remarks>
/// <param name="pos">Index of the bit at which to start searching.</param>
/// <param name="numBits">Number of contiguous 0 bits to look for.</param>
/// <returns>The index of the first occurrence in this array of `numBits` contiguous 0 bits. Range is pos up to (but not including) the length of this array. Returns -1 if no occurrence is found.</returns>
public int Find(int pos, int numBits)
{
var count = Length - pos;
CheckArgsPosCount(pos, count, numBits);
return Bitwise.Find(Ptr, pos, count, numBits);
}
/// <summary>
/// Returns the index of the first occurrence in this array of a given number of contiguous 0 bits.
/// </summary>
/// <remarks>The search is linear.</remarks>
/// <param name="pos">Index of the bit at which to start searching.</param>
/// <param name="numBits">Number of contiguous 0 bits to look for.</param>
/// <param name="count">Number of indexes to consider as the return value.</param>
/// <returns>The index of the first occurrence in this array of `numBits` contiguous 0 bits. Range is pos up to (but not including) `pos + count`. Returns -1 if no occurrence is found.</returns>
public int Find(int pos, int count, int numBits)
{
CheckArgsPosCount(pos, count, numBits);
return Bitwise.Find(Ptr, pos, count, numBits);
}
/// <summary>
/// Returns true if none of the bits in a range are 1 (*i.e.* all bits in the range are 0).
/// </summary>
/// <param name="pos">Index of the bit at which to start searching.</param>
/// <param name="numBits">Number of bits to test. Defaults to 1.</param>
/// <returns>Returns true if none of the bits in range `pos` up to (but not including) `pos + numBits` are 1.</returns>
/// <exception cref="ArgumentException">Thrown if `pos` is out of bounds or `numBits` is less than 1.</exception>
public bool TestNone(int pos, int numBits = 1)
{
CheckArgs(pos, numBits);
var end = math.min(pos + numBits, Length);
var idxB = pos >> 6;
var shiftB = pos & 0x3f;
var idxE = (end - 1) >> 6;
var shiftE = end & 0x3f;
var maskB = 0xfffffffffffffffful << shiftB;
var maskE = 0xfffffffffffffffful >> (64 - shiftE);
if (idxB == idxE)
{
var mask = maskB & maskE;
return 0ul == (Ptr[idxB] & mask);
}
if (0ul != (Ptr[idxB] & maskB))
{
return false;
}
for (var idx = idxB + 1; idx < idxE; ++idx)
{
if (0ul != Ptr[idx])
{
return false;
}
}
return 0ul == (Ptr[idxE] & maskE);
}
/// <summary>
/// Returns true if at least one of the bits in a range is 1.
/// </summary>
/// <param name="pos">Index of the bit at which to start searching.</param>
/// <param name="numBits">Number of bits to test. Defaults to 1.</param>
/// <returns>True if one or more of the bits in range `pos` up to (but not including) `pos + numBits` are 1.</returns>
/// <exception cref="ArgumentException">Thrown if `pos` is out of bounds or `numBits` is less than 1.</exception>
public bool TestAny(int pos, int numBits = 1)
{
CheckArgs(pos, numBits);
var end = math.min(pos + numBits, Length);
var idxB = pos >> 6;
var shiftB = pos & 0x3f;
var idxE = (end - 1) >> 6;
var shiftE = end & 0x3f;
var maskB = 0xfffffffffffffffful << shiftB;
var maskE = 0xfffffffffffffffful >> (64 - shiftE);
if (idxB == idxE)
{
var mask = maskB & maskE;
return 0ul != (Ptr[idxB] & mask);
}
if (0ul != (Ptr[idxB] & maskB))
{
return true;
}
for (var idx = idxB + 1; idx < idxE; ++idx)
{
if (0ul != Ptr[idx])
{
return true;
}
}
return 0ul != (Ptr[idxE] & maskE);
}
/// <summary>
/// Returns true if all of the bits in a range are 1.
/// </summary>
/// <param name="pos">Index of the bit at which to start searching.</param>
/// <param name="numBits">Number of bits to test. Defaults to 1.</param>
/// <returns>True if all of the bits in range `pos` up to (but not including) `pos + numBits` are 1.</returns>
/// <exception cref="ArgumentException">Thrown if `pos` is out of bounds or `numBits` is less than 1.</exception>
public bool TestAll(int pos, int numBits = 1)
{
CheckArgs(pos, numBits);
var end = math.min(pos + numBits, Length);
var idxB = pos >> 6;
var shiftB = pos & 0x3f;
var idxE = (end - 1) >> 6;
var shiftE = end & 0x3f;
var maskB = 0xfffffffffffffffful << shiftB;
var maskE = 0xfffffffffffffffful >> (64 - shiftE);
if (idxB == idxE)
{
var mask = maskB & maskE;
return mask == (Ptr[idxB] & mask);
}
if (maskB != (Ptr[idxB] & maskB))
{
return false;
}
for (var idx = idxB + 1; idx < idxE; ++idx)
{
if (0xfffffffffffffffful != Ptr[idx])
{
return false;
}
}
return maskE == (Ptr[idxE] & maskE);
}
/// <summary>
/// Returns the number of bits in a range that are 1.
/// </summary>
/// <param name="pos">Index of the bit at which to start searching.</param>
/// <param name="numBits">Number of bits to test. Defaults to 1.</param>
/// <returns>The number of bits in a range of bits that are 1.</returns>
/// <exception cref="ArgumentException">Thrown if `pos` is out of bounds or `numBits` is less than 1.</exception>
public int CountBits(int pos, int numBits = 1)
{
CheckArgs(pos, numBits);
var end = math.min(pos + numBits, Length);
var idxB = pos >> 6;
var shiftB = pos & 0x3f;
var idxE = (end - 1) >> 6;
var shiftE = end & 0x3f;
var maskB = 0xfffffffffffffffful << shiftB;
var maskE = 0xfffffffffffffffful >> (64 - shiftE);
if (idxB == idxE)
{
var mask = maskB & maskE;
return math.countbits(Ptr[idxB] & mask);
}
var count = math.countbits(Ptr[idxB] & maskB);
for (var idx = idxB + 1; idx < idxE; ++idx)
{
count += math.countbits(Ptr[idx]);
}
count += math.countbits(Ptr[idxE] & maskE);
return count;
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
static void CheckSizeMultipleOf8(int sizeInBytes)
{
if ((sizeInBytes & 7) != 0)
throw new ArgumentException($"BitArray invalid arguments: sizeInBytes {sizeInBytes} (must be multiple of 8-bytes, sizeInBytes: {sizeInBytes}).");
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckArgs(int pos, int numBits)
{
if (pos < 0
|| pos >= Length
|| numBits < 1)
{
throw new ArgumentException($"BitArray invalid arguments: pos {pos} (must be 0-{Length - 1}), numBits {numBits} (must be greater than 0).");
}
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckArgsPosCount(int begin, int count, int numBits)
{
if (begin < 0 || begin >= Length)
{
throw new ArgumentException($"BitArray invalid argument: begin {begin} (must be 0-{Length - 1}).");
}
if (count < 0 || count > Length)
{
throw new ArgumentException($"BitArray invalid argument: count {count} (must be 0-{Length}).");
}
if (numBits < 1 || count < numBits)
{
throw new ArgumentException($"BitArray invalid argument: numBits {numBits} (must be greater than 0).");
}
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
void CheckArgsUlong(int pos, int numBits)
{
CheckArgs(pos, numBits);
if (numBits < 1 || numBits > 64)
{
throw new ArgumentException($"BitArray invalid arguments: numBits {numBits} (must be 1-64).");
}
if (pos + numBits > Length)
{
throw new ArgumentException($"BitArray invalid arguments: Out of bounds pos {pos}, numBits {numBits}, Length {Length}.");
}
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
static void CheckArgsCopy(ref UnsafeBitArray dstBitArray, int dstPos, ref UnsafeBitArray srcBitArray, int srcPos, int numBits)
{
if (srcPos + numBits > srcBitArray.Length)
{
throw new ArgumentException($"BitArray invalid arguments: Out of bounds - source position {srcPos}, numBits {numBits}, source bit array Length {srcBitArray.Length}.");
}
if (dstPos + numBits > dstBitArray.Length)
{
throw new ArgumentException($"BitArray invalid arguments: Out of bounds - destination position {dstPos}, numBits {numBits}, destination bit array Length {dstBitArray.Length}.");
}
}
}
sealed class UnsafeBitArrayDebugView
{
UnsafeBitArray Data;
public UnsafeBitArrayDebugView(UnsafeBitArray data)
{
Data = data;
}
public bool[] Bits
{
get
{
var array = new bool[Data.Length];
for (int i = 0; i < Data.Length; ++i)
{
array[i] = Data.IsSet(i);
}
return array;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a1b7be6af453f88438d32c371f28015c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show more