|
using System; |
|
using System.Collections; |
|
using System.Collections.Generic; |
|
#if FAKE_UNSAFE_UTILITY |
|
using System.Runtime.InteropServices; |
|
#else |
|
using Unity.Collections; |
|
using Unity.Collections.LowLevel.Unsafe; |
|
#endif |
|
|
|
namespace Container |
|
{ |
|
#if FAKE_UNSAFE_UTILITY |
|
public enum Allocator |
|
{ |
|
Temp = 0, |
|
Persistent = 1 |
|
} |
|
|
|
public static unsafe class UnsafeUtility |
|
{ |
|
[DllImport("kernel32.dll", SetLastError = true)] |
|
private static extern void* HeapCreate(uint flOptions, long dwInitialSize, long dwMaximumSize); |
|
|
|
[DllImport("kernel32.dll", SetLastError = true)] |
|
private static extern bool HeapDestroy(void* hHeap); |
|
|
|
[DllImport("kernel32.dll", SetLastError = true)] |
|
private static extern void* HeapAlloc(void* hHeap, uint dwFlags, long dwBytes); |
|
|
|
[DllImport("kernel32.dll", SetLastError = true)] |
|
private static extern bool HeapFree(void* hHeap, uint dwFlags, void* lpMem); |
|
|
|
[DllImport("msvcrt.dll", EntryPoint = "memset", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] |
|
private static extern void* memset(void* dst, int c, long count); |
|
|
|
[DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] |
|
private static extern void* memcpy(void* dst, void* src, long count); |
|
|
|
[DllImport("msvcrt.dll", EntryPoint = "memcmp", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] |
|
private static extern int memcmp(void* lhs, void* rhs, long count); |
|
|
|
private static void* hTempHeap; |
|
private static void* hPersistentHeap; |
|
|
|
static UnsafeUtility() |
|
{ |
|
hTempHeap = HeapCreate(0, 0, 0); |
|
hPersistentHeap = HeapCreate(0, 0, 0); |
|
} |
|
|
|
public static void* Malloc(long size, int align, Allocator label) |
|
{ |
|
void* hHeap = hTempHeap; |
|
if (label == Allocator.Persistent) |
|
{ |
|
hHeap = hPersistentHeap; |
|
} |
|
return HeapAlloc(hHeap, 0, size); |
|
} |
|
|
|
public static void Free(void* ptr, Allocator label) |
|
{ |
|
void* hHeap = hTempHeap; |
|
if (label == Allocator.Persistent) |
|
{ |
|
hHeap = hPersistentHeap; |
|
} |
|
HeapFree(hHeap, 0, ptr); |
|
} |
|
|
|
public static void MemClear(void* ptr, long size) |
|
{ |
|
memset(ptr, 0, size); |
|
} |
|
|
|
public static void MemCpy(void* dst, void* src, long size) |
|
{ |
|
memcpy(dst, src, size); |
|
} |
|
|
|
public static int MemCmp(void* dst, void* src, long size) |
|
{ |
|
return memcmp(dst, src, size); |
|
} |
|
} |
|
#endif |
|
|
|
public unsafe struct JenkinsHash |
|
{ |
|
uint c, b, a, l; |
|
|
|
static uint rot(uint n, int s) |
|
{ |
|
return (n << s) | (n >> (32 - s)); |
|
} |
|
|
|
static void mix(JenkinsHash* h) |
|
{ |
|
uint a = h->a, b = h->b, c = h->c; |
|
a -= c; |
|
a ^= rot(c, 4); |
|
c += b; |
|
b -= a; |
|
b ^= rot(a, 6); |
|
a += c; |
|
c -= b; |
|
c ^= rot(b, 8); |
|
b += a; |
|
a -= c; |
|
a ^= rot(c, 16); |
|
c += b; |
|
b -= a; |
|
b ^= rot(a, 19); |
|
a += c; |
|
c -= b; |
|
c ^= rot(b, 4); |
|
b += a; |
|
h->a = a; |
|
h->b = b; |
|
h->c = c; |
|
} |
|
|
|
static void final(JenkinsHash* h) |
|
{ |
|
uint a = h->a, b = h->b, c = h->c; |
|
c ^= b; |
|
c -= rot(b, 14); |
|
a ^= c; |
|
a -= rot(c, 11); |
|
b ^= a; |
|
b -= rot(a, 25); |
|
c ^= b; |
|
c -= rot(b, 16); |
|
a ^= c; |
|
a -= rot(c, 4); |
|
b ^= a; |
|
b -= rot(a, 14); |
|
c ^= b; |
|
c -= rot(b, 24); |
|
h->a = a; |
|
h->b = b; |
|
h->c = c; |
|
} |
|
|
|
static void add12(JenkinsHash* h, uint* k) |
|
{ |
|
h->a += k[0]; |
|
h->b += k[1]; |
|
h->c += k[2]; |
|
mix(h); |
|
h->l -= 12; |
|
} |
|
|
|
static void addLast(JenkinsHash* h, uint* k) |
|
{ |
|
switch (h->l) |
|
{ |
|
case 12: |
|
h->c += k[2]; |
|
h->b += k[1]; |
|
h->a += k[0]; |
|
break; |
|
case 11: |
|
h->c += k[2] & 0xffffff; |
|
h->b += k[1]; |
|
h->a += k[0]; |
|
break; |
|
case 10: |
|
h->c += k[2] & 0xffff; |
|
h->b += k[1]; |
|
h->a += k[0]; |
|
break; |
|
case 9: |
|
h->c += k[2] & 0xff; |
|
h->b += k[1]; |
|
h->a += k[0]; |
|
break; |
|
case 8: |
|
h->b += k[1]; |
|
h->a += k[0]; |
|
break; |
|
case 7: |
|
h->b += k[1] & 0xffffff; |
|
h->a += k[0]; |
|
break; |
|
case 6: |
|
h->b += k[1] & 0xffff; |
|
h->a += k[0]; |
|
break; |
|
case 5: |
|
h->b += k[1] & 0xff; |
|
h->a += k[0]; |
|
break; |
|
case 4: |
|
h->a += k[0]; |
|
break; |
|
case 3: |
|
h->a += k[0] & 0xffffff; |
|
break; |
|
case 2: |
|
h->a += k[0] & 0xffff; |
|
break; |
|
case 1: |
|
h->a += k[0] & 0xff; |
|
break; |
|
case 0: |
|
return; /* zero length strings require no mixing */ |
|
} |
|
|
|
final(h); |
|
} |
|
|
|
static ulong value64(JenkinsHash* h) |
|
{ |
|
return h->c | ((ulong)h->b << 32); |
|
} |
|
|
|
static uint value32(JenkinsHash* h) |
|
{ |
|
return h->c; |
|
} |
|
|
|
public static uint Hash32(uint* k, int l) |
|
{ |
|
JenkinsHash h = new JenkinsHash(); |
|
h.a = h.b = h.c = 0xdeadbeefu + (uint)l; |
|
h.l = (uint)l; |
|
while (h.l > 12) |
|
{ |
|
add12(&h, k); |
|
k += 3; |
|
} |
|
addLast(&h, k); |
|
return value32(&h); |
|
} |
|
} |
|
|
|
// FastHashMap is a map from a byte[] key to a byte[] value. |
|
// Internally it is based off bytell_hash_map (https://github.com/skarupke/flat_hash_map/blob/master/bytell_hash_map.hpp) |
|
// However, it uses a power-of-two size and simply trusts that the hash is good |
|
public unsafe struct FastHashMapData |
|
{ |
|
public byte* entries; |
|
public int numSlotsMinusOne; |
|
public int numElements; |
|
public int keySize; |
|
public int valueSize; |
|
public bool hashKeys; |
|
public bool pathNormalizeKeys; |
|
public bool isMultiMap; |
|
|
|
const float kMaxLoadFactor = 0.9375f; // 15/16 |
|
const int kSlotsPerBlock = 8; |
|
const byte kEmptySlot = 0xff; |
|
const byte kReservedSlot = 0xfe; |
|
const byte kListEntry = 0x80; |
|
const byte kDistance = 0x7f; |
|
const byte kNumJumpDistances = 0x7e; |
|
|
|
// Using a managed array is not allowed in burst, so we can either copy |
|
// that array to a statically allocated memory block, or we can just use |
|
// a giant switch statement: |
|
private static long JumpDistance(int i) |
|
{ |
|
switch (i) |
|
{ |
|
default: return 0; |
|
case 1: return 1; |
|
case 2: return 2; |
|
case 3: return 3; |
|
case 4: return 4; |
|
case 5: return 5; |
|
case 6: return 6; |
|
case 7: return 7; |
|
case 8: return 8; |
|
case 9: return 9; |
|
case 10: return 10; |
|
case 11: return 11; |
|
case 12: return 12; |
|
case 13: return 13; |
|
case 14: return 14; |
|
case 15: return 15; |
|
case 16: return 21; |
|
case 17: return 28; |
|
case 18: return 36; |
|
case 19: return 45; |
|
case 20: return 55; |
|
case 21: return 66; |
|
case 22: return 78; |
|
case 23: return 91; |
|
case 24: return 105; |
|
case 25: return 120; |
|
case 26: return 136; |
|
case 27: return 153; |
|
case 28: return 171; |
|
case 29: return 190; |
|
case 30: return 210; |
|
case 31: return 231; |
|
case 32: return 253; |
|
case 33: return 276; |
|
case 34: return 300; |
|
case 35: return 325; |
|
case 36: return 351; |
|
case 37: return 378; |
|
case 38: return 406; |
|
case 39: return 435; |
|
case 40: return 465; |
|
case 41: return 496; |
|
case 42: return 528; |
|
case 43: return 561; |
|
case 44: return 595; |
|
case 45: return 630; |
|
case 46: return 666; |
|
case 47: return 703; |
|
case 48: return 741; |
|
case 49: return 780; |
|
case 50: return 820; |
|
case 51: return 861; |
|
case 52: return 903; |
|
case 53: return 946; |
|
case 54: return 990; |
|
case 55: return 1035; |
|
case 56: return 1081; |
|
case 57: return 1128; |
|
case 58: return 1176; |
|
case 59: return 1225; |
|
case 60: return 1275; |
|
case 61: return 1326; |
|
case 62: return 1378; |
|
case 63: return 1431; |
|
case 64: return 1485; |
|
case 65: return 1540; |
|
case 66: return 1596; |
|
case 67: return 1653; |
|
case 68: return 1711; |
|
case 69: return 1770; |
|
case 70: return 1830; |
|
case 71: return 1891; |
|
case 72: return 1953; |
|
case 73: return 2016; |
|
case 74: return 2080; |
|
case 75: return 2145; |
|
case 76: return 2211; |
|
case 77: return 2278; |
|
case 78: return 2346; |
|
case 79: return 2415; |
|
case 80: return 2485; |
|
case 81: return 2556; |
|
case 82: return 3741; |
|
case 83: return 8385; |
|
case 84: return 18915; |
|
case 85: return 42486; |
|
case 86: return 95703; |
|
case 87: return 215496; |
|
case 88: return 485605; |
|
case 89: return 1091503; |
|
case 90: return 2456436; |
|
case 91: return 5529475; |
|
case 92: return 12437578; |
|
case 93: return 27986421; |
|
case 94: return 62972253; |
|
case 95: return 141700195; |
|
case 96: return 318819126; |
|
case 97: return 717314626; |
|
case 98: return 1614000520; |
|
case 99: return 3631437253; |
|
case 100: return 8170829695; |
|
case 101: return 18384318876; |
|
case 102: return 41364501751; |
|
case 103: return 93070021080; |
|
case 104: return 209407709220; |
|
case 105: return 471167588430; |
|
case 106: return 1060127437995; |
|
case 107: return 2385287281530; |
|
case 108: return 5366895564381; |
|
case 109: return 12075513791265; |
|
case 110: return 27169907873235; |
|
case 111: return 61132301007778; |
|
case 112: return 137547673121001; |
|
case 113: return 309482258302503; |
|
case 114: return 696335090510256; |
|
case 115: return 1566753939653640; |
|
case 116: return 3525196427195653; |
|
case 117: return 7931691866727775; |
|
case 118: return 17846306747368716; |
|
case 119: return 40154190394120111; |
|
case 120: return 90346928493040500; |
|
case 121: return 203280588949935750; |
|
case 122: return 457381324898247375; |
|
case 123: return 1029107980662394500; |
|
case 124: return 2315492957028380766; |
|
case 125: return 5209859150892887590; |
|
} |
|
} |
|
|
|
private static int NumSlots(int items) |
|
{ |
|
// next power of two |
|
uint v = (uint)(items - 1); |
|
return (int)(1 + (v | v >> 1 | v >> 2 | v >> 4 | v >> 8 | v >> 16)); |
|
} |
|
|
|
private static int BlockSize(FastHashMapData* data) |
|
{ |
|
return kSlotsPerBlock + (data->keySize + data->valueSize) * kSlotsPerBlock; |
|
} |
|
|
|
public static void Rehash(FastHashMapData* data, int numItems, Allocator label) |
|
{ |
|
int elementCap = data->numElements * 16 / 15; |
|
if (elementCap > numItems) |
|
{ |
|
numItems = elementCap; |
|
} |
|
if (numItems < 16) |
|
{ |
|
numItems = 16; |
|
} |
|
int numSlots = NumSlots(numItems); |
|
if (numSlots == data->numSlotsMinusOne + 1) |
|
{ |
|
return; |
|
} |
|
int numBlocks = (numSlots + kSlotsPerBlock - 1) / kSlotsPerBlock; |
|
int keySize = data->keySize; |
|
int keyValueSize = keySize + data->valueSize; |
|
int blockSize = kSlotsPerBlock + keyValueSize * kSlotsPerBlock; |
|
long numBytes = blockSize * numBlocks + kSlotsPerBlock; |
|
byte* newEntries = (byte*)UnsafeUtility.Malloc(numBytes, 1, label); |
|
byte* newEndItem = newEntries + numBlocks * blockSize; |
|
for (byte* it = newEntries; it <= newEndItem; it += blockSize) |
|
{ |
|
for (int i = 0; i < kSlotsPerBlock; ++i) |
|
{ |
|
it[i] = kEmptySlot; |
|
} |
|
} |
|
byte* oldEntries = data->entries; |
|
int oldNumSlots = data->numSlotsMinusOne + 1; |
|
data->entries = newEntries; |
|
data->numSlotsMinusOne = numSlots - 1; |
|
data->numElements = 0; |
|
if (oldEntries == null) |
|
return; |
|
int oldNumBlocks = (oldNumSlots + kSlotsPerBlock - 1) / kSlotsPerBlock; |
|
for (byte* it = oldEntries, end = oldEntries + oldNumBlocks * blockSize; it != end; it += blockSize) |
|
{ |
|
for (int i = 0; i < kSlotsPerBlock; ++i) |
|
{ |
|
byte metadata = it[i]; |
|
if (metadata != kEmptySlot && metadata != kReservedSlot) |
|
{ |
|
byte* key = it + 8 + i * keyValueSize; |
|
byte* value = key + keySize; |
|
SetInternal(data, key, value, label); |
|
} |
|
} |
|
} |
|
UnsafeUtility.Free(oldEntries, label); |
|
} |
|
|
|
private static byte Normalize(byte c) |
|
{ |
|
if (c >= 'a' && c <= 'z') |
|
{ |
|
return (byte)(c & 0xdf); |
|
} |
|
if (c >= 'A' && c <= '_') |
|
{ |
|
return c; |
|
} |
|
if (c == '/') |
|
{ |
|
return (byte)'\\'; |
|
} |
|
if (c >= '+' && c <= '.') |
|
{ |
|
return c; |
|
} |
|
if (c >= '0' && c <= '9') |
|
{ |
|
return c; |
|
} |
|
if (c >= ' ' && c <= ')') |
|
{ |
|
return c; |
|
} |
|
return 0; |
|
} |
|
|
|
private static bool KeysEqual(FastHashMapData* data, byte* a, byte* b) |
|
{ |
|
if (data->pathNormalizeKeys) |
|
{ |
|
for (int i = 0; i < data->keySize; i++) |
|
{ |
|
if (Normalize(a[i]) != Normalize(b[i])) |
|
return false; |
|
} |
|
return true; |
|
} |
|
return UnsafeUtility.MemCmp(a, b, data->keySize) == 0; |
|
} |
|
|
|
public static int Hash(FastHashMapData* data, byte* key) |
|
{ |
|
int hash = 0; |
|
if (data->pathNormalizeKeys) |
|
{ |
|
byte* keyCopy = stackalloc byte[data->keySize]; |
|
for (int i = 0; i < data->keySize; i++) |
|
{ |
|
keyCopy[i] = Normalize(key[i]); |
|
} |
|
key = keyCopy; |
|
} |
|
if (data->hashKeys) |
|
{ |
|
hash = (int)JenkinsHash.Hash32((uint*)key, data->keySize); |
|
} |
|
else |
|
{ |
|
if (data->keySize >= 4) |
|
{ |
|
hash = *(int*)key; |
|
} |
|
else |
|
{ |
|
int s = data->keySize; |
|
for (int i = 0; i < s; ++i) |
|
{ |
|
hash <<= 8; |
|
hash |= key[i]; |
|
} |
|
} |
|
} |
|
return hash; |
|
} |
|
|
|
public static bool IsFull(FastHashMapData* data) |
|
{ |
|
return (data->numElements * 16 / 15) > data->numSlotsMinusOne + 1; |
|
} |
|
|
|
public static FastHashMapData* Create(int capacity, int keySize, int valueSize, bool hashKeys, bool isMultiMap, Allocator label) |
|
{ |
|
FastHashMapData* data = (FastHashMapData*)UnsafeUtility.Malloc(sizeof(FastHashMapData), 8, label); |
|
UnsafeUtility.MemClear(data, sizeof(FastHashMapData)); |
|
data->keySize = keySize; |
|
data->valueSize = valueSize; |
|
data->hashKeys = hashKeys; |
|
data->isMultiMap = isMultiMap; |
|
Rehash(data, capacity, label); |
|
return data; |
|
} |
|
|
|
public static void Destroy(FastHashMapData* data, Allocator label) |
|
{ |
|
if (data->entries != null) |
|
{ |
|
UnsafeUtility.Free(data->entries, label); |
|
} |
|
UnsafeUtility.Free(data, label); |
|
} |
|
|
|
// SetInternal writes a key-value pair to the map. Returns true when it has overwritten |
|
// an existing value. |
|
public static bool SetInternal(FastHashMapData* data, byte* key, byte* value, Allocator label) |
|
{ |
|
int keySize = data->keySize; |
|
int valueSize = data->valueSize; |
|
int keyValueSize = keySize + valueSize; |
|
int blockSize = kSlotsPerBlock + keyValueSize * kSlotsPerBlock; |
|
int index = Hash(data, key) & data->numSlotsMinusOne; |
|
bool first = true; |
|
while (true) |
|
{ |
|
int blockIndex = index >> 3; |
|
int indexInBlock = index & 7; |
|
byte* block = data->entries + blockIndex * blockSize; |
|
byte* keyPtr = block + kSlotsPerBlock + indexInBlock * keyValueSize; |
|
byte metadata = block[indexInBlock]; |
|
if (first) |
|
{ |
|
// List entry or empty |
|
if ((metadata & 0x80) != 0) |
|
{ |
|
// emplace_direct_hit |
|
if (IsFull(data)) |
|
{ |
|
// Load factor is too high, force growth: |
|
Grow(data, label); |
|
return SetInternal(data, key, value, label); |
|
} |
|
if (metadata == kEmptySlot) |
|
{ |
|
// This is an empty slot, write to it: |
|
block[indexInBlock] = 0; |
|
data->numElements++; |
|
UnsafeUtility.MemCpy(keyPtr, key, keySize); |
|
UnsafeUtility.MemCpy(keyPtr + keySize, value, valueSize); |
|
return false; |
|
} |
|
else |
|
{ |
|
// This slot is a list entry for another key; find its parent |
|
// and find a free slot to rehome it |
|
int parentIndex = FindParentBlock(data, index); |
|
int freeIndex = FindFreeIndex(data, parentIndex, out int freeJumpIndex); |
|
if (freeJumpIndex == 0) |
|
{ |
|
// Couldn't find a free slot, force growth: |
|
Grow(data, label); |
|
return SetInternal(data, key, value, label); |
|
} |
|
int itIndex = index; |
|
while (true) |
|
{ |
|
// Move what's currently at itIndex to freeIndex |
|
int freeBlockIndex = freeIndex >> 3; |
|
int freeIndexInBlock = freeIndex & 7; |
|
byte* freeBlock = data->entries + freeBlockIndex * blockSize; |
|
byte* freeKeyPtr = freeBlock + kSlotsPerBlock + freeIndexInBlock * keyValueSize; |
|
int itBlockIndex = itIndex >> 3; |
|
int itIndexInBlock = itIndex & 7; |
|
byte* itBlock = data->entries + itBlockIndex * blockSize; |
|
byte* itKeyPtr = itBlock + kSlotsPerBlock + itIndexInBlock * keyValueSize; |
|
UnsafeUtility.MemCpy(freeKeyPtr, itKeyPtr, keyValueSize); |
|
// Set parent's metadata to freeJumpIndex |
|
int parentBlockIndex = parentIndex >> 3; |
|
int parentIndexInBlock = parentIndex & 7; |
|
byte* parentBlock = data->entries + parentBlockIndex * blockSize; |
|
parentBlock[parentIndexInBlock] = (byte)((parentBlock[parentIndexInBlock] & 0x80) | freeJumpIndex); |
|
// Mark free's metadata as a list entry |
|
freeBlock[freeIndexInBlock] = kListEntry; |
|
// If itIndex had no next, break |
|
byte itMetadata = itBlock[itIndexInBlock]; |
|
if ((itMetadata & kDistance) == 0) |
|
{ |
|
itBlock[itIndexInBlock] = kEmptySlot; |
|
break; |
|
} |
|
// Follow itIndex so we can make its children members of the new list |
|
int nextIndex = (int)(itIndex + JumpDistance(itMetadata & kDistance)) & data->numSlotsMinusOne; |
|
itBlock[itIndexInBlock] = kEmptySlot; |
|
block[indexInBlock] = kReservedSlot; |
|
itIndex = nextIndex; |
|
parentIndex = freeIndex; |
|
freeIndex = FindFreeIndex(data, parentIndex, out freeJumpIndex); |
|
if (freeJumpIndex == 0) |
|
{ |
|
// Couldn't find a free slot, force growth: |
|
Grow(data, label); |
|
return SetInternal(data, key, value, label); |
|
} |
|
} |
|
// Write to index |
|
block[indexInBlock] = 0; |
|
data->numElements++; |
|
UnsafeUtility.MemCpy(keyPtr, key, keySize); |
|
UnsafeUtility.MemCpy(keyPtr + keySize, value, valueSize); |
|
return false; |
|
} |
|
} |
|
first = false; |
|
} |
|
// If we found key, overwrite the value: |
|
if (!data->isMultiMap && KeysEqual(data, keyPtr, key)) |
|
{ |
|
UnsafeUtility.MemCpy(keyPtr + keySize, value, valueSize); |
|
return true; |
|
} |
|
// If distance is 0, this is the end of a list |
|
// (0x00 would be a direct hit with no children, and 0x80 would be the end of a n-element list) |
|
int distance = metadata & kDistance; |
|
if (distance == 0) |
|
{ |
|
// emplace_new_key |
|
if (IsFull(data)) |
|
{ |
|
// Load factor is too high, force growth: |
|
Grow(data, label); |
|
return SetInternal(data, key, value, label); |
|
} |
|
int freeIndex = FindFreeIndex(data, index, out int freeJumpIndex); |
|
if (freeJumpIndex == 0) |
|
{ |
|
// Couldn't find a free slot, force growth: |
|
Grow(data, label); |
|
return SetInternal(data, key, value, label); |
|
} |
|
// Write to freeIndex |
|
int freeBlockIndex = freeIndex >> 3; |
|
int freeIndexInBlock = freeIndex & 7; |
|
byte* freeBlock = data->entries + freeBlockIndex * blockSize; |
|
byte* freeKeyPtr = freeBlock + kSlotsPerBlock + freeIndexInBlock * keyValueSize; |
|
freeBlock[freeIndexInBlock] = kListEntry; |
|
data->numElements++; |
|
UnsafeUtility.MemCpy(freeKeyPtr, key, keySize); |
|
UnsafeUtility.MemCpy(freeKeyPtr + keySize, value, valueSize); |
|
// Set parent's next: |
|
block[indexInBlock] = (byte)((block[indexInBlock] & 0x80) | freeJumpIndex); |
|
return false; |
|
} |
|
index = (int)(index + JumpDistance(distance)) & data->numSlotsMinusOne; |
|
} |
|
} |
|
|
|
// FindInternal returns a pointer in to the hashmap where the value for key is |
|
// stored, if key is present in the hashmap. |
|
public static byte* FindInternal(FastHashMapData* data, byte* key) |
|
{ |
|
int index = FindIndexInternal(data, key); |
|
if (index < 0) |
|
{ |
|
return null; |
|
} |
|
|
|
int keySize = data->keySize; |
|
int valueSize = data->valueSize; |
|
int keyValueSize = keySize + valueSize; |
|
int blockSize = kSlotsPerBlock + keyValueSize * kSlotsPerBlock; |
|
int blockIndex = index >> 3; |
|
int indexInBlock = index & 7; |
|
byte* block = data->entries + blockIndex * blockSize; |
|
byte* keyPtr = block + kSlotsPerBlock + indexInBlock * keyValueSize; |
|
return keyPtr + keySize; |
|
} |
|
|
|
private static int FindIndexInternal(FastHashMapData* data, byte* key) |
|
{ |
|
int keySize = data->keySize; |
|
int valueSize = data->valueSize; |
|
int keyValueSize = keySize + valueSize; |
|
int blockSize = kSlotsPerBlock + keyValueSize * kSlotsPerBlock; |
|
int index = Hash(data, key) & data->numSlotsMinusOne; |
|
bool first = true; |
|
while (true) |
|
{ |
|
int blockIndex = index >> 3; |
|
int indexInBlock = index & 7; |
|
byte* block = data->entries + blockIndex * blockSize; |
|
byte* keyPtr = block + kSlotsPerBlock + indexInBlock * keyValueSize; |
|
byte metadata = block[indexInBlock]; |
|
if (first) |
|
{ |
|
// If there's no direct hit here, then the key is guaranteed |
|
// to not be in the table: |
|
if ((metadata & 0x80) != 0) |
|
{ |
|
return -1; |
|
} |
|
first = false; |
|
} |
|
// If we found key, return the value: |
|
if (KeysEqual(data, keyPtr, key)) |
|
{ |
|
return index; |
|
} |
|
// If distance is 0, this is the end of a list |
|
// (0x00 would be a direct hit with no children, and 0x80 would be the end of a n-element list) |
|
int distance = metadata & kDistance; |
|
if (distance == 0) |
|
{ |
|
return -1; |
|
} |
|
index = (int)(index + JumpDistance(distance)) & data->numSlotsMinusOne; |
|
} |
|
} |
|
|
|
private static int FindNextIndexInternal(FastHashMapData* data, byte* key, int index) |
|
{ |
|
int keySize = data->keySize; |
|
int valueSize = data->valueSize; |
|
int keyValueSize = keySize + valueSize; |
|
int blockSize = kSlotsPerBlock + keyValueSize * kSlotsPerBlock; |
|
bool first = true; |
|
while (true) |
|
{ |
|
int blockIndex = index >> 3; |
|
int indexInBlock = index & 7; |
|
byte* block = data->entries + blockIndex * blockSize; |
|
byte* keyPtr = block + kSlotsPerBlock + indexInBlock * keyValueSize; |
|
byte metadata = block[indexInBlock]; |
|
// If we found key, return the value: |
|
if (!first && KeysEqual(data, keyPtr, key)) |
|
{ |
|
return index; |
|
} |
|
first = false; |
|
// If distance is 0, this is the end of a list |
|
// (0x00 would be a direct hit with no children, and 0x80 would be the end of a n-element list) |
|
int distance = metadata & kDistance; |
|
if (distance == 0) |
|
{ |
|
return -1; |
|
} |
|
index = (int)(index + JumpDistance(distance)) & data->numSlotsMinusOne; |
|
} |
|
} |
|
|
|
public static bool EraseInternal(FastHashMapData* data, byte* key) |
|
{ |
|
int index = FindIndexInternal(data, key); |
|
if (index < 0) |
|
{ |
|
return false; |
|
} |
|
|
|
int keySize = data->keySize; |
|
int valueSize = data->valueSize; |
|
int keyValueSize = keySize + valueSize; |
|
int blockSize = kSlotsPerBlock + keyValueSize * kSlotsPerBlock; |
|
|
|
int currentIndex = index; |
|
int currentBlockIndex = currentIndex >> 3; |
|
int currentIndexInBlock = currentIndex & 7; |
|
byte* currentBlock = data->entries + currentBlockIndex * blockSize; |
|
byte* currentKeyPtr = currentBlock + kSlotsPerBlock + currentIndexInBlock * keyValueSize; |
|
byte currentMetadata = currentBlock[currentIndexInBlock]; |
|
if ((currentMetadata & 0x7f) != 0) |
|
{ |
|
// Follow list to end to get the last item |
|
int previousIndex = currentIndex; |
|
int nextIndex = (int)(currentIndex + JumpDistance(currentMetadata & kDistance)) & data->numSlotsMinusOne; |
|
int nextBlockIndex = nextIndex >> 3; |
|
int nextIndexInBlock = nextIndex & 7; |
|
byte* nextBlock = data->entries + nextBlockIndex * blockSize; |
|
byte nextMetadata = nextBlock[nextIndexInBlock]; |
|
while ((nextMetadata & 0x7f) != 0) |
|
{ |
|
previousIndex = nextIndex; |
|
nextIndex = (int)(nextIndex + JumpDistance(nextMetadata & kDistance)) & data->numSlotsMinusOne; |
|
nextBlockIndex = nextIndex >> 3; |
|
nextIndexInBlock = nextIndex & 7; |
|
nextBlock = data->entries + nextBlockIndex * blockSize; |
|
nextMetadata = nextBlock[nextIndexInBlock]; |
|
} |
|
// Rehome the last item over current |
|
byte* nextKeyPtr = nextBlock + kSlotsPerBlock + nextIndexInBlock * keyValueSize; |
|
UnsafeUtility.MemCpy(currentKeyPtr, nextKeyPtr, keyValueSize); |
|
nextBlock[nextIndexInBlock] = kEmptySlot; |
|
// Clear the jump distance from previous |
|
int previousBlockIndex = previousIndex >> 3; |
|
int previousIndexInBlock = previousIndex & 7; |
|
byte* previousBlock = data->entries + previousBlockIndex * blockSize; |
|
previousBlock[previousIndexInBlock] &= 0x80; |
|
} |
|
else |
|
{ |
|
// current is at the end of a list or is a direct hit. |
|
// Clear the previous list entry's jump distance if it |
|
// isn't a direct hit: |
|
if ((currentMetadata & 0x80) != 0) |
|
{ |
|
int parentIndex = FindParentBlock(data, currentIndex); |
|
int parentBlockIndex = parentIndex >> 3; |
|
int parentIndexInBlock = parentIndex & 7; |
|
byte* parentBlock = data->entries + parentBlockIndex * blockSize; |
|
parentBlock[parentIndexInBlock] &= 0x80; |
|
} |
|
// clear current: |
|
currentBlock[currentIndexInBlock] = kEmptySlot; |
|
} |
|
data->numElements--; |
|
return true; |
|
} |
|
|
|
public static bool EraseMultiInternal(FastHashMapData* data, byte* key) |
|
{ |
|
if (!EraseInternal(data, key)) return false; |
|
while (EraseInternal(data, key)) ; |
|
return true; |
|
} |
|
|
|
public static void Clear(FastHashMapData* data) |
|
{ |
|
int numBlocks = (data->numSlotsMinusOne + kSlotsPerBlock) / kSlotsPerBlock; |
|
int keySize = data->keySize; |
|
int keyValueSize = keySize + data->valueSize; |
|
int blockSize = kSlotsPerBlock + keyValueSize * kSlotsPerBlock; |
|
long numBytes = blockSize * numBlocks + kSlotsPerBlock; |
|
byte* entries = data->entries; |
|
byte* newEndItem = entries + numBlocks * blockSize; |
|
for (byte* it = entries; it <= newEndItem; it += blockSize) |
|
{ |
|
for (int i = 0; i < kSlotsPerBlock; ++i) |
|
{ |
|
it[i] = kEmptySlot; |
|
} |
|
} |
|
} |
|
|
|
private static void Grow(FastHashMapData* data, Allocator label) |
|
{ |
|
int numItems = 2 * (data->numSlotsMinusOne + 1); |
|
Rehash(data, numItems, label); |
|
} |
|
|
|
public static byte* KeyAt(FastHashMapData* data, int index) |
|
{ |
|
int keySize = data->keySize; |
|
int valueSize = data->valueSize; |
|
int keyValueSize = keySize + valueSize; |
|
int blockSize = kSlotsPerBlock + keyValueSize * kSlotsPerBlock; |
|
int blockIndex = index >> 3; |
|
int indexInBlock = index & 7; |
|
byte* block = data->entries + blockIndex * blockSize; |
|
byte* keyPtr = block + kSlotsPerBlock + indexInBlock * keyValueSize; |
|
return keyPtr; |
|
} |
|
|
|
public static int IterNextIndex(FastHashMapData* data, int index) |
|
{ |
|
if (data->numSlotsMinusOne <= index) |
|
{ |
|
return data->numSlotsMinusOne + 1; |
|
} |
|
int nextIndex = index + 1; |
|
for (; nextIndex <= data->numSlotsMinusOne; nextIndex++) |
|
{ |
|
int keySize = data->keySize; |
|
int valueSize = data->valueSize; |
|
int keyValueSize = keySize + valueSize; |
|
int blockSize = kSlotsPerBlock + keyValueSize * kSlotsPerBlock; |
|
|
|
int nextBlockIndex = nextIndex >> 3; |
|
int nextIndexInBlock = nextIndex & 7; |
|
byte* nextBlock = data->entries + nextBlockIndex * blockSize; |
|
byte* nextKeyPtr = nextBlock + kSlotsPerBlock + nextIndexInBlock * keyValueSize; |
|
byte nextMetadata = nextBlock[nextIndexInBlock]; |
|
if (nextMetadata != kEmptySlot && nextMetadata != kReservedSlot) |
|
{ |
|
return nextIndex; |
|
} |
|
} |
|
return nextIndex; |
|
} |
|
|
|
private static int FindDirectHit(FastHashMapData* data, int child) |
|
{ |
|
byte* key = KeyAt(data, child); |
|
if (data->pathNormalizeKeys) |
|
{ |
|
byte* keyCopy = stackalloc byte[data->keySize]; |
|
for (int i = 0; i < data->keySize; i++) |
|
{ |
|
keyCopy[i] = Normalize(key[i]); |
|
} |
|
key = keyCopy; |
|
} |
|
int hash = Hash(data, key); |
|
int index = hash & data->numSlotsMinusOne; |
|
return index; |
|
} |
|
|
|
private static int JumpNext(FastHashMapData* data, int index) |
|
{ |
|
int keySize = data->keySize; |
|
int valueSize = data->valueSize; |
|
int keyValueSize = keySize + valueSize; |
|
int blockSize = kSlotsPerBlock + keyValueSize * kSlotsPerBlock; |
|
int blockIndex = index >> 3; |
|
int indexInBlock = index & 7; |
|
byte* block = data->entries + blockIndex * blockSize; |
|
return (int)(index + JumpDistance(block[indexInBlock] & kDistance)) & data->numSlotsMinusOne; |
|
} |
|
|
|
private static int FindParentBlock(FastHashMapData* data, int child) |
|
{ |
|
int parentIndex = FindDirectHit(data, child); |
|
while (true) |
|
{ |
|
int next = JumpNext(data, parentIndex); |
|
if (next == child) |
|
{ |
|
return parentIndex; |
|
} |
|
parentIndex = next; |
|
} |
|
} |
|
|
|
private static int FindFreeIndex(FastHashMapData* data, int parent, out int depth) |
|
{ |
|
int keySize = data->keySize; |
|
int valueSize = data->valueSize; |
|
int keyValueSize = keySize + valueSize; |
|
int blockSize = kSlotsPerBlock + keyValueSize * kSlotsPerBlock; |
|
for (int jumpIndex = 1; jumpIndex < kNumJumpDistances; ++jumpIndex) |
|
{ |
|
int index = (int)(parent + JumpDistance(jumpIndex)) & data->numSlotsMinusOne; |
|
int blockIndex = index >> 3; |
|
int indexInBlock = index & 7; |
|
byte* block = data->entries + blockIndex * blockSize; |
|
if (block[indexInBlock] == kEmptySlot) |
|
{ |
|
depth = jumpIndex; |
|
return index; |
|
} |
|
} |
|
depth = 0; |
|
return 0; |
|
} |
|
} |
|
|
|
public unsafe struct FastHashMap : IDisposable, IEnumerable<KeyValuePair<byte[], byte[]>> |
|
{ |
|
private FastHashMapData* m_data; |
|
Allocator m_label; |
|
|
|
public FastHashMap(int capacity, int keySize, int valueSize, bool hashKeys, Allocator label) |
|
{ |
|
m_label = label; |
|
m_data = FastHashMapData.Create(capacity, keySize, valueSize, hashKeys, false, label); |
|
} |
|
|
|
public int Length |
|
{ |
|
get |
|
{ |
|
return m_data->numElements; |
|
} |
|
} |
|
|
|
public int Capacity |
|
{ |
|
get |
|
{ |
|
return m_data->numSlotsMinusOne + 1; |
|
} |
|
} |
|
|
|
public void Clear() |
|
{ |
|
FastHashMapData.Clear(m_data); |
|
} |
|
|
|
public void Add(byte[] key, byte[] value) |
|
{ |
|
if (key.Length < m_data->keySize) |
|
{ |
|
throw new ArgumentException("key is shorter than keySize bytes"); |
|
} |
|
if (value.Length < m_data->valueSize) |
|
{ |
|
throw new ArgumentException("value is shorter than valueSize bytes"); |
|
} |
|
fixed (byte* pKey = key, pValue = value) |
|
{ |
|
byte* existingValue = FastHashMapData.FindInternal(m_data, pKey); |
|
if (existingValue != null) |
|
{ |
|
throw new ArgumentException("An element with the same key already exists."); |
|
} |
|
FastHashMapData.SetInternal(m_data, pKey, pValue, m_label); |
|
} |
|
} |
|
|
|
// Set sets the key-value pair in the HashMap. Set returns true when it has overwritten an existing item. |
|
public bool Set(byte[] keyValue) |
|
{ |
|
if (keyValue.Length < m_data->keySize + m_data->valueSize) |
|
{ |
|
throw new ArgumentException("keyValue is shorter than keyValueSize bytes"); |
|
} |
|
fixed (byte* pKeyValue = keyValue) |
|
{ |
|
return FastHashMapData.SetInternal(m_data, pKeyValue, pKeyValue + m_data->keySize, m_label); |
|
} |
|
} |
|
|
|
// Set sets the key-value pair in the HashMap. Set returns true when it has overwritten an existing item. |
|
public bool Set(byte[] key, byte[] value) |
|
{ |
|
if (key.Length < m_data->keySize) |
|
{ |
|
throw new ArgumentException("key is shorter than keySize bytes"); |
|
} |
|
if (value.Length < m_data->valueSize) |
|
{ |
|
throw new ArgumentException("value is shorter than valueSize bytes"); |
|
} |
|
fixed (byte* pKey = key, pValue = value) |
|
{ |
|
return FastHashMapData.SetInternal(m_data, pKey, pValue, m_label); |
|
} |
|
} |
|
|
|
// Set sets the key-value pair in the HashMap. Set returns true when it has overwritten an existing item. |
|
public unsafe bool Set(byte* key, byte* value) |
|
{ |
|
return FastHashMapData.SetInternal(m_data, key, value, m_label); |
|
} |
|
|
|
public bool ContainsKey(byte[] key) |
|
{ |
|
if (key.Length < m_data->keySize) |
|
{ |
|
throw new ArgumentException("key is shorter than keySize bytes"); |
|
} |
|
fixed (byte* pKey = key) |
|
{ |
|
return FastHashMapData.FindInternal(m_data, pKey) != null; |
|
} |
|
} |
|
|
|
public unsafe bool ContainsKey(byte* key) |
|
{ |
|
return FastHashMapData.FindInternal(m_data, key) != null; |
|
} |
|
|
|
public bool TryGetValue(byte[] key, byte[] value) |
|
{ |
|
if (key.Length < m_data->keySize) |
|
{ |
|
throw new ArgumentException("key is shorter than keySize bytes"); |
|
} |
|
if (value.Length < m_data->valueSize) |
|
{ |
|
throw new ArgumentException("value is shorter than valueSize bytes"); |
|
} |
|
fixed (byte* pKey = key, pValue = value) |
|
{ |
|
return TryGetValue(pKey, pValue); |
|
} |
|
} |
|
|
|
public unsafe bool TryGetValue(byte* key, byte* value) |
|
{ |
|
byte* existingValue = FastHashMapData.FindInternal(m_data, key); |
|
if (existingValue == null) |
|
{ |
|
return false; |
|
} |
|
UnsafeUtility.MemCpy(value, existingValue, m_data->valueSize); |
|
return true; |
|
} |
|
|
|
// Returns a pointer to the value for key, or null if no such value exists. |
|
public unsafe byte* GetValue(byte* key) |
|
{ |
|
return FastHashMapData.FindInternal(m_data, key); |
|
} |
|
|
|
public bool Remove(byte[] key) |
|
{ |
|
if (key.Length < m_data->keySize) |
|
{ |
|
throw new ArgumentException("key is shorter than keySize bytes"); |
|
} |
|
fixed (byte* pKey = key) |
|
{ |
|
return FastHashMapData.EraseInternal(m_data, pKey); |
|
} |
|
} |
|
|
|
public unsafe bool Remove(byte* key) |
|
{ |
|
return FastHashMapData.EraseInternal(m_data, key); |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
if (m_data != null) |
|
{ |
|
FastHashMapData.Destroy(m_data, m_label); |
|
m_data = null; |
|
} |
|
} |
|
|
|
public IEnumerator<KeyValuePair<byte[], byte[]>> GetEnumerator() |
|
{ |
|
return new Enumerator(m_data); |
|
} |
|
|
|
IEnumerator IEnumerable.GetEnumerator() |
|
{ |
|
return new Enumerator(m_data); |
|
} |
|
|
|
internal unsafe class Enumerator : IEnumerator<KeyValuePair<byte[], byte[]>> |
|
{ |
|
FastHashMapData* data; |
|
int index; |
|
byte[] keyData; |
|
byte[] valueData; |
|
|
|
internal Enumerator(FastHashMapData* hashMap) |
|
{ |
|
data = hashMap; |
|
index = FastHashMapData.IterNextIndex(hashMap, -1); |
|
keyData = new byte[hashMap->keySize]; |
|
valueData = new byte[hashMap->valueSize]; |
|
} |
|
|
|
public KeyValuePair<byte[], byte[]> Current |
|
{ |
|
get |
|
{ |
|
byte* key = FastHashMapData.KeyAt(data, index); |
|
byte* value = key + data->keySize; |
|
fixed (byte* pKeyData = keyData, pValueData = valueData) |
|
{ |
|
UnsafeUtility.MemCpy(pKeyData, key, data->keySize); |
|
UnsafeUtility.MemCpy(pValueData, value, data->valueSize); |
|
} |
|
return new KeyValuePair<byte[], byte[]>(keyData, valueData); |
|
} |
|
} |
|
|
|
object IEnumerator.Current |
|
{ |
|
get |
|
{ |
|
return (object)Current; |
|
} |
|
} |
|
|
|
void IEnumerator.Reset() |
|
{ |
|
index = FastHashMapData.IterNextIndex(data, -1); |
|
} |
|
|
|
public bool MoveNext() |
|
{ |
|
index = FastHashMapData.IterNextIndex(data, index); |
|
if (index > data->numSlotsMinusOne) |
|
return false; |
|
return true; |
|
} |
|
|
|
public void Dispose() { } |
|
} |
|
} |
|
} |