Skip to content

Instantly share code, notes, and snippets.

@copygirl
Last active January 25, 2019 10:28
Show Gist options
  • Select an option

  • Save copygirl/a010aada7d7131aee7b5bd83140ab514 to your computer and use it in GitHub Desktop.

Select an option

Save copygirl/a010aada7d7131aee7b5bd83140ab514 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
namespace ProperEngine.ECS.Collections
{
public class ComponentMap<TEntity, TComponent>
where TEntity : struct, IEntity
where TComponent : struct, IComponent
{
private readonly RefDictionary<TEntity, TComponent> _dict
= new RefDictionary<TEntity, TComponent>();
public ref TComponent TryGetRef(TEntity entity, out bool success)
=> ref _dict.TryGetEntry(GetBehavior.Default, entity, out success);
public ref TComponent GetOrCreateRef(TEntity entity)
=> ref _dict.TryGetEntry(GetBehavior.Create, entity, out var _);
// Apparently this works, though?
private static bool _;
public ref TComponent GetOrCreateRef2(TEntity entity)
=> ref _dict.TryGetEntry(GetBehavior.Create, entity, out _);
}
}
src/ProperEngine.ES/Collections/ES.cs(21,61): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference [/home/copygirl/projects/ProperEngine/ProperEngine/ProperEngine.csproj]
src/ProperEngine.ES/Collections/ES.cs(21,11): error CS8347: Cannot use a result of 'RefDictionary<TEntity, TComponent>.TryGetEntry(GetBehavior, TEntity, out bool)' in this context because it may expose variables referenced by parameter 'found' outside of their declaration scope [/home/copygirl/projects/ProperEngine/ProperEngine/ProperEngine.csproj]
using System;
using System.Collections.Generic;
namespace ProperEngine.ECS.Collections
{
internal class RefDictionary<TKey, TValue>
{
private readonly object _syncRoot = new object();
private readonly IEqualityComparer<TKey> _comparer;
private int[] _buckets;
private Entry[] _entries;
private int _count;
private int _version;
private int _freeEntry;
private int _freeCount;
public int Count => (_count - _freeCount);
public int Capacity {
get => _entries.Length;
set => Resize(value);
}
public RefDictionary()
: this(0, EqualityComparer<TKey>.Default) { }
public RefDictionary(int capacity)
: this(capacity, EqualityComparer<TKey>.Default) { }
public RefDictionary(IEqualityComparer<TKey> comparer)
: this(0, comparer) { }
public RefDictionary(int capacity, IEqualityComparer<TKey> comparer)
{
if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity));
if (comparer == null) throw new ArgumentNullException(nameof(comparer));
if (capacity > 0) Initialize(capacity);
_comparer = comparer;
}
private void Initialize(int capacity) {
int size = HashHelpers.GetPrime(capacity);
_buckets = new int[size];
_entries = new Entry[size];
Array.Fill(_buckets, -1);
_freeEntry = -1;
}
// public void Clear()
// {
// if (_count == 0) return;
// Array.Fill(_buckets, -1);
// Array.Clear(_entries, 0, _count);
// _count = 0;
// _freeList = -1;
// _freeCount = 0;
// _version++;
// }
private void Resize()
=> Resize(HashHelpers.ExpandPrime(_count));
private void Resize(int newSize)
{
if (newSize < _entries.Length) throw new ArgumentOutOfRangeException(nameof(newSize));
var newBuckets = new int[newSize];
var newEntries = new Entry[newSize];
Array.Fill(newBuckets, -1);
Array.Copy(_entries, 0, newEntries, 0, _count);
for (int i = 0; i < _count; i++) {
ref var entry = ref newEntries[i];
if (entry.HashCode < 0) continue;
var bucket = (newEntries[i].HashCode % newSize);
entry.Next = newBuckets[bucket];
newBuckets[bucket] = i;
}
_buckets = newBuckets;
_entries = newEntries;
}
public ref TValue TryGetEntry(
GetBehavior behavior, TKey key, out bool found)
{
found = false;
if (_buckets == null) {
if (behavior != GetBehavior.Create)
return ref EMPTY_COMPONENT;
Initialize(0);
}
var hashCode = _comparer.GetHashCode(key) & 0x7FFFFFFF;
var targetBucket = hashCode & _buckets.Length;
var last = -1;
for (var i = _buckets[targetBucket]; i >= 0; last = i) {
ref var entry = ref _entries[i];
if ((entry.HashCode == hashCode) && _comparer.Equals(entry.Key, key)) {
found = true;
if (behavior == GetBehavior.Remove) {
_buckets[(last >= 0) ? last : targetBucket] = entry.Next;
entry.HashCode = -1;
entry.Next = _freeEntry;
entry.Key = default(TKey);
// Not resetting allows us to return previous value.
// entry.Value = default(TComponent);
_freeEntry = i;
_freeCount++;
_version++;
}
return ref entry.Value;
}
i = entry.Next;
}
if (behavior != GetBehavior.Create)
return ref EMPTY_COMPONENT;
int index;
if (_freeCount > 0) {
index = _freeEntry;
_freeEntry = _entries[index].Next;
_freeCount--;
} else {
if (_count == _entries.Length) {
Resize();
targetBucket = hashCode % _buckets.Length;
}
index = _count;
_count++;
}
{
ref var entry = ref _entries[index];
entry.HashCode = hashCode;
entry.Next = _buckets[targetBucket];
entry.Key = key;
entry.Value = default(TValue);
_version++;
return ref entry.Value;
}
}
private static TValue EMPTY_COMPONENT
= default(TValue);
private struct Entry
{
public int HashCode;
public int Next;
public TKey Key;
public TValue Value;
}
}
internal enum GetBehavior
{
Default,
Create,
Remove,
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment