hello filters my old friend
parent
e6059a2f0a
commit
c6f8b65b32
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
namespace MoonTools.ECS;
|
||||
|
||||
internal readonly record struct ArchetypeEdge(Archetype Add, Archetype Remove);
|
|
@ -1,3 +0,0 @@
|
|||
namespace MoonTools.ECS;
|
||||
|
||||
internal readonly record struct ArchetypeRecord(Archetype Archetype, int Row);
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
200
src/Filter.cs
200
src/Filter.cs
|
@ -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)
|
||||
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)
|
||||
{
|
||||
if (index < archetype.Count)
|
||||
{
|
||||
return archetype.Entities[index];
|
||||
}
|
||||
|
||||
index -= archetype.Count;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Filter index out of range!");
|
||||
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
|
||||
{
|
||||
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)
|
||||
foreach (var type in Signature.Included)
|
||||
{
|
||||
var empty = filter.EmptyArchetype;
|
||||
ArchetypeSearchQueue.Enqueue(empty);
|
||||
|
||||
// TODO: can we cache this search effectively?
|
||||
while (ArchetypeSearchQueue.TryDequeue(out var current))
|
||||
if (!World.Has(entity, type))
|
||||
{
|
||||
// exclude the empty archetype
|
||||
var satisfiesFilter = filter.Included.Count != 0;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
EntitySet.Remove(entity);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
foreach (var type in Signature.Excluded)
|
||||
{
|
||||
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)
|
||||
if (World.Has(entity, type))
|
||||
{
|
||||
foreach (var entity in archetype.Entities)
|
||||
{
|
||||
EntityQueue.Enqueue(entity);
|
||||
}
|
||||
EntitySet.Remove(entity);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
return EntityQueue.TryDequeue(out CurrentEntity);
|
||||
}
|
||||
EntitySet.Add(entity);
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
130
src/Snapshot.cs
130
src/Snapshot.cs
|
@ -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>();
|
||||
|
|
238
src/World.cs
238
src/World.cs
|
@ -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,89 +223,15 @@ namespace MoonTools.ECS
|
|||
|
||||
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
|
||||
|
||||
private RelationStorage RegisterRelationType(TypeId typeId)
|
||||
|
@ -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,29 +427,67 @@ 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]];
|
||||
}
|
||||
#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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue