hello filters my old friend

pull/6/head
cosmonaut 2023-11-07 17:46:44 -08:00
parent e6059a2f0a
commit c6f8b65b32
13 changed files with 241 additions and 516 deletions

View File

@ -1,38 +0,0 @@
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS;
internal class Archetype
{
public World World;
public ArchetypeSignature Signature;
public NativeArray<Entity> Entities = new NativeArray<Entity>();
public SortedDictionary<TypeId, Archetype> AddEdges =
new SortedDictionary<TypeId, Archetype>();
public SortedDictionary<TypeId, Archetype> RemoveEdges =
new SortedDictionary<TypeId, Archetype>();
public int Count => Entities.Count;
public Archetype(World world, ArchetypeSignature signature)
{
World = world;
Signature = signature;
}
public int Append(Entity entity)
{
Entities.Append(entity);
return Entities.Count - 1;
}
public void ClearAll()
{
for (int i = Entities.Count - 1; i >= 0; i -= 1)
{
World.Destroy(Entities[i]);
}
}
}

View File

@ -1,3 +0,0 @@
namespace MoonTools.ECS;
internal readonly record struct ArchetypeEdge(Archetype Add, Archetype Remove);

View File

@ -1,3 +0,0 @@
namespace MoonTools.ECS;
internal readonly record struct ArchetypeRecord(Archetype Archetype, int Row);

View File

@ -1,89 +0,0 @@
using System;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS;
internal class ArchetypeSignature : IEquatable<ArchetypeSignature>
{
public static ArchetypeSignature Empty = new ArchetypeSignature(0);
IndexableSet<TypeId> Ids;
public int Count => Ids.Count;
public TypeId this[int i] => Ids[i];
public ArchetypeSignature()
{
Ids = new IndexableSet<TypeId>();
}
public ArchetypeSignature(int capacity)
{
Ids = new IndexableSet<TypeId>(capacity);
}
// Maintains sorted order
public void Insert(TypeId componentId)
{
Ids.Add(componentId);
}
public void Remove(TypeId componentId)
{
Ids.Remove(componentId);
}
public bool Contains(TypeId componentId)
{
return Ids.Contains(componentId);
}
public void CopyTo(ArchetypeSignature other)
{
foreach (var id in Ids.AsSpan())
{
other.Ids.Add(id);
}
}
public override bool Equals(object? obj)
{
return obj is ArchetypeSignature signature && Equals(signature);
}
public bool Equals(ArchetypeSignature? other)
{
if (other == null)
{
return false;
}
if (Ids.Count != other.Ids.Count)
{
return false;
}
for (int i = 0; i < Ids.Count; i += 1)
{
if (Ids[i] != other.Ids[i])
{
return false;
}
}
return true;
}
public override int GetHashCode()
{
var hashcode = 1;
foreach (var id in Ids)
{
hashcode = HashCode.Combine(hashcode, id);
}
return hashcode;
}
}

View File

@ -76,22 +76,6 @@ internal unsafe class NativeArray : IDisposable
Count += 1;
}
public void CopyElementToEnd(int index, NativeArray other)
{
if (other.Count >= other.Capacity)
{
other.Resize();
}
NativeMemory.Copy(
(void*) (Elements + (index * ElementSize)),
(void*) (other.Elements + (other.Count * ElementSize)),
(nuint) ElementSize
);
other.Count += 1;
}
public void CopyAllTo(NativeArray other)
{
if (Count >= other.Capacity)

View File

@ -9,13 +9,15 @@ namespace MoonTools.ECS
internal readonly Dictionary<Entity, int> EntityIDToStorageIndex = new Dictionary<Entity, int>(16);
internal readonly NativeArray Components;
internal readonly NativeArray<Entity> EntityIDs;
internal readonly TypeId TypeId;
private bool disposed;
public ComponentStorage(int elementSize)
public ComponentStorage(TypeId typeId, int elementSize)
{
Components = new NativeArray(elementSize);
EntityIDs = new NativeArray<Entity>();
TypeId = typeId;
}
public bool Any()

View File

@ -1,193 +1,77 @@
using System;
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS;
// TODO: do we want to get fancy with queries beyond Include and Exclude?
public class Filter
{
private Archetype EmptyArchetype;
private HashSet<TypeId> Included;
private HashSet<TypeId> Excluded;
private World World;
internal FilterSignature Signature;
public EntityEnumerator Entities => new EntityEnumerator(this);
internal ArchetypeEnumerator Archetypes => new ArchetypeEnumerator(this);
public RandomEntityEnumerator EntitiesInRandomOrder => new RandomEntityEnumerator(this);
internal IndexableSet<Entity> EntitySet = new IndexableSet<Entity>();
public bool Empty
{
get
{
var empty = true;
public ReverseSpanEnumerator<Entity> Entities => EntitySet.GetEnumerator();
foreach (var archetype in Archetypes)
{
if (archetype.Count > 0)
{
return false;
}
}
return empty;
}
}
public int Count
{
get
{
var count = 0;
foreach (var archetype in Archetypes)
{
count += archetype.Count;
}
return count;
}
}
public Entity RandomEntity
{
get
{
var randomIndex = RandomManager.Next(Count);
return NthEntity(randomIndex);
}
}
public bool Empty => EntitySet.Count == 0;
public int Count => EntitySet.Count;
// WARNING: this WILL crash if the index is out of range!
public Entity NthEntity(int index)
{
foreach (var archetype in Archetypes)
{
if (index < archetype.Count)
{
return archetype.Entities[index];
}
public Entity NthEntity(int index) => EntitySet[index];
index -= archetype.Count;
}
// WARNING: this WILL crash if the filter is empty!
public Entity RandomEntity => EntitySet[RandomManager.Next(EntitySet.Count)];
public RandomEntityEnumerator EntitiesInRandomOrder => new RandomEntityEnumerator(this);
throw new InvalidOperationException("Filter index out of range!");
internal Filter(World world, FilterSignature signature)
{
World = world;
Signature = signature;
}
public void DestroyAllEntities()
{
foreach (var archetype in Archetypes)
foreach (var entity in EntitySet)
{
archetype.ClearAll();
World.Destroy(entity);
}
}
internal Filter(Archetype emptyArchetype, HashSet<TypeId> included, HashSet<TypeId> excluded)
internal void Check(Entity entity)
{
EmptyArchetype = emptyArchetype;
Included = included;
Excluded = excluded;
}
internal ref struct ArchetypeEnumerator
foreach (var type in Signature.Included)
{
private Archetype CurrentArchetype;
// TODO: pool these
private Queue<Archetype> ArchetypeQueue = new Queue<Archetype>();
private Queue<Archetype> ArchetypeSearchQueue = new Queue<Archetype>();
private HashSet<Archetype> Explored = new HashSet<Archetype>();
public ArchetypeEnumerator GetEnumerator() => this;
public ArchetypeEnumerator(Filter filter)
if (!World.Has(entity, type))
{
var empty = filter.EmptyArchetype;
ArchetypeSearchQueue.Enqueue(empty);
// TODO: can we cache this search effectively?
while (ArchetypeSearchQueue.TryDequeue(out var current))
{
// exclude the empty archetype
var satisfiesFilter = filter.Included.Count != 0;
foreach (var componentId in filter.Included)
{
if (!current.Signature.Contains(componentId))
{
satisfiesFilter = false;
EntitySet.Remove(entity);
return;
}
}
foreach (var componentId in filter.Excluded)
foreach (var type in Signature.Excluded)
{
if (current.Signature.Contains(componentId))
if (World.Has(entity, type))
{
satisfiesFilter = false;
EntitySet.Remove(entity);
return;
}
}
if (satisfiesFilter)
{
ArchetypeQueue.Enqueue(current);
EntitySet.Add(entity);
}
// breadth-first search
// ignore excluded component edges
foreach (var (componentId, archetype) in current.AddEdges)
internal void AddEntity(in Entity entity)
{
if (!Explored.Contains(archetype) && !filter.Excluded.Contains(componentId))
{
Explored.Add(archetype);
ArchetypeSearchQueue.Enqueue(archetype);
}
EntitySet.Add(entity);
}
foreach (var (componentId, archetype) in current.RemoveEdges)
internal void RemoveEntity(in Entity entity)
{
if (!Explored.Contains(archetype))
EntitySet.Remove(entity);
}
internal void Clear()
{
Explored.Add(archetype);
ArchetypeSearchQueue.Enqueue(archetype);
}
}
}
}
public bool MoveNext()
{
return ArchetypeQueue.TryDequeue(out CurrentArchetype!);
}
public Archetype Current => CurrentArchetype;
}
public ref struct EntityEnumerator
{
private Entity CurrentEntity;
public EntityEnumerator GetEnumerator() => this;
// TODO: pool this
Queue<Entity> EntityQueue = new Queue<Entity>();
internal EntityEnumerator(Filter filter)
{
var archetypeEnumerator = new ArchetypeEnumerator(filter);
foreach (var archetype in archetypeEnumerator)
{
foreach (var entity in archetype.Entities)
{
EntityQueue.Enqueue(entity);
}
}
}
public bool MoveNext()
{
return EntityQueue.TryDequeue(out CurrentEntity);
}
public Entity Current => CurrentEntity;
EntitySet.Clear();
}
public ref struct RandomEntityEnumerator

View File

@ -1,21 +1,22 @@
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
{
public struct FilterBuilder
{
World World;
HashSet<TypeId> Included;
HashSet<TypeId> Excluded;
IndexableSet<TypeId> Included;
IndexableSet<TypeId> Excluded;
internal FilterBuilder(World world)
{
World = world;
Included = new HashSet<TypeId>();
Excluded = new HashSet<TypeId>();
Included = new IndexableSet<TypeId>();
Excluded = new IndexableSet<TypeId>();
}
private FilterBuilder(World world, HashSet<TypeId> included, HashSet<TypeId> excluded)
private FilterBuilder(World world, IndexableSet<TypeId> included, IndexableSet<TypeId> excluded)
{
World = world;
Included = included;
@ -24,19 +25,20 @@ namespace MoonTools.ECS
public FilterBuilder Include<T>() where T : unmanaged
{
Included.Add(World.GetTypeId<T>());
Included.Add(World.GetComponentTypeId<T>());
return new FilterBuilder(World, Included, Excluded);
}
public FilterBuilder Exclude<T>() where T : unmanaged
{
Excluded.Add(World.GetTypeId<T>());
Excluded.Add(World.GetComponentTypeId<T>());
return new FilterBuilder(World, Included, Excluded);
}
public Filter Build()
{
return new Filter(World.EmptyArchetype, Included, Excluded);
var signature = new FilterSignature(Included, Excluded);
return World.GetFilter(signature);
}
}
}

View File

@ -1,15 +1,14 @@
using System;
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
{
public struct FilterSignature : IEquatable<FilterSignature>
{
public readonly IndexableSet<int> Included;
public readonly IndexableSet<int> Excluded;
public readonly IndexableSet<TypeId> Included;
public readonly IndexableSet<TypeId> Excluded;
public FilterSignature(IndexableSet<int> included, IndexableSet<int> excluded)
public FilterSignature(IndexableSet<TypeId> included, IndexableSet<TypeId> excluded)
{
Included = included;
Excluded = excluded;

View File

@ -7,14 +7,17 @@ internal class IdAssigner
uint Next;
NativeArray<uint> AvailableIds = new NativeArray<uint>();
public uint Assign()
public uint Assign(out bool recycled)
{
if (!AvailableIds.TryPop(out var id))
recycled = AvailableIds.TryPop(out var id);
if (recycled)
{
id = Next;
Next += 1;
return id;
}
id = Next;
Next += 1;
return id;
}

View File

@ -9,51 +9,38 @@ public class Snapshot
{
private Dictionary<TypeId, ComponentSnapshot> ComponentSnapshots = new Dictionary<TypeId, ComponentSnapshot>();
private Dictionary<ArchetypeSignature, ArchetypeSnapshot> ArchetypeSnapshots =
new Dictionary<ArchetypeSignature, ArchetypeSnapshot>();
private Dictionary<FilterSignature, List<Entity>> Filters = new Dictionary<FilterSignature, List<Entity>>();
private Dictionary<TypeId, RelationSnapshot> RelationSnapshots =
new Dictionary<TypeId, RelationSnapshot>();
private Dictionary<Entity, ArchetypeRecord> EntityIndex = new Dictionary<Entity, ArchetypeRecord>();
private Dictionary<Entity, IndexableSet<TypeId>> EntityRelationIndex =
new Dictionary<Entity, IndexableSet<TypeId>>();
private Dictionary<Entity, IndexableSet<TypeId>> EntityComponentIndex =
new Dictionary<Entity, IndexableSet<TypeId>>();
private Dictionary<Entity, string> EntityTags = new Dictionary<Entity, string>();
private IdAssigner EntityIdAssigner = new IdAssigner();
public int Count
{
get
{
var count = 0;
foreach (var snapshot in ArchetypeSnapshots.Values)
{
count += snapshot.Count;
}
return count;
}
}
public void Restore(World world)
{
// restore archetype storage
foreach (var (archetypeSignature, archetypeSnapshot) in ArchetypeSnapshots)
{
var archetype = world.ArchetypeIndex[archetypeSignature];
archetypeSnapshot.Restore(archetype);
}
// restore id assigner state
EntityIdAssigner.CopyTo(world.EntityIdAssigner);
// restore entity index
world.EntityIndex.Clear();
foreach (var (id, ArchetypeRecord) in EntityIndex)
// restore filter states
// this could be sped up if we figured out a direct IndexableSet copy
foreach (var (signature, entityList) in Filters)
{
world.EntityIndex[id] = ArchetypeRecord;
var filter = world.FilterIndex[signature];
filter.Clear();
foreach (var entity in entityList)
{
filter.AddEntity(entity);
}
}
// restore components
@ -63,9 +50,6 @@ public class Snapshot
componentSnapshot.Restore(componentStorage);
}
// restore id assigner state
EntityIdAssigner.CopyTo(world.EntityIdAssigner);
// restore relation state
foreach (var (typeId, relationSnapshot) in RelationSnapshots)
{
@ -85,6 +69,19 @@ public class Snapshot
}
}
// restore entity component index state
// FIXME: arrghghhh this is so slow
foreach (var (id, componentTypeSet) in EntityComponentIndex)
{
world.EntityComponentIndex[id].Clear();
foreach (var typeId in componentTypeSet)
{
world.EntityComponentIndex[id].Add(typeId);
}
}
// restore entity tags
foreach (var (id, s) in EntityTags)
{
world.EntityTags[id] = s;
@ -96,17 +93,10 @@ public class Snapshot
// copy id assigner state
world.EntityIdAssigner.CopyTo(EntityIdAssigner);
// copy entity index
EntityIndex.Clear();
foreach (var (id, ArchetypeRecord) in world.EntityIndex)
// copy filter states
foreach (var (_, filter) in world.FilterIndex)
{
EntityIndex[id] = ArchetypeRecord;
}
// copy archetypes
foreach (var archetype in world.ArchetypeIndex.Values)
{
TakeArchetypeSnapshot(archetype);
TakeFilterSnapshot(filter);
}
// copy components
@ -138,21 +128,44 @@ public class Snapshot
}
}
// copy entity component index
// FIXME: arghhhh this is so slow
foreach (var (id, componentTypeSet) in world.EntityComponentIndex)
{
if (!EntityComponentIndex.ContainsKey(id))
{
EntityComponentIndex.Add(id, new IndexableSet<TypeId>());
}
EntityComponentIndex[id].Clear();
foreach (var typeId in componentTypeSet)
{
EntityComponentIndex[id].Add(typeId);
}
}
// copy entity tags
foreach (var (id, s) in world.EntityTags)
{
EntityTags[id] = s;
}
}
private void TakeArchetypeSnapshot(Archetype archetype)
private void TakeFilterSnapshot(Filter filter)
{
if (!ArchetypeSnapshots.TryGetValue(archetype.Signature, out var archetypeSnapshot))
if (!Filters.TryGetValue(filter.Signature, out var entities))
{
archetypeSnapshot = new ArchetypeSnapshot();
ArchetypeSnapshots.Add(archetype.Signature, archetypeSnapshot);
entities = new List<Entity>();
Filters.Add(filter.Signature, entities);
}
archetypeSnapshot.Take(archetype);
entities.Clear();
foreach (var entity in filter.EntitySet.AsSpan())
{
entities.Add(entity);
}
}
private void TakeComponentSnapshot(TypeId typeId, ComponentStorage componentStorage)
@ -177,27 +190,6 @@ public class Snapshot
snapshot.Take(relationStorage);
}
private class ArchetypeSnapshot
{
private readonly NativeArray<Entity> Entities;
public int Count => Entities.Count;
public ArchetypeSnapshot()
{
Entities = new NativeArray<Entity>();
}
public void Take(Archetype archetype)
{
archetype.Entities.CopyTo(Entities);
}
public void Restore(Archetype archetype)
{
Entities.CopyTo(archetype.Entities);
}
}
private class ComponentSnapshot
{
private readonly Dictionary<Entity, int> EntityIDToStorageIndex = new Dictionary<Entity, int>();

View File

@ -5,7 +5,7 @@ using MoonTools.ECS.Collections;
namespace MoonTools.ECS
{
public class World
public class World : IDisposable
{
// Get TypeId from a Type
private readonly Dictionary<Type, TypeId> TypeToId = new Dictionary<Type, TypeId>();
@ -17,10 +17,9 @@ namespace MoonTools.ECS
// Get element size from a TypeId
private readonly Dictionary<TypeId, int> ElementSizes = new Dictionary<TypeId, int>();
// Archetypes
internal readonly Dictionary<ArchetypeSignature, Archetype> ArchetypeIndex = new Dictionary<ArchetypeSignature, Archetype>();
internal readonly Dictionary<Entity, ArchetypeRecord> EntityIndex = new Dictionary<Entity, ArchetypeRecord>();
internal readonly Archetype EmptyArchetype;
// Filters
internal readonly Dictionary<FilterSignature, Filter> FilterIndex = new Dictionary<FilterSignature, Filter>();
private readonly Dictionary<TypeId, List<Filter>> TypeToFilter = new Dictionary<TypeId, List<Filter>>();
// TODO: can we make the tag an native array of chars at some point?
internal Dictionary<Entity, string> EntityTags = new Dictionary<Entity, string>();
@ -35,14 +34,12 @@ namespace MoonTools.ECS
public FilterBuilder FilterBuilder => new FilterBuilder(this);
internal readonly Dictionary<TypeId, ComponentStorage> ComponentIndex = new Dictionary<TypeId, ComponentStorage>();
internal Dictionary<Entity, IndexableSet<TypeId>> EntityComponentIndex = new Dictionary<Entity, IndexableSet<TypeId>>();
internal IdAssigner EntityIdAssigner = new IdAssigner();
private IdAssigner TypeIdAssigner = new IdAssigner();
public World()
{
EmptyArchetype = CreateArchetype(ArchetypeSignature.Empty);
}
private bool IsDisposed;
internal TypeId GetTypeId<T>() where T : unmanaged
{
@ -51,7 +48,7 @@ namespace MoonTools.ECS
return TypeToId[typeof(T)];
}
var typeId = new TypeId(TypeIdAssigner.Assign());
var typeId = new TypeId(TypeIdAssigner.Assign(out var _));
TypeToId.Add(typeof(T), typeId);
ElementSizes.Add(typeId, Unsafe.SizeOf<T>());
@ -62,6 +59,20 @@ namespace MoonTools.ECS
return typeId;
}
internal TypeId GetComponentTypeId<T>() where T : unmanaged
{
var typeId = GetTypeId<T>();
if (ComponentIndex.TryGetValue(typeId, out var componentStorage))
{
return typeId;
}
componentStorage = new ComponentStorage(typeId, ElementSizes[typeId]);
ComponentIndex.Add(typeId, componentStorage);
TypeToFilter.Add(typeId, new List<Filter>());
return typeId;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ComponentStorage GetComponentStorage<T>() where T : unmanaged
{
@ -71,29 +82,44 @@ namespace MoonTools.ECS
return componentStorage;
}
componentStorage = new ComponentStorage(ElementSizes[typeId]);
componentStorage = new ComponentStorage(typeId, ElementSizes[typeId]);
ComponentIndex.Add(typeId, componentStorage);
TypeToFilter.Add(typeId, new List<Filter>());
return componentStorage;
}
private Archetype CreateArchetype(ArchetypeSignature signature)
// FILTERS
internal Filter GetFilter(FilterSignature signature)
{
var archetype = new Archetype(this, signature);
ArchetypeIndex.Add(signature, archetype);
return archetype;
if (!FilterIndex.TryGetValue(signature, out var filter))
{
filter = new Filter(this, signature);
foreach (var typeId in signature.Included)
{
TypeToFilter[typeId].Add(filter);
}
foreach (var typeId in signature.Excluded)
{
TypeToFilter[typeId].Add(filter);
}
}
return filter;
}
// ENTITIES
public Entity CreateEntity(string tag = "")
{
var entity = new Entity(EntityIdAssigner.Assign());
EntityIndex.Add(entity, new ArchetypeRecord(EmptyArchetype, EmptyArchetype.Count));
EmptyArchetype.Append(entity);
var entity = new Entity(EntityIdAssigner.Assign(out var recycled));
if (!EntityRelationIndex.ContainsKey(entity))
if (!recycled)
{
EntityRelationIndex.Add(entity, new IndexableSet<TypeId>());
EntityComponentIndex.Add(entity, new IndexableSet<TypeId>());
}
EntityTags[entity] = tag;
@ -113,15 +139,16 @@ namespace MoonTools.ECS
public void Destroy(in Entity entity)
{
var record = EntityIndex[entity];
var archetype = record.Archetype;
var row = record.Row;
// remove all components from storages
for (int i = 0; i < archetype.Signature.Count; i += 1)
foreach (var componentTypeIndex in EntityComponentIndex[entity])
{
var componentStorage = ComponentIndex[archetype.Signature[i]];
var componentStorage = ComponentIndex[componentTypeIndex];
componentStorage.Remove(entity);
foreach (var filter in TypeToFilter[componentTypeIndex])
{
filter.RemoveEntity(entity);
}
}
// remove all relations from storage
@ -131,18 +158,9 @@ namespace MoonTools.ECS
relationStorage.RemoveEntity(entity);
}
EntityComponentIndex[entity].Clear();
EntityRelationIndex[entity].Clear();
// remove from archetype
if (row != archetype.Count - 1)
{
var lastEntity = archetype.Entities[archetype.Count - 1];
archetype.Entities[row] = lastEntity;
EntityIndex[lastEntity] = new ArchetypeRecord(archetype, row);
}
archetype.Entities.RemoveLastElement();
EntityIndex.Remove(entity);
// recycle ID
EntityIdAssigner.Unassign(entity.ID);
}
@ -155,6 +173,11 @@ namespace MoonTools.ECS
return storage.Has(entity);
}
internal bool Has(in Entity entity, in TypeId typeId)
{
return EntityComponentIndex[entity].Contains(typeId);
}
public bool Some<T>() where T : unmanaged
{
var storage = GetComponentStorage<T>();
@ -185,7 +208,12 @@ namespace MoonTools.ECS
if (!componentStorage.Set(entity, component))
{
TransferArchetype(entity, FindArchetypeByAdd<T>(entity));
EntityComponentIndex[entity].Add(componentStorage.TypeId);
foreach (var filter in TypeToFilter[componentStorage.TypeId])
{
filter.Check(entity);
}
}
}
@ -195,87 +223,13 @@ namespace MoonTools.ECS
if (componentStorage.Remove(entity))
{
TransferArchetype(entity, FindArchetypeByRemove<T>(entity));
}
}
EntityComponentIndex[entity].Remove(componentStorage.TypeId);
private Archetype FindArchetypeByAdd<T>(in Entity entity)
foreach (var filter in TypeToFilter[componentStorage.TypeId])
{
var componentTypeId = TypeToId[typeof(T)];
var record = EntityIndex[entity];
var archetype = record.Archetype;
if (archetype.AddEdges.TryGetValue(componentTypeId, out var nextArchetype))
{
return nextArchetype;
filter.Check(entity);
}
var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1);
archetype.Signature.CopyTo(nextSignature);
nextSignature.Insert(componentTypeId);
if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype))
{
nextArchetype = CreateArchetype(nextSignature);
}
archetype.AddEdges.Add(componentTypeId, nextArchetype);
if (!nextArchetype.RemoveEdges.ContainsKey(componentTypeId))
{
nextArchetype.RemoveEdges.Add(componentTypeId, archetype);
}
return nextArchetype;
}
private Archetype FindArchetypeByRemove<T>(in Entity entity)
{
var componentTypeId = TypeToId[typeof(T)];
var record = EntityIndex[entity];
var archetype = record.Archetype;
if (archetype.RemoveEdges.TryGetValue(componentTypeId, out var nextArchetype))
{
return nextArchetype;
}
var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1);
archetype.Signature.CopyTo(nextSignature);
nextSignature.Remove(componentTypeId);
if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype))
{
nextArchetype = CreateArchetype(nextSignature);
}
archetype.RemoveEdges.Add(componentTypeId, nextArchetype);
if (!nextArchetype.AddEdges.ContainsKey(componentTypeId))
{
nextArchetype.AddEdges.Add(componentTypeId, archetype);
}
return nextArchetype;
}
private void TransferArchetype(in Entity entity, Archetype nextArchetype)
{
var record = EntityIndex[entity];
var archetype = record.Archetype;
var row = record.Row;
// fill the gap
if (row != archetype.Count - 1)
{
var lastEntity = archetype.Entities[archetype.Count - 1];
archetype.Entities[row] = lastEntity;
EntityIndex[lastEntity] = new ArchetypeRecord(archetype, row);
}
archetype.Entities.RemoveLastElement();
nextArchetype.Entities.Append(entity);
EntityIndex[entity] = new ArchetypeRecord(nextArchetype, nextArchetype.Count - 1);
}
// RELATIONS
@ -450,7 +404,7 @@ namespace MoonTools.ECS
#if DEBUG
public ComponentTypeEnumerator Debug_GetAllComponentTypes(Entity entity)
{
return new ComponentTypeEnumerator(this, EntityIndex[entity]);
return new ComponentTypeEnumerator(this, EntityComponentIndex[entity]);
}
public IEnumerable<Entity> Debug_GetEntities(Type componentType)
@ -473,28 +427,66 @@ namespace MoonTools.ECS
public ref struct ComponentTypeEnumerator
{
private World World;
private ArchetypeRecord Record;
private IndexableSet<TypeId> Types;
private int ComponentIndex;
public ComponentTypeEnumerator GetEnumerator() => this;
internal ComponentTypeEnumerator(
World world,
ArchetypeRecord record
IndexableSet<TypeId> types
)
{
World = world;
Record = record;
Types = types;
ComponentIndex = -1;
}
public bool MoveNext()
{
ComponentIndex += 1;
return ComponentIndex < Record.Archetype.Signature.Count;
return ComponentIndex < Types.Count;
}
public unsafe Type Current => World.IdToType[Record.Archetype.Signature[ComponentIndex]];
public unsafe Type Current => World.IdToType[Types[ComponentIndex]];
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
foreach (var componentStorage in ComponentIndex.Values)
{
componentStorage.Dispose();
}
foreach (var relationStorage in RelationIndex.Values)
{
relationStorage.Dispose();
}
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
IsDisposed = true;
}
}
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~World()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endif
}