initial snapshot system

filter_relations
cosmonaut 2022-12-06 01:59:22 -08:00
parent f045335881
commit 8061590195
17 changed files with 153 additions and 311 deletions

View File

@ -87,5 +87,28 @@ namespace MoonTools.ECS
{
Lookup<TComponent>().Remove(entityID);
}
public void Clear()
{
for (var i = 0; i < ComponentTypeIndices.Count; i += 1)
{
if (storages[i] != null)
{
storages[i].Clear();
}
}
}
// used to fill snapshot depot with correct storages
public void FillMissingStorages(ComponentDepot other)
{
for (var i = 0; i < ComponentTypeIndices.Count; i += 1)
{
if (storages[i] == null && other.storages[i] != null)
{
storages[i] = other.storages[i].CreateStorage();
}
}
}
}
}

View File

@ -8,12 +8,12 @@ namespace MoonTools.ECS
{
internal abstract void Set(int entityID, object component);
public abstract bool Remove(int entityID);
public abstract ComponentStorageState CreateState();
public abstract void Save(ComponentStorageState state);
public abstract void Load(ComponentStorageState state);
public abstract void Clear();
// used for debugging and template instantiation
internal abstract object UntypedGet(int entityID);
// used to create correctly typed storage on snapshot
public abstract ComponentStorage CreateStorage();
}
internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : unmanaged
@ -101,7 +101,7 @@ namespace MoonTools.ECS
return false;
}
public void Clear()
public override void Clear()
{
nextID = 0;
entityIDToStorageIndex.Clear();
@ -123,43 +123,9 @@ namespace MoonTools.ECS
return new Entity(entityIDs[0]);
}
public override ComponentStorageState CreateState()
public override ComponentStorage<TComponent> CreateStorage()
{
return ComponentStorageState.Create<TComponent>(nextID);
}
public override void Save(ComponentStorageState state)
{
ReadOnlySpan<byte> entityIDBytes = MemoryMarshal.Cast<int, byte>(new ReadOnlySpan<int>(entityIDs, 0, nextID));
if (entityIDBytes.Length > state.EntityIDs.Length)
{
Array.Resize(ref state.EntityIDs, entityIDBytes.Length);
}
entityIDBytes.CopyTo(state.EntityIDs);
ReadOnlySpan<byte> componentBytes = MemoryMarshal.Cast<TComponent, byte>(AllComponents());
if (componentBytes.Length > state.Components.Length)
{
Array.Resize(ref state.Components, componentBytes.Length);
}
componentBytes.CopyTo(state.Components);
state.Count = nextID;
}
public override void Load(ComponentStorageState state)
{
state.EntityIDs.CopyTo(MemoryMarshal.Cast<int, byte>(entityIDs));
state.Components.CopyTo(MemoryMarshal.Cast<TComponent, byte>(components));
entityIDToStorageIndex.Clear();
for (var i = 0; i < state.Count; i += 1)
{
entityIDToStorageIndex[entityIDs[i]] = i;
}
nextID = state.Count;
return new ComponentStorage<TComponent>();
}
}
}

View File

@ -52,11 +52,6 @@ namespace MoonTools.ECS
return ComponentDepot.GetSingletonEntity<TComponent>();
}
protected bool Exists(in Entity entity)
{
return EntityStorage.Exists(entity);
}
protected IEnumerable<(Entity, Entity, TRelationKind)> Relations<TRelationKind>() where TRelationKind : unmanaged
{
return RelationDepot.Relations<TRelationKind>();

View File

@ -7,11 +7,14 @@ namespace MoonTools.ECS
private int nextID = 0;
// FIXME: why is this duplicated?
private readonly Stack<int> availableIDs = new Stack<int>();
// FIXME: this is only needed in debug mode
private readonly HashSet<int> availableIDHash = new HashSet<int>();
private Dictionary<int, HashSet<int>> EntityToComponentTypeIndices = new Dictionary<int, HashSet<int>>();
private Dictionary<int, HashSet<int>> EntityToRelationTypeIndices = new Dictionary<int, HashSet<int>>();
public int Count => nextID - availableIDs.Count;
public Entity Create()
{
var entity = new Entity(NextID());
@ -70,6 +73,21 @@ namespace MoonTools.ECS
return EntityToRelationTypeIndices[entityID];
}
public void Clear()
{
nextID = 0;
foreach (var componentSet in EntityToComponentTypeIndices.Values)
{
componentSet.Clear();
}
foreach (var relationSet in EntityToRelationTypeIndices.Values)
{
relationSet.Clear();
}
availableIDs.Clear();
availableIDHash.Clear();
}
private int NextID()
{
if (availableIDs.Count > 0)

View File

@ -84,32 +84,5 @@ namespace MoonTools.ECS
{
Count = 0;
}
public void Save(IndexableSetState<T> state)
{
ReadOnlySpan<byte> arrayBytes = MemoryMarshal.Cast<T, byte>(array);
if (arrayBytes.Length > state.Array.Length)
{
Array.Resize(ref state.Array, arrayBytes.Length);
}
arrayBytes.CopyTo(state.Array);
state.Count = Count;
}
public void Load(IndexableSetState<T> state)
{
state.Array.CopyTo(MemoryMarshal.Cast<T, byte>(array));
indices.Clear();
for (var i = 0; i < state.Count; i += 1)
{
indices[array[i]] = i;
}
Count = state.Count;
}
}
}

View File

@ -6,9 +6,6 @@ namespace MoonTools.ECS
{
internal abstract class RelationStorage
{
public abstract RelationStorageState CreateState();
public abstract void Save(RelationStorageState state);
public abstract void Load(RelationStorageState state);
public abstract void UnrelateAll(int entityID);
}
@ -228,60 +225,5 @@ namespace MoonTools.ECS
hashSet.Clear();
listPool.Push(hashSet);
}
public override RelationStorageState CreateState()
{
return RelationStorageState.Create<TRelation>(count);
}
public override void Save(RelationStorageState state)
{
ReadOnlySpan<byte> relationBytes = MemoryMarshal.Cast<Relation, byte>(relations);
if (relationBytes.Length > state.Relations.Length)
{
Array.Resize(ref state.Relations, relationBytes.Length);
}
relationBytes.CopyTo(state.Relations);
ReadOnlySpan<byte> relationDataBytes = MemoryMarshal.Cast<TRelation, byte>(relationDatas);
if (relationDataBytes.Length > state.RelationDatas.Length)
{
Array.Resize(ref state.RelationDatas, relationDataBytes.Length);
}
relationDataBytes.CopyTo(state.RelationDatas);
state.Count = count;
}
public override void Load(RelationStorageState state)
{
state.Relations.CopyTo(MemoryMarshal.Cast<Relation, byte>(relations));
state.RelationDatas.CopyTo(MemoryMarshal.Cast<TRelation, byte>(relationDatas));
indices.Clear();
outRelations.Clear();
inRelations.Clear();
for (var i = 0; i < state.Count; i += 1)
{
var relation = relations[i];
indices[relation] = i;
if (!outRelations.ContainsKey(relation.A.ID))
{
outRelations[relation.A.ID] = AcquireHashSetFromPool();
}
outRelations[relation.A.ID].Add(relation.B.ID);
if (!inRelations.ContainsKey(relation.B.ID))
{
inRelations[relation.B.ID] = AcquireHashSetFromPool();
}
inRelations[relation.B.ID].Add(relation.A.ID);
}
count = state.Count;
}
}
}

66
src/Snapshot.cs Normal file
View File

@ -0,0 +1,66 @@
namespace MoonTools.ECS
{
public class Snapshot
{
private World World;
private Filter? Filter;
private EntityStorage SnapshotEntityStorage;
private ComponentDepot SnapshotComponentDepot;
internal Snapshot(World world)
{
World = world;
SnapshotEntityStorage = new EntityStorage();
SnapshotComponentDepot = new ComponentDepot(World.ComponentTypeIndices);
}
public void Take(Filter filter)
{
Clear();
Filter = filter;
SnapshotComponentDepot.FillMissingStorages(World.ComponentDepot);
foreach (var worldEntity in filter.Entities)
{
var snapshotEntity = SnapshotEntityStorage.Create();
foreach (var componentTypeIndex in World.EntityStorage.ComponentTypeIndices(worldEntity.ID))
{
SnapshotEntityStorage.SetComponent(snapshotEntity.ID, componentTypeIndex);
SnapshotComponentDepot.Set(snapshotEntity.ID, componentTypeIndex, World.ComponentDepot.UntypedGet(worldEntity.ID, componentTypeIndex));
}
}
}
public void Restore()
{
if (Filter == null)
{
return;
}
foreach (var entity in Filter.Entities)
{
World.Destroy(entity);
}
for (var i = 0; i < SnapshotEntityStorage.Count; i += 1)
{
var entity = World.CreateEntity();
foreach (var componentTypeIndex in SnapshotEntityStorage.ComponentTypeIndices(i))
{
World.EntityStorage.SetComponent(entity.ID, componentTypeIndex);
World.FilterStorage.Check(entity.ID, componentTypeIndex);
World.ComponentDepot.Set(entity.ID, componentTypeIndex, SnapshotComponentDepot.UntypedGet(i, componentTypeIndex));
}
}
}
private void Clear()
{
SnapshotEntityStorage.Clear();
SnapshotComponentDepot.Clear();
}
}
}

View File

@ -1,11 +0,0 @@
using System;
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class ComponentDepotState
{
public Dictionary<Type, ComponentStorageState> StorageStates = new Dictionary<Type, ComponentStorageState>();
public Dictionary<FilterSignature, IndexableSetState<int>> FilterStates = new Dictionary<FilterSignature, IndexableSetState<int>>();
}
}

View File

@ -1,27 +0,0 @@
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class ComponentStorageState
{
public int Count;
public byte[] EntityIDs;
public byte[] Components;
public unsafe static ComponentStorageState Create<TComponent>(int count) where TComponent : unmanaged
{
return new ComponentStorageState(
count,
count * sizeof(int),
count * sizeof(TComponent)
);
}
private ComponentStorageState(int count, int entityIDSize, int componentSize)
{
Count = count;
EntityIDs = new byte[entityIDSize];
Components = new byte[componentSize];
}
}
}

View File

@ -1,13 +0,0 @@
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class EntityStorageState
{
public int NextID;
public List<int> availableIDs = new List<int>();
public Dictionary<int, HashSet<int>> EntityToComponentTypeIndices = new Dictionary<int, HashSet<int>>();
public Dictionary<int, HashSet<int>> EntityToRelationTypeIndices = new Dictionary<int, HashSet<int>>();
}
}

View File

@ -1,16 +0,0 @@
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class IndexableSetState<T> where T : unmanaged
{
public int Count;
public byte[] Array;
public unsafe IndexableSetState(int count)
{
Count = count;
Array = new byte[sizeof(T) * count];
}
}
}

View File

@ -1,10 +0,0 @@
using System;
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class RelationDepotState
{
public Dictionary<Type, RelationStorageState> StorageStates = new Dictionary<Type, RelationStorageState>();
}
}

View File

@ -1,27 +0,0 @@
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class RelationStorageState
{
public int Count;
public byte[] Relations;
public byte[] RelationDatas;
public unsafe static RelationStorageState Create<TRelation>(int count) where TRelation : unmanaged
{
return new RelationStorageState(
count,
count * sizeof(Relation),
count * sizeof(TRelation)
);
}
private RelationStorageState(int count, int relationSize, int relationDataSize)
{
Count = count;
Relations = new byte[relationSize];
RelationDatas = new byte[relationDataSize];
}
}
}

View File

@ -1,16 +0,0 @@
namespace MoonTools.ECS
{
public class WorldState
{
internal readonly ComponentDepotState ComponentDepotState;
internal readonly EntityStorageState EntityStorageState;
internal readonly RelationDepotState RelationDepotState;
public WorldState()
{
ComponentDepotState = new ComponentDepotState();
EntityStorageState = new EntityStorageState();
RelationDepotState = new RelationDepotState();
}
}
}

View File

@ -11,27 +11,9 @@ namespace MoonTools.ECS
public abstract void Update(TimeSpan delta);
protected Entity CreateEntity()
{
return EntityStorage.Create();
}
protected Entity CreateEntity() => World.CreateEntity();
protected void Set<TComponent>(in Entity entity, in TComponent component) where TComponent : unmanaged
{
#if DEBUG
// check for use after destroy
if (!Exists(entity))
{
throw new ArgumentException("This entity is not valid!");
}
#endif
if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex<TComponent>()))
{
FilterStorage.Check<TComponent>(entity.ID);
}
ComponentDepot.Set<TComponent>(entity.ID, component);
}
protected void Set<TComponent>(in Entity entity, in TComponent component) where TComponent : unmanaged => World.Set<TComponent>(entity, component);
protected void Remove<TComponent>(in Entity entity) where TComponent : unmanaged
{
@ -42,27 +24,9 @@ namespace MoonTools.ECS
}
}
protected void Set<TComponent>(in Template template, in TComponent component) where TComponent : unmanaged
{
var componentTypeIndex = ComponentTypeIndices.GetIndex<TComponent>();
TemplateStorage.SetComponent(template.ID, componentTypeIndex);
TemplateComponentDepot.Set(template.ID, component);
ComponentDepot.Register<TComponent>(componentTypeIndex);
}
protected void Set<TComponent>(in Template template, in TComponent component) where TComponent : unmanaged => World.Set<TComponent>(template, component);
protected Entity Instantiate(in Template template)
{
var entity = EntityStorage.Create();
foreach (var componentTypeIndex in TemplateStorage.ComponentTypeIndices(template.ID))
{
EntityStorage.SetComponent(entity.ID, componentTypeIndex);
FilterStorage.Check(entity.ID, componentTypeIndex);
ComponentDepot.Set(entity.ID, componentTypeIndex, TemplateComponentDepot.UntypedGet(template.ID, componentTypeIndex));
}
return entity;
}
protected Entity Instantiate(in Template template) => World.Instantiate(template);
protected ReadOnlySpan<TMessage> ReadMessages<TMessage>() where TMessage : unmanaged
{
@ -128,20 +92,6 @@ namespace MoonTools.ECS
EntityStorage.RemoveRelation(entity.ID, RelationTypeIndices.GetIndex<TRelationKind>());
}
protected void Destroy(in Entity entity)
{
foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID))
{
ComponentDepot.Remove(entity.ID, componentTypeIndex);
FilterStorage.RemoveEntity(entity.ID, componentTypeIndex);
}
foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID))
{
RelationDepot.UnrelateAll(entity.ID, relationTypeIndex);
}
EntityStorage.Destroy(entity);
}
protected void Destroy(in Entity entity) => World.Destroy(entity);
}
}

View File

@ -7,6 +7,7 @@ namespace MoonTools.ECS
{
Dictionary<Type, int> TypeToIndex = new Dictionary<Type, int>();
int nextID = 0;
public int Count => TypeToIndex.Count;
public int GetIndex<T>() where T : unmanaged
{
@ -24,6 +25,7 @@ namespace MoonTools.ECS
return TypeToIndex[type];
}
#if DEBUG
public IEnumerable<Type> Types => TypeToIndex.Keys;
#endif

View File

@ -1,4 +1,6 @@
namespace MoonTools.ECS
using System;
namespace MoonTools.ECS
{
public class World
{
@ -9,11 +11,11 @@
internal readonly MessageDepot MessageDepot = new MessageDepot();
internal readonly RelationDepot RelationDepot;
internal readonly FilterStorage FilterStorage;
public FilterBuilder FilterBuilder => new FilterBuilder(FilterStorage, ComponentTypeIndices);
internal readonly TemplateStorage TemplateStorage = new TemplateStorage();
internal readonly ComponentDepot TemplateComponentDepot;
public World()
{
ComponentDepot = new ComponentDepot(ComponentTypeIndices);
@ -29,6 +31,13 @@
public void Set<TComponent>(Entity entity, in TComponent component) where TComponent : unmanaged
{
#if DEBUG
// check for use after destroy
if (!EntityStorage.Exists(entity))
{
throw new InvalidOperationException("This entity is not valid!");
}
#endif
if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex<TComponent>()))
{
FilterStorage.Check<TComponent>(entity.ID);
@ -44,11 +53,12 @@
public void Set<TComponent>(in Template template, in TComponent component) where TComponent : unmanaged
{
TemplateStorage.SetComponent(template.ID, ComponentTypeIndices.GetIndex<TComponent>());
var componentTypeIndex = ComponentTypeIndices.GetIndex<TComponent>();
TemplateStorage.SetComponent(template.ID, componentTypeIndex);
TemplateComponentDepot.Set(template.ID, component);
ComponentDepot.Register<TComponent>(componentTypeIndex);
}
// TODO: TEST ME!!!
public Entity Instantiate(in Template template)
{
var entity = EntityStorage.Create();
@ -68,14 +78,31 @@
MessageDepot.Add(message);
}
public void Destroy(in Entity entity)
{
foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID))
{
ComponentDepot.Remove(entity.ID, componentTypeIndex);
FilterStorage.RemoveEntity(entity.ID, componentTypeIndex);
}
foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID))
{
RelationDepot.UnrelateAll(entity.ID, relationTypeIndex);
}
EntityStorage.Destroy(entity);
}
public void FinishUpdate()
{
MessageDepot.Clear();
}
public WorldState CreateState()
public Snapshot CreateSnapshot()
{
return new WorldState();
return new Snapshot(this);
}
}
}