I wrote a BlobHashMap half years ago. Using similar extension API like allocating blobArray, can be easily convert from/to C# dictionary. Also can store in byte[ ], so it’s a good way to implementing Monobehavior dictionary serialization.
Core part:
public struct BlobHashMap<TKey, TValue> where TKey : struct, IEquatable<TKey> where TValue : struct
{
[StructLayout(LayoutKind.Sequential)]
public struct Entry
{
public int next;
public TKey key;
public TValue value;
}
internal BlobArray<int> buckets;
internal BlobArray<Entry> data;
internal int halfPV;
public int Count
{
get { return data.Length; }
}
public ref TValue Get(in TKey _key)
{
if (buckets.Length == -1) throw new Exception();
if (halfPV <= 0 || Count <= 5)
{
for (int i = 0, c = Count; i < c; ++i)
{
ref var entry = ref data[i];
if (entry.key.Equals(_key))
{
return ref entry.value;
}
}
}
else
{
var bucketIndex = _key.GetHashCode() % halfPV;
bucketIndex = buckets[bucketIndex + halfPV];
while (bucketIndex != -1)
{
ref var entry = ref data[bucketIndex];
ref var key = ref entry.key;
if (key.Equals(_key))
{
return ref entry.value;
}
bucketIndex = entry.next;
}
}
throw new KeyNotFoundException();
}
public ref TValue Get(in string _key)
{
if (halfPV <= 0 || Count <= 5)
{
for (int i = 0, c = Count; i < c; ++i)
{
ref var entry = ref data[i];
if (entry.key.Equals(_key))
{
return ref entry.value;
}
}
}
else
{
var bucketIndex = _key.GetHashCode() % halfPV;
bucketIndex = buckets[bucketIndex + halfPV];
while (bucketIndex != -1)
{
ref var entry = ref data[bucketIndex];
if (entry.key.Equals(_key))
{
return ref entry.value;
}
bucketIndex = entry.next;
}
}
throw new KeyNotFoundException();
}
public bool ContainsKey(in TKey _key)
{
if (halfPV <= 0 || Count <= 5)
{
for (int i = 0, c = Count; i < c; ++i)
{
ref var entry = ref data[i];
if (entry.key.Equals(_key))
{
return true;
}
}
}
else
{
var bucketIndex = _key.GetHashCode() % halfPV;
bucketIndex = buckets[bucketIndex + halfPV];
while (bucketIndex != -1)
{
ref var entry = ref data[bucketIndex];
if (entry.key.Equals(_key))
{
return true;
}
bucketIndex = entry.next;
}
}
return false;
}
public bool ContainsKey(in string _key)
{
if (halfPV <= 0 || Count <= 5)
{
for (int i = 0, c = Count; i < c; ++i)
{
ref var entry = ref data[i];
if (entry.key.Equals(_key))
{
return true;
}
}
}
else
{
var bucketIndex = _key.GetHashCode() % halfPV;
bucketIndex = buckets[bucketIndex + halfPV];
while (bucketIndex != -1)
{
ref var entry = ref data[bucketIndex];
if (entry.key.Equals(_key))
{
return true;
}
bucketIndex = entry.next;
}
}
return false;
}
public bool TryGetValue(in TKey _key, out TValue _value)
{
if (halfPV <= 0 || Count <= 5)
{
for (int i = 0, c = Count; i < c; ++i)
{
ref var entry = ref data[i];
if (entry.key.Equals(_key))
{
_value = entry.value;
return true;
}
}
}
else
{
var bucketIndex = buckets[_key.GetHashCode() % halfPV + halfPV];
while (bucketIndex != -1)
{
ref var entry = ref data[bucketIndex];
if (entry.key.Equals(_key))
{
_value = entry.value;
return true;
}
bucketIndex = entry.next;
}
}
_value = default;
return false;
}
public bool TryGetValue(in string _key, out TValue _value)
{
if (halfPV <= 0 || Count <= 5)
{
for (int i = 0, c = Count; i < c; ++i)
{
ref var entry = ref data[i];
if (entry.key.Equals(_key))
{
_value = entry.value;
return true;
}
}
}
else
{
var bucketIndex = buckets[_key.GetHashCode() % halfPV + halfPV];
while (bucketIndex != -1)
{
ref var entry = ref data[bucketIndex];
if (entry.key.Equals(_key))
{
_value = entry.value;
return true;
}
bucketIndex = entry.next;
}
}
_value = default;
return false;
}
public void ForEach(BlobHashMapForEachCallback<TKey, TValue> _callback)
{
for (int i = 0, count = data.Length; i < count; ++i)
{
ref var entry = ref data[i];
_callback.Invoke(ref entry.key, ref entry.value);
}
}
public NativeArray<TKey> GetKeys(Allocator _allocator = Allocator.Temp)
{
var length = data.Length;
var array = new NativeArray<TKey>(length, _allocator, NativeArrayOptions.UninitializedMemory);
for (var i = 0; i < length; ++i)
{
ref var entry = ref data[i];
array[i] = entry.key;
}
return array;
}
}
Creation Part
public static class BlobHashMapExtensions
{
private static bool IsPrime(int n)
{
if (n < 2) return false;
for (var i = n - 1; i > 1; i--)
{
if (n % i == 0)
return false;
}
return true;
}
private static void InternalAllocateHashMap<TKey, TValue, TDataKey, TDataValue>(ref this BlobBuilder builder,
ref BlobHashMap<TKey, TValue> hashMap, Dictionary<TDataKey, TDataValue> value,
BlobElementConverter<TKey, TValue, TDataKey, TDataValue> converter)
where TKey : struct, IEquatable<TKey> where TValue : struct
{
var count = value.Count;
if (count == 0) return;
var pV = 2;
if (count > 2)
{
for (int i = count - 1; i >= 0; --i)
{
if (IsPrime(i))
{
pV = i;
break;
}
}
}
var bucketArray = builder.Allocate(ref hashMap.buckets, pV);
var dataArray = builder.Allocate(ref hashMap.data, count);
var halfPv = pV / 2;
hashMap.halfPV = halfPv;
for (int i = 0; i < pV; ++i)
{
bucketArray[i] = -1;
}
var entryIndex = 0;
foreach (var pair in value)
{
var bucketIndex = pair.Key.GetHashCode() % halfPv + halfPv;
dataArray[entryIndex] = new BlobHashMap<TKey, TValue>.Entry
{
next = -1
};
ref var a = ref dataArray[entryIndex];
converter(ref builder, ref a, pair);
if (bucketArray[bucketIndex] == -1)
{
bucketArray[bucketIndex] = entryIndex;
}
else
{
var tempIndex = bucketArray[bucketIndex];
while (dataArray[tempIndex].next != -1)
{
tempIndex = dataArray[tempIndex].next;
}
ref var v = ref dataArray[tempIndex];
v.next = entryIndex;
}
++entryIndex;
}
}
public static void DebugHashMap<TKey, TValue>(ref this BlobHashMap<TKey, TValue> _map)
where TKey : struct, IEquatable<TKey> where TValue : struct
{
_map.ForEach(((ref TKey _key, ref TValue _value) =>
{
UnityEngine.Debug.Log($"Key = {_key}, Value = {_value.ToString()}");
}));
}
public static void AllocateHashMap<TKey, TValue>(ref this BlobBuilder builder,
ref BlobHashMap<TKey, TValue> hashMap, Dictionary<TKey, TValue> value)
where TKey : struct, IEquatable<TKey> where TValue : struct
{
InternalAllocateHashMap(ref builder, ref hashMap, value,
(ref BlobBuilder _builder, ref BlobHashMap<TKey, TValue>.Entry _entry,
in KeyValuePair<TKey, TValue> _value) =>
{
_entry.key = _value.Key;
_entry.value = _value.Value;
});
}
public static void AllocateHashMap<TKey, TValue>(ref this BlobBuilder builder,
ref BlobHashMap<TKey, BlobArray<TValue>> hashMap, Dictionary<TKey, List<TValue>> value)
where TKey : struct, IEquatable<TKey> where TValue : struct
{
InternalAllocateHashMap(ref builder, ref hashMap, value,
(ref BlobBuilder _builder, ref BlobHashMap<TKey, BlobArray<TValue>>.Entry _entry,
in KeyValuePair<TKey, List<TValue>> _value) =>
{
_entry.key = _value.Key;
if (_value.Value != null && _value.Value.Count > 0)
{
_builder.AllocateArray(ref _entry.value, _value.Value);
}
});
}
public static void AllocateHashMap(ref this BlobBuilder builder,
ref BlobHashMap<BlobString, BlobString> hashMap, Dictionary<string, string> value)
{
InternalAllocateHashMap(ref builder, ref hashMap, value,
((ref BlobBuilder _builder, ref BlobHashMap<BlobString, BlobString>.Entry _entry,
in KeyValuePair<string, string> _value) =>
{
_builder.AllocateString(ref _entry.key, _value.Key);
_builder.AllocateString(ref _entry.value, _value.Value);
}));
}
public static void AllocateHashMap(ref this BlobBuilder builder,
ref BlobHashMap<BlobString, BlobArray<BlobString>> hashMap, Dictionary<string, List<string>> value)
{
InternalAllocateHashMap(ref builder, ref hashMap, value,
((ref BlobBuilder _builder, ref BlobHashMap<BlobString, BlobArray<BlobString>>.Entry _entry,
in KeyValuePair<string, List<string>> _value) =>
{
_builder.AllocateString(ref _entry.key, _value.Key);
var list = _value.Value;
if (list != null && list.Count > 0)
{
var array = _builder.Allocate(ref _entry.value, list.Count);
for (int i = 0; i < list.Count; ++i)
{
_builder.AllocateString(ref array[i], list[i]);
}
}
}));
}
public static void AllocateHashMap<TKey>(ref this BlobBuilder builder,
ref BlobHashMap<TKey, BlobString> hashMap, Dictionary<TKey, string> value)
where TKey : struct, IEquatable<TKey>
{
InternalAllocateHashMap(ref builder, ref hashMap, value,
((ref BlobBuilder _builder, ref BlobHashMap<TKey, BlobString>.Entry _entry,
in KeyValuePair<TKey, string> _value) =>
{
_entry.key = _value.Key;
_builder.AllocateString(ref _entry.value, _value.Value);
}));
}
public static void AllocateHashMap<TKey>(ref this BlobBuilder builder,
ref BlobHashMap<TKey, BlobArray<BlobString>> hashMap, Dictionary<TKey, List<string>> value)
where TKey : struct, IEquatable<TKey>
{
InternalAllocateHashMap(ref builder, ref hashMap, value,
((ref BlobBuilder _builder, ref BlobHashMap<TKey, BlobArray<BlobString>>.Entry _entry,
in KeyValuePair<TKey, List<string>> _value) =>
{
_entry.key = _value.Key;
var list = _value.Value;
if (list != null && list.Count > 0)
{
var array = _builder.Allocate(ref _entry.value, list.Count);
for (int i = 0; i < list.Count; ++i)
{
_builder.AllocateString(ref array[i], list[i]);
}
}
}));
}
public static void AllocateHashMap<TValue>(ref this BlobBuilder builder,
ref BlobHashMap<BlobString, TValue> hashMap, Dictionary<string, TValue> value)
where TValue : struct
{
InternalAllocateHashMap(ref builder, ref hashMap, value,
((ref BlobBuilder _builder, ref BlobHashMap<BlobString, TValue>.Entry _entry,
in KeyValuePair<string, TValue> _value) =>
{
_builder.AllocateString(ref _entry.key, _value.Key);
_entry.value = _value.Value;
}));
}
public static void AllocateHashMap<TValue>(ref this BlobBuilder builder,
ref BlobHashMap<BlobString, BlobArray<TValue>> hashMap, Dictionary<string, List<TValue>> value)
where TValue : struct
{
InternalAllocateHashMap(ref builder, ref hashMap, value,
((ref BlobBuilder _builder, ref BlobHashMap<BlobString,BlobArray<TValue>>.Entry _entry,
in KeyValuePair<string, List<TValue>> _value) =>
{
_builder.AllocateString(ref _entry.key, _value.Key);
if (_value.Value != null && _value.Value.Count > 0)
{
_builder.AllocateArray(ref _entry.value, _value.Value);
}
}));
}
public static void AllocateHashMap<TKey, TValue, TValueProxy>(ref this BlobBuilder builder,
ref BlobHashMap<TKey, TValue> hashMap, Dictionary<TKey, TValueProxy> value)
where TKey : struct, IEquatable<TKey>
where TValue : struct
where TValueProxy : IBlobProxy<TValue>
{
InternalAllocateHashMap(ref builder, ref hashMap, value,
((ref BlobBuilder _builder, ref BlobHashMap<TKey, TValue>.Entry _entry,
in KeyValuePair<TKey, TValueProxy> _value) =>
{
_entry.key = _value.Key;
_value.Value.Build(ref _builder, ref _entry.value);
}));
}
public static void AllocateHashMap<TKey, TValue, TValueProxy>(ref this BlobBuilder builder,
ref BlobHashMap<TKey, BlobArray<TValue>> hashMap, Dictionary<TKey, List<TValueProxy>> value)
where TKey : struct, IEquatable<TKey>
where TValue : struct
where TValueProxy : IBlobProxy<TValue>
{
InternalAllocateHashMap(ref builder, ref hashMap, value,
((ref BlobBuilder _builder, ref BlobHashMap<TKey, BlobArray<TValue>>.Entry _entry,
in KeyValuePair<TKey, List<TValueProxy>> _value) =>
{
_entry.key = _value.Key;
var list = _value.Value;
if (list != null && list.Count > 0)
{
var array = _builder.Allocate(ref _entry.value, list.Count);
for (int i = 0; i < list.Count; ++i)
{
list[i].Build(ref _builder, ref array[i]);
}
}
}));
}
public static void AllocateHashMap<TKey, TValue, TKeyProxy>(ref this BlobBuilder builder,
ref BlobHashMap<TKey, TValue> hashMap, Dictionary<TKeyProxy, TValue> value)
where TKey : struct, IEquatable<TKey>
where TValue : struct
where TKeyProxy : IBlobProxy<TKey>
{
InternalAllocateHashMap(ref builder, ref hashMap, value,
((ref BlobBuilder _builder, ref BlobHashMap<TKey, TValue>.Entry _entry,
in KeyValuePair<TKeyProxy, TValue> _value) =>
{
_value.Key.Build(ref _builder, ref _entry.key);
_entry.value = _value.Value;
}));
}
public static void AllocateHashMap<TKey, TValue, TKeyProxy>(ref this BlobBuilder builder,
ref BlobHashMap<TKey, BlobArray<TValue>> hashMap, Dictionary<TKeyProxy, List<TValue>> value)
where TKey : struct, IEquatable<TKey>
where TValue : struct
where TKeyProxy : IBlobProxy<TKey>
{
InternalAllocateHashMap(ref builder, ref hashMap, value,
((ref BlobBuilder _builder, ref BlobHashMap<TKey, BlobArray<TValue>>.Entry _entry,
in KeyValuePair<TKeyProxy, List<TValue>> _value) =>
{
_value.Key.Build(ref _builder, ref _entry.key);
var list = _value.Value;
if (list != null && list.Count > 0)
{
_builder.AllocateArray(ref _entry.value, list);
}
}));
}
public static void AllocateHashMap<TKey, TValue, TKeyProxy, TValueProxy>(ref this BlobBuilder builder,
ref BlobHashMap<TKey, TValue> hashMap, Dictionary<TKeyProxy, TValueProxy> value)
where TKey : struct, IEquatable<TKey>
where TValue : struct
where TKeyProxy : IBlobProxy<TKey>
where TValueProxy : IBlobProxy<TValue>
{
InternalAllocateHashMap(ref builder, ref hashMap, value,
((ref BlobBuilder _builder, ref BlobHashMap<TKey, TValue>.Entry _entry,
in KeyValuePair<TKeyProxy, TValueProxy> _value) =>
{
_value.Key.Build(ref _builder, ref _entry.key);
_value.Value.Build(ref _builder, ref _entry.value);
}));
}
public static void AllocateHashMap<TKey, TValue, TKeyProxy, TValueProxy>(ref this BlobBuilder builder,
ref BlobHashMap<TKey, BlobArray<TValue>> hashMap, Dictionary<TKeyProxy, List<TValueProxy>> value)
where TKey : struct, IEquatable<TKey>
where TValue : struct
where TKeyProxy : IBlobProxy<TKey>
where TValueProxy : IBlobProxy<TValue>
{
InternalAllocateHashMap(ref builder, ref hashMap, value,
((ref BlobBuilder _builder, ref BlobHashMap<TKey, BlobArray<TValue>>.Entry _entry,
in KeyValuePair<TKeyProxy, List<TValueProxy>> _value) =>
{
_value.Key.Build(ref _builder, ref _entry.key);
var list = _value.Value;
if (list != null && list.Count > 0)
{
var array = _builder.Allocate(ref _entry.value, list.Count);
for (int i = 0; i < list.Count; ++i)
{
list[i].Build(ref _builder, ref array[i]);
}
}
}));
}
public static void ConvertToDictionary<TKey, TValue>(ref this BlobHashMap<TKey, TValue> hashMap,
out Dictionary<TKey, TValue> result)
where TKey : struct, IEquatable<TKey> where TValue : struct
{
var temp = new Dictionary<TKey, TValue>();
hashMap.ForEach(((ref TKey _key, ref TValue _value) => { temp.Add(_key, _value); }));
result = temp;
}
public static void ConvertToDictionaryProxyKey<TKey, TValue, TKeyProxy>(
ref this BlobHashMap<TKey, TValue> hashMap,
out Dictionary<TKeyProxy, TValue> result)
where TKey : struct, IEquatable<TKey>
where TValue : struct
where TKeyProxy : IBlobAutoProxy<TKey>, new()
{
var temp = new Dictionary<TKeyProxy, TValue>();
hashMap.ForEach(((ref TKey _key, ref TValue _value) =>
{
var k = new TKeyProxy();
k.Revert(ref _key);
temp.Add(k, _value);
}));
result = temp;
}
public static void ConvertToDictionaryProxyValue<TKey, TValue, TValueProxy>(
ref this BlobHashMap<TKey, TValue> hashMap,
out Dictionary<TKey, TValueProxy> result)
where TKey : struct, IEquatable<TKey>
where TValue : struct
where TValueProxy : IBlobAutoProxy<TValue>, new()
{
var temp = new Dictionary<TKey, TValueProxy>();
hashMap.ForEach(((ref TKey _key, ref TValue _value) =>
{
var v = new TValueProxy();
v.Revert(ref _value);
temp.Add(_key, v);
}));
result = temp;
}
public static void ConvertToDictionaryProxyKeyValue<TKey, TValue, TKeyProxy, TValueProxy>(
ref this BlobHashMap<TKey, TValue> hashMap,
out Dictionary<TKeyProxy, TValueProxy> result)
where TKey : struct, IEquatable<TKey>
where TValue : struct
where TKeyProxy : IBlobAutoProxy<TKey>, new()
where TValueProxy : IBlobAutoProxy<TValue>, new()
{
var temp = new Dictionary<TKeyProxy, TValueProxy>();
hashMap.ForEach(((ref TKey _key, ref TValue _value) =>
{
var k = new TKeyProxy();
k.Revert(ref _key);
var v = new TValueProxy();
v.Revert(ref _value);
temp.Add(k, v);
}));
result = temp;
}
public static void ConvertToDictionary<TKey>(ref this BlobHashMap<TKey, BlobString> hashMap,
out Dictionary<TKey, string> result)
where TKey : struct, IEquatable<TKey>
{
var temp = new Dictionary<TKey, string>();
hashMap.ForEach(((ref TKey _key, ref BlobString _value) => { temp.Add(_key, _value.ToString()); }));
result = temp;
}
public static void ConvertToDictionary<TValue>(ref this BlobHashMap<BlobString, TValue> hashMap,
out Dictionary<string, TValue> result)
where TValue : struct
{
var temp = new Dictionary<string, TValue>();
hashMap.ForEach(((ref BlobString _key, ref TValue _value) => { temp.Add(_key.ToString(), _value); }));
result = temp;
}
public static void ConvertToDictionary(ref this BlobHashMap<BlobString, BlobString> hashMap,
out Dictionary<string, string> result)
{
var temp = new Dictionary<string, string>();
hashMap.ForEach(((ref BlobString _key, ref BlobString _value) =>
{
temp.Add(_key.ToString(), _value.ToString());
}));
result = temp;
}
}
Example :
var data = new Dictionary<TDataKey, TDataValue>();
for (var i = 0; i < _randomDataCount; ++i)
{
var pair = _randomData();
data[pair.Key] = pair.Value;
}
var builder = new BlobBuilder(Allocator.Temp);
try
{
ref var root = ref builder.ConstructRoot<BlobHashMap<TKey, TValue>>();
builder.AllocateHashMap(ref root, data);
var result = builder.CreateBlobAssetReference<BlobHashMap<TKey, TValue>>(Allocator.Temp);
MemoryBinaryReader reader;
MemoryBinaryWriter writer;
unsafe
{
writer = new MemoryBinaryWriter();
writer.Write(result);
reader = new MemoryBinaryReader(writer.Data);
}
try
{
_assertion(reader, data);
}
finally
{
writer.Dispose();
reader.Dispose();
}
}
finally
{
builder.Dispose();
}