unifying Ids and adding Relations

rev2
cosmonaut 2023-10-26 16:16:28 -07:00
parent 344a0082b4
commit 545637aaf3
10 changed files with 200 additions and 81 deletions

View File

@ -7,11 +7,11 @@ internal class Archetype
public World World; public World World;
public ArchetypeSignature Signature; public ArchetypeSignature Signature;
public List<Column> ComponentColumns = new List<Column>(); public List<Column> ComponentColumns = new List<Column>();
public List<EntityId> RowToEntity = new List<EntityId>(); public List<Id> RowToEntity = new List<Id>();
public Dictionary<ComponentId, int> ComponentToColumnIndex = public Dictionary<Id, int> ComponentToColumnIndex =
new Dictionary<ComponentId, int>(); new Dictionary<Id, int>();
public SortedDictionary<ComponentId, ArchetypeEdge> Edges = new SortedDictionary<ComponentId, ArchetypeEdge>(); public SortedDictionary<Id, ArchetypeEdge> Edges = new SortedDictionary<Id, ArchetypeEdge>();
public int Count => RowToEntity.Count; public int Count => RowToEntity.Count;

View File

@ -7,36 +7,36 @@ namespace MoonTools.ECS.Rev2
{ {
public static ArchetypeSignature Empty = new ArchetypeSignature(0); public static ArchetypeSignature Empty = new ArchetypeSignature(0);
List<int> Ids; List<ulong> Ids;
public int Count => Ids.Count; public int Count => Ids.Count;
public ComponentId this[int i] => new ComponentId(Ids[i]); public Id this[int i] => new Id(Ids[i]);
public ArchetypeSignature() public ArchetypeSignature()
{ {
Ids = new List<int>(); Ids = new List<ulong>();
} }
public ArchetypeSignature(int capacity) public ArchetypeSignature(int capacity)
{ {
Ids = new List<int>(capacity); Ids = new List<ulong>(capacity);
} }
// Maintains sorted order // Maintains sorted order
public void Insert(ComponentId componentId) public void Insert(Id componentId)
{ {
var index = Ids.BinarySearch(componentId.Id); var index = Ids.BinarySearch(componentId.Value);
if (index < 0) if (index < 0)
{ {
Ids.Insert(~index, componentId.Id); Ids.Insert(~index, componentId.Value);
} }
} }
public void Remove(ComponentId componentId) public void Remove(Id componentId)
{ {
var index = Ids.BinarySearch(componentId.Id); var index = Ids.BinarySearch(componentId.Value);
if (index >= 0) if (index >= 0)
{ {

View File

@ -1,12 +0,0 @@
using System;
namespace MoonTools.ECS.Rev2
{
internal readonly record struct ComponentId(int Id) : IHasId, IComparable<ComponentId>
{
public int CompareTo(ComponentId other)
{
return Id.CompareTo(other.Id);
}
}
}

3
src/Rev2/Entity.cs Normal file
View File

@ -0,0 +1,3 @@
namespace MoonTools.ECS.Rev2;
public readonly record struct Entity(uint Id);

View File

@ -1,4 +0,0 @@
namespace MoonTools.ECS.Rev2
{
public readonly record struct EntityId(int Id) : IHasId;
}

View File

@ -7,8 +7,8 @@ namespace MoonTools.ECS.Rev2
public class Filter public class Filter
{ {
private Archetype EmptyArchetype; private Archetype EmptyArchetype;
private HashSet<ComponentId> Included; private HashSet<Id> Included;
private HashSet<ComponentId> Excluded; private HashSet<Id> Excluded;
public EntityEnumerator Entities => new EntityEnumerator(this); public EntityEnumerator Entities => new EntityEnumerator(this);
internal ArchetypeEnumerator Archetypes => new ArchetypeEnumerator(this); internal ArchetypeEnumerator Archetypes => new ArchetypeEnumerator(this);
@ -47,7 +47,7 @@ namespace MoonTools.ECS.Rev2
} }
} }
public EntityId RandomEntity public Id RandomEntity
{ {
get get
{ {
@ -57,7 +57,7 @@ namespace MoonTools.ECS.Rev2
} }
// WARNING: this WILL crash if the index is out of range! // WARNING: this WILL crash if the index is out of range!
public EntityId NthEntity(int index) public Id NthEntity(int index)
{ {
var count = 0; var count = 0;
@ -93,7 +93,7 @@ namespace MoonTools.ECS.Rev2
} }
} }
internal Filter(Archetype emptyArchetype, HashSet<ComponentId> included, HashSet<ComponentId> excluded) internal Filter(Archetype emptyArchetype, HashSet<Id> included, HashSet<Id> excluded)
{ {
EmptyArchetype = emptyArchetype; EmptyArchetype = emptyArchetype;
Included = included; Included = included;
@ -165,12 +165,12 @@ namespace MoonTools.ECS.Rev2
public ref struct EntityEnumerator public ref struct EntityEnumerator
{ {
private EntityId CurrentEntity; private Id CurrentEntity;
public EntityEnumerator GetEnumerator() => this; public EntityEnumerator GetEnumerator() => this;
// TODO: pool this // TODO: pool this
Queue<EntityId> EntityQueue = new Queue<EntityId>(); Queue<Id> EntityQueue = new Queue<Id>();
internal EntityEnumerator(Filter filter) internal EntityEnumerator(Filter filter)
{ {
@ -190,7 +190,7 @@ namespace MoonTools.ECS.Rev2
return EntityQueue.TryDequeue(out CurrentEntity); return EntityQueue.TryDequeue(out CurrentEntity);
} }
public EntityId Current => CurrentEntity; public Id Current => CurrentEntity;
} }
public ref struct RandomEntityEnumerator public ref struct RandomEntityEnumerator
@ -208,7 +208,7 @@ namespace MoonTools.ECS.Rev2
} }
public bool MoveNext() => LinearCongruentialEnumerator.MoveNext(); public bool MoveNext() => LinearCongruentialEnumerator.MoveNext();
public EntityId Current => Filter.NthEntity(LinearCongruentialEnumerator.Current); public Id Current => Filter.NthEntity(LinearCongruentialEnumerator.Current);
} }
} }
} }

View File

@ -5,17 +5,17 @@ namespace MoonTools.ECS.Rev2
public ref struct FilterBuilder public ref struct FilterBuilder
{ {
World World; World World;
HashSet<ComponentId> Included; HashSet<Id> Included;
HashSet<ComponentId> Excluded; HashSet<Id> Excluded;
internal FilterBuilder(World world) internal FilterBuilder(World world)
{ {
World = world; World = world;
Included = new HashSet<ComponentId>(); Included = new HashSet<Id>();
Excluded = new HashSet<ComponentId>(); Excluded = new HashSet<Id>();
} }
private FilterBuilder(World world, HashSet<ComponentId> included, HashSet<ComponentId> excluded) private FilterBuilder(World world, HashSet<Id> included, HashSet<Id> excluded)
{ {
World = world; World = world;
Included = included; Included = included;

30
src/Rev2/Id.cs Normal file
View File

@ -0,0 +1,30 @@
using System;
namespace MoonTools.ECS.Rev2;
public readonly record struct Id : IComparable<Id>
{
public readonly ulong Value;
private const ulong HI = 0xFFFFFFFF00000000;
private const ulong LO = 0xFFFFFFFF;
public bool IsPair => (HI & Value) != 0;
public uint Low => (uint)(LO & Value);
public uint High => (uint)((HI & Value) >>> 32);
public Id(ulong value)
{
Value = value;
}
public Id(uint relation, uint target)
{
Value = (relation << 31) | target;
}
public int CompareTo(Id other)
{
return Value.CompareTo(other.Value);
}
}

View File

@ -2,12 +2,12 @@ using System.Collections.Generic;
namespace MoonTools.ECS.Rev2 namespace MoonTools.ECS.Rev2
{ {
internal class IdAssigner<T> where T : struct, IHasId internal class IdAssigner
{ {
int Next; ulong Next;
Queue<int> AvailableIds = new Queue<int>(); Queue<ulong> AvailableIds = new Queue<ulong>();
public T Assign() public Id Assign()
{ {
if (!AvailableIds.TryDequeue(out var id)) if (!AvailableIds.TryDequeue(out var id))
{ {
@ -15,12 +15,12 @@ namespace MoonTools.ECS.Rev2
Next += 1; Next += 1;
} }
return new T { Id = id }; return new Id(id);
} }
public void Unassign(T idHaver) public void Unassign(Id id)
{ {
AvailableIds.Enqueue(idHaver.Id); AvailableIds.Enqueue(id.Value);
} }
} }
} }

View File

@ -8,22 +8,21 @@ namespace MoonTools.ECS.Rev2
public class World : IDisposable public class World : IDisposable
{ {
// Get ComponentId from a Type // Get ComponentId from a Type
internal static Dictionary<Type, ComponentId> TypeToComponentId = new Dictionary<Type, ComponentId>(); internal static Dictionary<Type, Id> TypeToComponentId = new Dictionary<Type, Id>();
// Get element size from a ComponentId // Get element size from a ComponentId
internal static Dictionary<ComponentId, int> ElementSizes = new Dictionary<ComponentId, int>(); internal static Dictionary<Id, int> ElementSizes = new Dictionary<Id, int>();
// Lookup from ArchetypeSignature to Archetype // Lookup from ArchetypeSignature to Archetype
internal Dictionary<ArchetypeSignature, Archetype> ArchetypeIndex = new Dictionary<ArchetypeSignature, Archetype>(); internal Dictionary<ArchetypeSignature, Archetype> ArchetypeIndex = new Dictionary<ArchetypeSignature, Archetype>();
// Going from EntityId to Archetype and storage row // Going from EntityId to Archetype and storage row
Dictionary<EntityId, Record> EntityIndex = new Dictionary<EntityId, Record>(); Dictionary<Id, Record> EntityIndex = new Dictionary<Id, Record>();
// Going from ComponentId to Archetype list // Going from ComponentId to Archetype list
Dictionary<ComponentId, List<Archetype>> ComponentIndex = new Dictionary<ComponentId, List<Archetype>>(); Dictionary<Id, List<Archetype>> ComponentIndex = new Dictionary<Id, List<Archetype>>();
// ID Management // ID Management
IdAssigner<EntityId> EntityIdAssigner = new IdAssigner<EntityId>(); IdAssigner IdAssigner = new IdAssigner();
IdAssigner<ComponentId> ComponentIdAssigner = new IdAssigner<ComponentId>();
internal readonly Archetype EmptyArchetype; internal readonly Archetype EmptyArchetype;
@ -59,9 +58,9 @@ namespace MoonTools.ECS.Rev2
return archetype; return archetype;
} }
public EntityId CreateEntity() public Id CreateEntity()
{ {
var entityId = EntityIdAssigner.Assign(); var entityId = IdAssigner.Assign();
EntityIndex.Add(entityId, new Record(EmptyArchetype, EmptyArchetype.Count)); EntityIndex.Add(entityId, new Record(EmptyArchetype, EmptyArchetype.Count));
EmptyArchetype.RowToEntity.Add(entityId); EmptyArchetype.RowToEntity.Add(entityId);
return entityId; return entityId;
@ -70,27 +69,41 @@ namespace MoonTools.ECS.Rev2
// used as a fast path by snapshot restore // used as a fast path by snapshot restore
internal void CreateEntityOnArchetype(Archetype archetype) internal void CreateEntityOnArchetype(Archetype archetype)
{ {
var entityId = EntityIdAssigner.Assign(); var entityId = IdAssigner.Assign();
EntityIndex.Add(entityId, new Record(archetype, archetype.Count)); EntityIndex.Add(entityId, new Record(archetype, archetype.Count));
archetype.RowToEntity.Add(entityId); archetype.RowToEntity.Add(entityId);
} }
// used as a fast path by Archetype.ClearAll and snapshot restore // used as a fast path by Archetype.ClearAll and snapshot restore
internal void FreeEntity(EntityId entityId) internal void FreeEntity(Id entityId)
{ {
EntityIndex.Remove(entityId); EntityIndex.Remove(entityId);
EntityIdAssigner.Unassign(entityId); IdAssigner.Unassign(entityId);
}
private void RegisterTypeId(Id typeId, int elementSize)
{
ComponentIndex.Add(typeId, new List<Archetype>());
ElementSizes.Add(typeId, elementSize);
} }
// FIXME: would be much more efficient to do all this at load time somehow // FIXME: would be much more efficient to do all this at load time somehow
private void RegisterComponent<T>() where T : unmanaged private void RegisterComponent<T>() where T : unmanaged
{ {
var componentId = ComponentIdAssigner.Assign(); var componentId = IdAssigner.Assign();
TypeToComponentId.Add(typeof(T), componentId); TypeToComponentId.Add(typeof(T), componentId);
ComponentIndex.Add(componentId, new List<Archetype>()); ComponentIndex.Add(componentId, new List<Archetype>());
ElementSizes.Add(componentId, Unsafe.SizeOf<T>()); ElementSizes.Add(componentId, Unsafe.SizeOf<T>());
} }
private void TryRegisterTypeId(Id typeId, int elementSize)
{
if (!ComponentIndex.ContainsKey(typeId))
{
RegisterTypeId(typeId, elementSize);
}
}
private void TryRegisterComponentId<T>() where T : unmanaged private void TryRegisterComponentId<T>() where T : unmanaged
{ {
if (!TypeToComponentId.ContainsKey(typeof(T))) if (!TypeToComponentId.ContainsKey(typeof(T)))
@ -99,22 +112,13 @@ namespace MoonTools.ECS.Rev2
} }
} }
// non-generic variant for use with Transfer
internal void AddComponentIndexEntry(ComponentId componentId)
{
if (!ComponentIndex.ContainsKey(componentId))
{
ComponentIndex.Add(componentId, new List<Archetype>());
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private ComponentId GetComponentId<T>() where T : unmanaged private Id GetComponentId<T>() where T : unmanaged
{ {
return TypeToComponentId[typeof(T)]; return TypeToComponentId[typeof(T)];
} }
public bool Has<T>(EntityId entityId) where T : unmanaged public bool Has<T>(Id entityId) where T : unmanaged
{ {
var componentId = GetComponentId<T>(); var componentId = GetComponentId<T>();
var record = EntityIndex[entityId]; var record = EntityIndex[entityId];
@ -122,7 +126,7 @@ namespace MoonTools.ECS.Rev2
} }
// will throw if non-existent // will throw if non-existent
public unsafe ref T Get<T>(EntityId entityId) where T : unmanaged public unsafe ref T Get<T>(Id entityId) where T : unmanaged
{ {
var componentId = GetComponentId<T>(); var componentId = GetComponentId<T>();
@ -133,7 +137,7 @@ namespace MoonTools.ECS.Rev2
return ref ((T*) column.Elements)[record.Row]; return ref ((T*) column.Elements)[record.Row];
} }
public unsafe void Set<T>(in EntityId entityId, in T component) where T : unmanaged public unsafe void Set<T>(in Id entityId, in T component) where T : unmanaged
{ {
TryRegisterComponentId<T>(); TryRegisterComponentId<T>();
var componentId = GetComponentId<T>(); var componentId = GetComponentId<T>();
@ -152,7 +156,7 @@ namespace MoonTools.ECS.Rev2
} }
} }
private void Add<T>(EntityId entityId, in T component) where T : unmanaged private void Add<T>(Id entityId, in T component) where T : unmanaged
{ {
Archetype? nextArchetype; Archetype? nextArchetype;
@ -191,7 +195,7 @@ namespace MoonTools.ECS.Rev2
column.Append(component); column.Append(component);
} }
public void Remove<T>(EntityId entityId) where T : unmanaged public void Remove<T>(Id entityId) where T : unmanaged
{ {
Archetype? nextArchetype; Archetype? nextArchetype;
@ -223,7 +227,105 @@ namespace MoonTools.ECS.Rev2
MoveEntityToLowerArchetype(entityId, row, archetype, nextArchetype, componentId); MoveEntityToLowerArchetype(entityId, row, archetype, nextArchetype, componentId);
} }
public void Destroy(EntityId entityId) private Id Pair(in Id relation, in Id target)
{
return new Id(relation.Low, target.Low);
}
public void Relate<T>(in Id entityA, in Id entityB, in T relation) where T : unmanaged
{
TryRegisterComponentId<T>();
var relationDataTypeId = GetComponentId<T>();
var typeId = Pair(relationDataTypeId, entityB);
TryRegisterTypeId(typeId, Unsafe.SizeOf<T>());
SetRelationData(entityA, typeId, relation);
}
public bool Related<T>(in Id entityA, in Id entityB) where T : unmanaged
{
var relationDataTypeId = GetComponentId<T>();
var typeId = Pair(relationDataTypeId, entityB);
return Has(entityA, typeId);
}
public T GetRelationData<T>(in Id entityA, in Id entityB) where T : unmanaged
{
var relationDataTypeId = GetComponentId<T>();
var typeId = Pair(relationDataTypeId, entityB);
return Get<T>(entityA, typeId);
}
private unsafe ref T Get<T>(Id entityId, Id typeId) where T : unmanaged
{
var record = EntityIndex[entityId];
var columnIndex = record.Archetype.ComponentToColumnIndex[typeId];
var column = record.Archetype.ComponentColumns[columnIndex];
return ref ((T*) column.Elements)[record.Row];
}
private bool Has(Id entityId, Id typeId)
{
var record = EntityIndex[entityId];
return record.Archetype.ComponentToColumnIndex.ContainsKey(typeId);
}
private void Add<T>(Id entityId, Id typeId, in T component) where T : unmanaged
{
Archetype? nextArchetype;
// move the entity to the new archetype
var record = EntityIndex[entityId];
var archetype = record.Archetype;
if (archetype.Edges.TryGetValue(typeId, out var edge))
{
nextArchetype = edge.Add;
}
else
{
// FIXME: pool the signatures
var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1);
archetype.Signature.CopyTo(nextSignature);
nextSignature.Insert(typeId);
if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype))
{
nextArchetype = CreateArchetype(nextSignature);
}
var newEdge = new ArchetypeEdge(nextArchetype, archetype);
archetype.Edges.Add(typeId, newEdge);
nextArchetype.Edges.Add(typeId, newEdge);
}
MoveEntityToHigherArchetype(entityId, record.Row, archetype, nextArchetype);
// add the new component to the new archetype
var columnIndex = nextArchetype.ComponentToColumnIndex[typeId];
var column = nextArchetype.ComponentColumns[columnIndex];
column.Append(component);
}
private unsafe void SetRelationData<T>(in Id entityId, in Id typeId, T data) where T : unmanaged
{
if (Has(entityId, typeId))
{
var record = EntityIndex[entityId];
var columnIndex = record.Archetype.ComponentToColumnIndex[typeId];
var column = record.Archetype.ComponentColumns[columnIndex];
((T*) column.Elements)[record.Row] = data;
}
else
{
Add(entityId, typeId, data);
}
}
public void Destroy(Id entityId)
{ {
var record = EntityIndex[entityId]; var record = EntityIndex[entityId];
var archetype = record.Archetype; var archetype = record.Archetype;
@ -244,10 +346,10 @@ namespace MoonTools.ECS.Rev2
archetype.RowToEntity.RemoveAt(archetype.Count - 1); archetype.RowToEntity.RemoveAt(archetype.Count - 1);
EntityIndex.Remove(entityId); EntityIndex.Remove(entityId);
EntityIdAssigner.Unassign(entityId); IdAssigner.Unassign(entityId);
} }
private void MoveEntityToHigherArchetype(EntityId entityId, int row, Archetype from, Archetype to) private void MoveEntityToHigherArchetype(Id entityId, int row, Archetype from, Archetype to)
{ {
for (int i = 0; i < from.ComponentColumns.Count; i += 1) for (int i = 0; i < from.ComponentColumns.Count; i += 1)
{ {
@ -276,7 +378,7 @@ namespace MoonTools.ECS.Rev2
to.RowToEntity.Add(entityId); to.RowToEntity.Add(entityId);
} }
private void MoveEntityToLowerArchetype(EntityId entityId, int row, Archetype from, Archetype to, ComponentId removed) private void MoveEntityToLowerArchetype(Id entityId, int row, Archetype from, Archetype to, Id removed)
{ {
for (int i = 0; i < from.ComponentColumns.Count; i += 1) for (int i = 0; i < from.ComponentColumns.Count; i += 1)
{ {