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; 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) public void CopyAllTo(NativeArray other)
{ {
if (Count >= other.Capacity) 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 Dictionary<Entity, int> EntityIDToStorageIndex = new Dictionary<Entity, int>(16);
internal readonly NativeArray Components; internal readonly NativeArray Components;
internal readonly NativeArray<Entity> EntityIDs; internal readonly NativeArray<Entity> EntityIDs;
internal readonly TypeId TypeId;
private bool disposed; private bool disposed;
public ComponentStorage(int elementSize) public ComponentStorage(TypeId typeId, int elementSize)
{ {
Components = new NativeArray(elementSize); Components = new NativeArray(elementSize);
EntityIDs = new NativeArray<Entity>(); EntityIDs = new NativeArray<Entity>();
TypeId = typeId;
} }
public bool Any() public bool Any()

View File

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

View File

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

View File

@ -7,14 +7,17 @@ internal class IdAssigner
uint Next; uint Next;
NativeArray<uint> AvailableIds = new NativeArray<uint>(); 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; return id;
Next += 1;
} }
id = Next;
Next += 1;
return id; return id;
} }

View File

@ -9,51 +9,38 @@ public class Snapshot
{ {
private Dictionary<TypeId, ComponentSnapshot> ComponentSnapshots = new Dictionary<TypeId, ComponentSnapshot>(); private Dictionary<TypeId, ComponentSnapshot> ComponentSnapshots = new Dictionary<TypeId, ComponentSnapshot>();
private Dictionary<ArchetypeSignature, ArchetypeSnapshot> ArchetypeSnapshots = private Dictionary<FilterSignature, List<Entity>> Filters = new Dictionary<FilterSignature, List<Entity>>();
new Dictionary<ArchetypeSignature, ArchetypeSnapshot>();
private Dictionary<TypeId, RelationSnapshot> RelationSnapshots = private Dictionary<TypeId, RelationSnapshot> RelationSnapshots =
new Dictionary<TypeId, RelationSnapshot>(); new Dictionary<TypeId, RelationSnapshot>();
private Dictionary<Entity, ArchetypeRecord> EntityIndex = new Dictionary<Entity, ArchetypeRecord>();
private Dictionary<Entity, IndexableSet<TypeId>> EntityRelationIndex = private Dictionary<Entity, IndexableSet<TypeId>> EntityRelationIndex =
new Dictionary<Entity, IndexableSet<TypeId>>(); 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 Dictionary<Entity, string> EntityTags = new Dictionary<Entity, string>();
private IdAssigner EntityIdAssigner = new IdAssigner(); 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) public void Restore(World world)
{ {
// restore archetype storage // restore id assigner state
foreach (var (archetypeSignature, archetypeSnapshot) in ArchetypeSnapshots) EntityIdAssigner.CopyTo(world.EntityIdAssigner);
{
var archetype = world.ArchetypeIndex[archetypeSignature];
archetypeSnapshot.Restore(archetype);
}
// restore entity index // restore filter states
world.EntityIndex.Clear(); // this could be sped up if we figured out a direct IndexableSet copy
foreach (var (id, ArchetypeRecord) in EntityIndex) 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 // restore components
@ -63,9 +50,6 @@ public class Snapshot
componentSnapshot.Restore(componentStorage); componentSnapshot.Restore(componentStorage);
} }
// restore id assigner state
EntityIdAssigner.CopyTo(world.EntityIdAssigner);
// restore relation state // restore relation state
foreach (var (typeId, relationSnapshot) in RelationSnapshots) 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) foreach (var (id, s) in EntityTags)
{ {
world.EntityTags[id] = s; world.EntityTags[id] = s;
@ -96,17 +93,10 @@ public class Snapshot
// copy id assigner state // copy id assigner state
world.EntityIdAssigner.CopyTo(EntityIdAssigner); world.EntityIdAssigner.CopyTo(EntityIdAssigner);
// copy entity index // copy filter states
EntityIndex.Clear(); foreach (var (_, filter) in world.FilterIndex)
foreach (var (id, ArchetypeRecord) in world.EntityIndex)
{ {
EntityIndex[id] = ArchetypeRecord; TakeFilterSnapshot(filter);
}
// copy archetypes
foreach (var archetype in world.ArchetypeIndex.Values)
{
TakeArchetypeSnapshot(archetype);
} }
// copy components // 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) foreach (var (id, s) in world.EntityTags)
{ {
EntityTags[id] = s; 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(); entities = new List<Entity>();
ArchetypeSnapshots.Add(archetype.Signature, archetypeSnapshot); 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) private void TakeComponentSnapshot(TypeId typeId, ComponentStorage componentStorage)
@ -177,27 +190,6 @@ public class Snapshot
snapshot.Take(relationStorage); 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 class ComponentSnapshot
{ {
private readonly Dictionary<Entity, int> EntityIDToStorageIndex = new Dictionary<Entity, int>(); private readonly Dictionary<Entity, int> EntityIDToStorageIndex = new Dictionary<Entity, int>();

View File

@ -5,7 +5,7 @@ using MoonTools.ECS.Collections;
namespace MoonTools.ECS namespace MoonTools.ECS
{ {
public class World public class World : IDisposable
{ {
// Get TypeId from a Type // Get TypeId from a Type
private readonly Dictionary<Type, TypeId> TypeToId = new Dictionary<Type, TypeId>(); private readonly Dictionary<Type, TypeId> TypeToId = new Dictionary<Type, TypeId>();
@ -17,10 +17,9 @@ namespace MoonTools.ECS
// Get element size from a TypeId // Get element size from a TypeId
private readonly Dictionary<TypeId, int> ElementSizes = new Dictionary<TypeId, int>(); private readonly Dictionary<TypeId, int> ElementSizes = new Dictionary<TypeId, int>();
// Archetypes // Filters
internal readonly Dictionary<ArchetypeSignature, Archetype> ArchetypeIndex = new Dictionary<ArchetypeSignature, Archetype>(); internal readonly Dictionary<FilterSignature, Filter> FilterIndex = new Dictionary<FilterSignature, Filter>();
internal readonly Dictionary<Entity, ArchetypeRecord> EntityIndex = new Dictionary<Entity, ArchetypeRecord>(); private readonly Dictionary<TypeId, List<Filter>> TypeToFilter = new Dictionary<TypeId, List<Filter>>();
internal readonly Archetype EmptyArchetype;
// TODO: can we make the tag an native array of chars at some point? // TODO: can we make the tag an native array of chars at some point?
internal Dictionary<Entity, string> EntityTags = new Dictionary<Entity, string>(); internal Dictionary<Entity, string> EntityTags = new Dictionary<Entity, string>();
@ -35,14 +34,12 @@ namespace MoonTools.ECS
public FilterBuilder FilterBuilder => new FilterBuilder(this); public FilterBuilder FilterBuilder => new FilterBuilder(this);
internal readonly Dictionary<TypeId, ComponentStorage> ComponentIndex = new Dictionary<TypeId, ComponentStorage>(); 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(); internal IdAssigner EntityIdAssigner = new IdAssigner();
private IdAssigner TypeIdAssigner = new IdAssigner(); private IdAssigner TypeIdAssigner = new IdAssigner();
public World() private bool IsDisposed;
{
EmptyArchetype = CreateArchetype(ArchetypeSignature.Empty);
}
internal TypeId GetTypeId<T>() where T : unmanaged internal TypeId GetTypeId<T>() where T : unmanaged
{ {
@ -51,7 +48,7 @@ namespace MoonTools.ECS
return TypeToId[typeof(T)]; return TypeToId[typeof(T)];
} }
var typeId = new TypeId(TypeIdAssigner.Assign()); var typeId = new TypeId(TypeIdAssigner.Assign(out var _));
TypeToId.Add(typeof(T), typeId); TypeToId.Add(typeof(T), typeId);
ElementSizes.Add(typeId, Unsafe.SizeOf<T>()); ElementSizes.Add(typeId, Unsafe.SizeOf<T>());
@ -62,6 +59,20 @@ namespace MoonTools.ECS
return typeId; 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private ComponentStorage GetComponentStorage<T>() where T : unmanaged private ComponentStorage GetComponentStorage<T>() where T : unmanaged
{ {
@ -71,29 +82,44 @@ namespace MoonTools.ECS
return componentStorage; return componentStorage;
} }
componentStorage = new ComponentStorage(ElementSizes[typeId]); componentStorage = new ComponentStorage(typeId, ElementSizes[typeId]);
ComponentIndex.Add(typeId, componentStorage); ComponentIndex.Add(typeId, componentStorage);
TypeToFilter.Add(typeId, new List<Filter>());
return componentStorage; return componentStorage;
} }
private Archetype CreateArchetype(ArchetypeSignature signature) // FILTERS
internal Filter GetFilter(FilterSignature signature)
{ {
var archetype = new Archetype(this, signature); if (!FilterIndex.TryGetValue(signature, out var filter))
ArchetypeIndex.Add(signature, archetype); {
return archetype; 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 // ENTITIES
public Entity CreateEntity(string tag = "") public Entity CreateEntity(string tag = "")
{ {
var entity = new Entity(EntityIdAssigner.Assign()); var entity = new Entity(EntityIdAssigner.Assign(out var recycled));
EntityIndex.Add(entity, new ArchetypeRecord(EmptyArchetype, EmptyArchetype.Count));
EmptyArchetype.Append(entity);
if (!EntityRelationIndex.ContainsKey(entity)) if (!recycled)
{ {
EntityRelationIndex.Add(entity, new IndexableSet<TypeId>()); EntityRelationIndex.Add(entity, new IndexableSet<TypeId>());
EntityComponentIndex.Add(entity, new IndexableSet<TypeId>());
} }
EntityTags[entity] = tag; EntityTags[entity] = tag;
@ -113,15 +139,16 @@ namespace MoonTools.ECS
public void Destroy(in Entity entity) public void Destroy(in Entity entity)
{ {
var record = EntityIndex[entity];
var archetype = record.Archetype;
var row = record.Row;
// remove all components from storages // 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); componentStorage.Remove(entity);
foreach (var filter in TypeToFilter[componentTypeIndex])
{
filter.RemoveEntity(entity);
}
} }
// remove all relations from storage // remove all relations from storage
@ -131,18 +158,9 @@ namespace MoonTools.ECS
relationStorage.RemoveEntity(entity); relationStorage.RemoveEntity(entity);
} }
EntityComponentIndex[entity].Clear();
EntityRelationIndex[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 // recycle ID
EntityIdAssigner.Unassign(entity.ID); EntityIdAssigner.Unassign(entity.ID);
} }
@ -155,6 +173,11 @@ namespace MoonTools.ECS
return storage.Has(entity); 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 public bool Some<T>() where T : unmanaged
{ {
var storage = GetComponentStorage<T>(); var storage = GetComponentStorage<T>();
@ -185,7 +208,12 @@ namespace MoonTools.ECS
if (!componentStorage.Set(entity, component)) 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,89 +223,15 @@ namespace MoonTools.ECS
if (componentStorage.Remove(entity)) if (componentStorage.Remove(entity))
{ {
TransferArchetype(entity, FindArchetypeByRemove<T>(entity)); EntityComponentIndex[entity].Remove(componentStorage.TypeId);
foreach (var filter in TypeToFilter[componentStorage.TypeId])
{
filter.Check(entity);
}
} }
} }
private Archetype FindArchetypeByAdd<T>(in Entity entity)
{
var componentTypeId = TypeToId[typeof(T)];
var record = EntityIndex[entity];
var archetype = record.Archetype;
if (archetype.AddEdges.TryGetValue(componentTypeId, out var nextArchetype))
{
return nextArchetype;
}
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 // RELATIONS
private RelationStorage RegisterRelationType(TypeId typeId) private RelationStorage RegisterRelationType(TypeId typeId)
@ -450,7 +404,7 @@ namespace MoonTools.ECS
#if DEBUG #if DEBUG
public ComponentTypeEnumerator Debug_GetAllComponentTypes(Entity entity) 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) public IEnumerable<Entity> Debug_GetEntities(Type componentType)
@ -473,29 +427,67 @@ namespace MoonTools.ECS
public ref struct ComponentTypeEnumerator public ref struct ComponentTypeEnumerator
{ {
private World World; private World World;
private ArchetypeRecord Record; private IndexableSet<TypeId> Types;
private int ComponentIndex; private int ComponentIndex;
public ComponentTypeEnumerator GetEnumerator() => this; public ComponentTypeEnumerator GetEnumerator() => this;
internal ComponentTypeEnumerator( internal ComponentTypeEnumerator(
World world, World world,
ArchetypeRecord record IndexableSet<TypeId> types
) )
{ {
World = world; World = world;
Record = record; Types = types;
ComponentIndex = -1; ComponentIndex = -1;
} }
public bool MoveNext() public bool MoveNext()
{ {
ComponentIndex += 1; 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]];
} }
#endif
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
} }
} }