World State Save and Load (#2)

Reviewed-on: #2
filter_relations
cosmonaut 2022-05-03 04:51:11 +00:00
parent 5243259acb
commit 56948e4c31
21 changed files with 535 additions and 183 deletions

View File

@ -4,6 +4,7 @@
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>

View File

@ -11,13 +11,13 @@ namespace MoonTools.ECS
private Dictionary<Type, HashSet<FilterSignature>> typeToFilterSignatures = new Dictionary<Type, HashSet<FilterSignature>>();
private Dictionary<int, HashSet<Type>> entityComponentMap = new Dictionary<int, HashSet<Type>>();
#if DEBUG
private Dictionary<Type, Filter> singleComponentFilters = new Dictionary<Type, Filter>();
#endif
internal void Register<TComponent>() where TComponent : struct
private HashSet<Type> TypesWithDisabledSerialization = new HashSet<Type>();
internal void Register<TComponent>() where TComponent : unmanaged
{
if (!storages.ContainsKey(typeof(TComponent)))
{
@ -33,19 +33,19 @@ namespace MoonTools.ECS
return storages[type];
}
private ComponentStorage<TComponent> Lookup<TComponent>() where TComponent : struct
private ComponentStorage<TComponent> Lookup<TComponent>() where TComponent : unmanaged
{
// TODO: is it possible to optimize this?
Register<TComponent>();
return (ComponentStorage<TComponent>) storages[typeof(TComponent)];
}
public bool Some<TComponent>() where TComponent : struct
public bool Some<TComponent>() where TComponent : unmanaged
{
return Lookup<TComponent>().Any();
}
public bool Has<TComponent>(int entityID) where TComponent : struct
public bool Has<TComponent>(int entityID) where TComponent : unmanaged
{
return Lookup<TComponent>().Has(entityID);
}
@ -55,29 +55,22 @@ namespace MoonTools.ECS
return Lookup(type).Has(entityID);
}
public ref readonly TComponent Get<TComponent>(int entityID) where TComponent : struct
public ref readonly TComponent Get<TComponent>(int entityID) where TComponent : unmanaged
{
return ref Lookup<TComponent>().Get(entityID);
}
public ref readonly TComponent Get<TComponent>() where TComponent : struct
public ref readonly TComponent Get<TComponent>() where TComponent : unmanaged
{
return ref Lookup<TComponent>().Get();
}
public void Set<TComponent>(int entityID, in TComponent component) where TComponent : struct
public void Set<TComponent>(int entityID, in TComponent component) where TComponent : unmanaged
{
Lookup<TComponent>().Set(entityID, component);
if (!entityComponentMap.ContainsKey(entityID))
{
entityComponentMap.Add(entityID, new HashSet<Type>());
}
var notFound = entityComponentMap[entityID].Add(typeof(TComponent));
var existed = Lookup<TComponent>().Set(entityID, component);
// update filters
if (notFound)
if (!existed)
{
if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures))
{
@ -89,24 +82,22 @@ namespace MoonTools.ECS
}
}
public Entity GetSingletonEntity<TComponent>() where TComponent : struct
public Entity GetSingletonEntity<TComponent>() where TComponent : unmanaged
{
return Lookup<TComponent>().FirstEntity();
}
public ReadOnlySpan<TComponent> ReadComponents<TComponent>() where TComponent : struct
public ReadOnlySpan<TComponent> ReadComponents<TComponent>() where TComponent : unmanaged
{
return Lookup<TComponent>().AllComponents();
}
private void Remove(Type type, int entityID)
{
Lookup(type).Remove(entityID);
var found = entityComponentMap[entityID].Remove(type);
var existed = Lookup(type).Remove(entityID);
// update filters
if (found)
if (existed)
{
if (typeToFilterSignatures.TryGetValue(type, out var filterSignatures))
{
@ -118,14 +109,12 @@ namespace MoonTools.ECS
}
}
public void Remove<TComponent>(int entityID) where TComponent : struct
public void Remove<TComponent>(int entityID) where TComponent : unmanaged
{
Lookup<TComponent>().Remove(entityID);
var found = entityComponentMap[entityID].Remove(typeof(TComponent));
var existed = Lookup<TComponent>().Remove(entityID);
// update filters
if (found)
if (existed)
{
if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures))
{
@ -137,16 +126,12 @@ namespace MoonTools.ECS
}
}
// TODO: is there some way to optimize this without complicating serialization?
public void OnEntityDestroy(int entityID)
{
if (entityComponentMap.ContainsKey(entityID))
foreach (var type in storages.Keys)
{
foreach (var type in entityComponentMap[entityID])
{
Remove(type, entityID);
}
entityComponentMap.Remove(entityID);
Remove(type, entityID);
}
}
@ -231,6 +216,53 @@ namespace MoonTools.ECS
filterSignatureToEntityIDs[filterSignature].Add(entityID);
}
public void DisableSerialization<TComponent>() where TComponent : unmanaged
{
TypesWithDisabledSerialization.Add(typeof(TComponent));
}
public void Save(ComponentDepotState state)
{
foreach (var (type, storage) in storages)
{
if (!TypesWithDisabledSerialization.Contains(type))
{
if (!state.StorageStates.ContainsKey(type))
{
state.StorageStates.Add(type, storage.CreateState());
}
storage.Save(state.StorageStates[type]);
}
}
foreach (var (signature, set) in filterSignatureToEntityIDs)
{
// FIXME: we could cache this
if (!signature.Included.Overlaps(TypesWithDisabledSerialization) && !signature.Excluded.Overlaps(TypesWithDisabledSerialization))
{
if (!state.FilterStates.ContainsKey(signature))
{
state.FilterStates[signature] = new IndexableSetState<int>(set.Count);
}
set.Save(state.FilterStates[signature]);
}
}
}
public void Load(ComponentDepotState state)
{
foreach (var (type, storageState) in state.StorageStates)
{
storages[type].Load(storageState);
}
foreach (var (signature, setState) in state.FilterStates)
{
filterSignatureToEntityIDs[signature].Load(setState);
}
}
#if DEBUG
public IEnumerable<object> Debug_GetAllComponents(int entityID)
{

View File

@ -1,17 +1,20 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace MoonTools.ECS
{
internal abstract class ComponentStorage
{
public abstract bool Has(int entityID);
public abstract void Remove(int entityID);
public abstract bool Remove(int entityID);
public abstract object Debug_Get(int entityID);
public abstract ComponentStorageState CreateState();
public abstract void Save(ComponentStorageState state);
public abstract void Load(ComponentStorageState state);
}
// FIXME: we can probably get rid of this weird entity storage system by using filters
internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : struct
internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : unmanaged
{
private int nextID;
private readonly Dictionary<int, int> entityIDToStorageIndex = new Dictionary<int, int>(16);
@ -49,8 +52,11 @@ namespace MoonTools.ECS
return ref components[0];
}
public void Set(int entityID, in TComponent component)
// Returns true if the entity already had this component.
public bool Set(int entityID, in TComponent component)
{
bool result = true;
if (!entityIDToStorageIndex.ContainsKey(entityID))
{
var index = nextID;
@ -64,12 +70,17 @@ namespace MoonTools.ECS
entityIDToStorageIndex[entityID] = index;
entityIDs[index] = entityID;
result = false;
}
components[entityIDToStorageIndex[entityID]] = component;
return result;
}
public override void Remove(int entityID)
// Returns true if the entity had this component.
public override bool Remove(int entityID)
{
if (entityIDToStorageIndex.ContainsKey(entityID))
{
@ -88,7 +99,11 @@ namespace MoonTools.ECS
}
nextID -= 1;
return true;
}
return false;
}
public void Clear()
@ -106,5 +121,44 @@ namespace MoonTools.ECS
{
return new Entity(entityIDs[0]);
}
public override ComponentStorageState CreateState()
{
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;
}
}
}

View File

@ -5,52 +5,43 @@ namespace MoonTools.ECS
{
public abstract class EntityComponentReader
{
internal EntityStorage EntityStorage;
internal ComponentDepot ComponentDepot;
internal RelationDepot RelationDepot;
protected readonly World World;
internal EntityStorage EntityStorage => World.EntityStorage;
internal ComponentDepot ComponentDepot => World.ComponentDepot;
internal RelationDepot RelationDepot => World.RelationDepot;
protected FilterBuilder FilterBuilder => new FilterBuilder(ComponentDepot);
internal void RegisterEntityStorage(EntityStorage entityStorage)
public EntityComponentReader(World world)
{
EntityStorage = entityStorage;
World = world;
}
internal void RegisterComponentDepot(ComponentDepot componentDepot)
{
ComponentDepot = componentDepot;
}
internal void RegisterRelationDepot(RelationDepot relationDepot)
{
RelationDepot = relationDepot;
}
protected ReadOnlySpan<TComponent> ReadComponents<TComponent>() where TComponent : struct
protected ReadOnlySpan<TComponent> ReadComponents<TComponent>() where TComponent : unmanaged
{
return ComponentDepot.ReadComponents<TComponent>();
}
protected bool Has<TComponent>(in Entity entity) where TComponent : struct
protected bool Has<TComponent>(in Entity entity) where TComponent : unmanaged
{
return ComponentDepot.Has<TComponent>(entity.ID);
}
protected bool Some<TComponent>() where TComponent : struct
protected bool Some<TComponent>() where TComponent : unmanaged
{
return ComponentDepot.Some<TComponent>();
}
protected ref readonly TComponent Get<TComponent>(in Entity entity) where TComponent : struct
protected ref readonly TComponent Get<TComponent>(in Entity entity) where TComponent : unmanaged
{
return ref ComponentDepot.Get<TComponent>(entity.ID);
}
protected ref readonly TComponent GetSingleton<TComponent>() where TComponent : struct
protected ref readonly TComponent GetSingleton<TComponent>() where TComponent : unmanaged
{
return ref ComponentDepot.Get<TComponent>();
}
protected Entity GetSingletonEntity<TComponent>() where TComponent : struct
protected Entity GetSingletonEntity<TComponent>() where TComponent : unmanaged
{
return ComponentDepot.GetSingletonEntity<TComponent>();
}
@ -60,22 +51,22 @@ namespace MoonTools.ECS
return EntityStorage.Exists(entity);
}
protected IEnumerable<(Entity, Entity, TRelationKind)> Relations<TRelationKind>() where TRelationKind : struct
protected IEnumerable<(Entity, Entity, TRelationKind)> Relations<TRelationKind>() where TRelationKind : unmanaged
{
return RelationDepot.Relations<TRelationKind>();
}
protected bool Related<TRelationKind>(in Entity a, in Entity b) where TRelationKind : struct
protected bool Related<TRelationKind>(in Entity a, in Entity b) where TRelationKind : unmanaged
{
return RelationDepot.Related<TRelationKind>(a.ID, b.ID);
}
protected IEnumerable<(Entity, TRelationKind)> RelatedToA<TRelationKind>(in Entity entity) where TRelationKind : struct
protected IEnumerable<(Entity, TRelationKind)> RelatedToA<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
{
return RelationDepot.RelatedToA<TRelationKind>(entity.ID);
}
protected IEnumerable<(Entity, TRelationKind)> RelatedToB<TRelationKind>(in Entity entity) where TRelationKind : struct
protected IEnumerable<(Entity, TRelationKind)> RelatedToB<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
{
return RelationDepot.RelatedToB<TRelationKind>(entity.ID);
}

View File

@ -1,22 +1,75 @@
namespace MoonTools.ECS
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class EntityStorage
{
public IDStorage idStorage = new IDStorage();
private int nextID = 0;
private readonly Stack<int> availableIDs = new Stack<int>();
private readonly HashSet<int> availableIDHash = new HashSet<int>();
public Entity Create()
{
return new Entity(idStorage.NextID());
return new Entity(NextID());
}
public bool Exists(in Entity entity)
{
return idStorage.Taken(entity.ID);
return Taken(entity.ID);
}
public void Destroy(in Entity entity)
{
idStorage.Release(entity.ID);
Release(entity.ID);
}
public void Save(EntityStorageState state)
{
state.NextID = nextID;
state.availableIDs.Clear();
foreach (var id in availableIDs)
{
state.availableIDs.Add(id);
}
}
public void Load(EntityStorageState state)
{
nextID = state.NextID;
availableIDs.Clear();
availableIDHash.Clear();
foreach (var id in state.availableIDs)
{
availableIDs.Push(id);
availableIDHash.Add(id);
}
}
private int NextID()
{
if (availableIDs.Count > 0)
{
var id = availableIDs.Pop();
availableIDHash.Remove(id);
return id;
}
else
{
var id = nextID;
nextID += 1;
return id;
}
}
private bool Taken(int id)
{
return !availableIDHash.Contains(id) && id < nextID;
}
private void Release(int id)
{
availableIDs.Push(id);
availableIDHash.Add(id);
}
}
}

View File

@ -23,14 +23,14 @@ namespace MoonTools.ECS
Excluded = excluded;
}
public FilterBuilder Include<TComponent>() where TComponent : struct
public FilterBuilder Include<TComponent>() where TComponent : unmanaged
{
ComponentDepot.Register<TComponent>();
Included.Add(typeof(TComponent));
return new FilterBuilder(ComponentDepot, Included, Excluded);
}
public FilterBuilder Exclude<TComponent>() where TComponent : struct
public FilterBuilder Exclude<TComponent>() where TComponent : unmanaged
{
ComponentDepot.Register<TComponent>();
Excluded.Add(typeof(TComponent));

View File

@ -7,8 +7,8 @@ namespace MoonTools.ECS
{
private const int HASH_FACTOR = 97;
public HashSet<Type> Included;
public HashSet<Type> Excluded;
public readonly HashSet<Type> Included;
public readonly HashSet<Type> Excluded;
public FilterSignature(HashSet<Type> included, HashSet<Type> excluded)
{

View File

@ -1,39 +0,0 @@
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class IDStorage
{
private int nextID = 0;
private readonly Stack<int> availableIDs = new Stack<int>();
private readonly HashSet<int> availableIDHash = new HashSet<int>();
public int NextID()
{
if (availableIDs.Count > 0)
{
var id = availableIDs.Pop();
availableIDHash.Remove(id);
return id;
}
else
{
var id = nextID;
nextID += 1;
return id;
}
}
public bool Taken(int id)
{
return !availableIDHash.Contains(id) && id < nextID;
}
public void Release(int id)
{
availableIDs.Push(id);
availableIDHash.Add(id);
}
}
}

View File

@ -1,10 +1,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace MoonTools.ECS
{
internal class IndexableSet<T> : IEnumerable<T> where T : notnull
internal class IndexableSet<T> : IEnumerable<T> where T : unmanaged
{
private Dictionary<T, int> indices;
private T[] array;
@ -78,5 +79,32 @@ namespace MoonTools.ECS
yield return array[i];
}
}
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

@ -7,7 +7,7 @@ namespace MoonTools.ECS
{
private Dictionary<Type, RelationStorage> storages = new Dictionary<Type, RelationStorage>();
private void Register<TRelationKind>() where TRelationKind : struct
private void Register<TRelationKind>() where TRelationKind : unmanaged
{
if (!storages.ContainsKey(typeof(TRelationKind)))
{
@ -15,18 +15,18 @@ namespace MoonTools.ECS
}
}
private RelationStorage<TRelationKind> Lookup<TRelationKind>() where TRelationKind : struct
private RelationStorage<TRelationKind> Lookup<TRelationKind>() where TRelationKind : unmanaged
{
Register<TRelationKind>();
return (RelationStorage<TRelationKind>) storages[typeof(TRelationKind)];
}
public void Add<TRelationKind>(Relation relation, TRelationKind relationData) where TRelationKind : struct
public void Set<TRelationKind>(Relation relation, TRelationKind relationData) where TRelationKind : unmanaged
{
Lookup<TRelationKind>().Add(relation, relationData);
Lookup<TRelationKind>().Set(relation, relationData);
}
public void Remove<TRelationKind>(Relation relation) where TRelationKind : struct
public void Remove<TRelationKind>(Relation relation) where TRelationKind : unmanaged
{
Lookup<TRelationKind>().Remove(relation);
}
@ -40,24 +40,45 @@ namespace MoonTools.ECS
}
}
public IEnumerable<(Entity, Entity, TRelationKind)> Relations<TRelationKind>() where TRelationKind : struct
public IEnumerable<(Entity, Entity, TRelationKind)> Relations<TRelationKind>() where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().All();
}
public bool Related<TRelationKind>(int idA, int idB) where TRelationKind : struct
public bool Related<TRelationKind>(int idA, int idB) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().Has(new Relation(idA, idB));
}
public IEnumerable<(Entity, TRelationKind)> RelatedToA<TRelationKind>(int entityID) where TRelationKind : struct
public IEnumerable<(Entity, TRelationKind)> RelatedToA<TRelationKind>(int entityID) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().RelatedToA(entityID);
}
public IEnumerable<(Entity, TRelationKind)> RelatedToB<TRelationKind>(int entityID) where TRelationKind : struct
public IEnumerable<(Entity, TRelationKind)> RelatedToB<TRelationKind>(int entityID) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().RelatedToB(entityID);
}
public void Save(RelationDepotState state)
{
foreach (var (type, storage) in storages)
{
if (!state.StorageStates.ContainsKey(type))
{
state.StorageStates.Add(type, storage.CreateState());
}
storage.Save(state.StorageStates[type]);
}
}
public void Load(RelationDepotState state)
{
foreach (var (type, storageState) in state.StorageStates)
{
storages[type].Load(storageState);
}
}
}
}

View File

@ -1,32 +1,46 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
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 OnEntityDestroy(int entityID);
}
// Relation is the two entities, A related to B.
// TRelation is the data attached to the relation.
internal class RelationStorage<TRelation> : RelationStorage where TRelation : struct
internal class RelationStorage<TRelation> : RelationStorage where TRelation : unmanaged
{
private Dictionary<Relation, TRelation> relations = new Dictionary<Relation, TRelation>(16);
private int count = 0;
private Dictionary<Relation, int> indices = new Dictionary<Relation, int>(16);
private Relation[] relations = new Relation[16];
private TRelation[] relationDatas = new TRelation[16];
private Dictionary<int, HashSet<int>> entitiesRelatedToA = new Dictionary<int, HashSet<int>>(16);
private Dictionary<int, HashSet<int>> entitiesRelatedToB = new Dictionary<int, HashSet<int>>(16);
private Stack<HashSet<int>> listPool = new Stack<HashSet<int>>();
public IEnumerable<(Entity, Entity, TRelation)> All()
{
foreach (var relationData in relations)
for (var i = 0; i < count; i += 1)
{
yield return (relationData.Key.A, relationData.Key.B, relationData.Value);
var relation = relations[i];
yield return (relation.A, relation.B, relationDatas[i]);
}
}
public void Add(Relation relation, TRelation relationData)
public void Set(Relation relation, TRelation relationData)
{
if (relations.ContainsKey(relation)) { return; }
if (indices.ContainsKey(relation))
{
var index = indices[relation];
relationDatas[index] = relationData;
return;
}
var idA = relation.A.ID;
var idB = relation.B.ID;
@ -43,12 +57,20 @@ namespace MoonTools.ECS
}
entitiesRelatedToB[idB].Add(idA);
relations.Add(relation, relationData);
if (count >= relationDatas.Length)
{
Array.Resize(ref relationDatas, relationDatas.Length * 2);
}
relations[count] = relation;
relationDatas[count] = relationData;
indices.Add(relation, count);
count += 1;
}
public bool Has(Relation relation)
{
return relations.ContainsKey(relation);
return indices.ContainsKey(relation);
}
// FIXME: is there a more descriptive name for these?
@ -59,7 +81,7 @@ namespace MoonTools.ECS
foreach (var id in entitiesRelatedToA[entityID])
{
var relation = new Relation(entityID, id);
yield return (relation.B, relations[relation]);
yield return (relation.B, relationDatas[indices[relation]]);
}
}
}
@ -71,7 +93,7 @@ namespace MoonTools.ECS
foreach (var id in entitiesRelatedToB[entityID])
{
var relation = new Relation(id, entityID);
yield return (relation.A, relations[relation]);
yield return (relation.A, relationDatas[indices[relation]]);
}
}
}
@ -88,7 +110,26 @@ namespace MoonTools.ECS
entitiesRelatedToB[relation.B.ID].Remove(relation.A.ID);
}
return relations.Remove(relation);
if (indices.ContainsKey(relation))
{
var index = indices[relation];
var lastElementIndex = count - 1;
// move an element into the hole
if (index != lastElementIndex)
{
var lastRelation = relations[lastElementIndex];
indices[lastRelation] = index;
relationDatas[index] = relationDatas[lastElementIndex];
relations[index] = lastRelation;
}
count -= 1;
indices.Remove(relation);
return true;
}
return false;
}
public override void OnEntityDestroy(int entityID)
@ -131,5 +172,60 @@ 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();
entitiesRelatedToA.Clear();
entitiesRelatedToB.Clear();
for (var i = 0; i < state.Count; i += 1)
{
var relation = relations[i];
indices[relation] = i;
if (!entitiesRelatedToA.ContainsKey(relation.A.ID))
{
entitiesRelatedToA[relation.A.ID] = AcquireHashSetFromPool();
}
entitiesRelatedToA[relation.A.ID].Add(relation.B.ID);
if (!entitiesRelatedToB.ContainsKey(relation.B.ID))
{
entitiesRelatedToB[relation.B.ID] = AcquireHashSetFromPool();
}
entitiesRelatedToB[relation.B.ID].Add(relation.A.ID);
}
count = state.Count;
}
}
}

View File

@ -2,9 +2,6 @@
{
public abstract class Renderer : EntityComponentReader
{
public Renderer(World world)
{
world.AddRenderer(this);
}
public Renderer(World world) : base(world) { }
}
}

View File

@ -0,0 +1,11 @@
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

@ -0,0 +1,27 @@
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

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

View File

@ -0,0 +1,16 @@
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

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

View File

@ -0,0 +1,27 @@
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];
}
}
}

16
src/State/WorldState.cs Normal file
View File

@ -0,0 +1,16 @@
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

@ -5,17 +5,9 @@ namespace MoonTools.ECS
{
public abstract class System : EntityComponentReader
{
internal MessageDepot MessageDepot;
internal MessageDepot MessageDepot => World.MessageDepot;
internal void RegisterMessageDepot(MessageDepot messageDepot)
{
MessageDepot = messageDepot;
}
public System(World world)
{
world.AddSystem(this);
}
public System(World world) : base(world) { }
public abstract void Update(TimeSpan delta);
@ -24,7 +16,7 @@ namespace MoonTools.ECS
return EntityStorage.Create();
}
protected void Set<TComponent>(in Entity entity, in TComponent component) where TComponent : struct
protected void Set<TComponent>(in Entity entity, in TComponent component) where TComponent : unmanaged
{
#if DEBUG
// check for use after destroy
@ -36,52 +28,52 @@ namespace MoonTools.ECS
ComponentDepot.Set<TComponent>(entity.ID, component);
}
protected void Remove<TComponent>(in Entity entity) where TComponent : struct
protected void Remove<TComponent>(in Entity entity) where TComponent : unmanaged
{
ComponentDepot.Remove<TComponent>(entity.ID);
}
protected ReadOnlySpan<TMessage> ReadMessages<TMessage>() where TMessage : struct
protected ReadOnlySpan<TMessage> ReadMessages<TMessage>() where TMessage : unmanaged
{
return MessageDepot.All<TMessage>();
}
protected TMessage ReadMessage<TMessage>() where TMessage : struct
protected TMessage ReadMessage<TMessage>() where TMessage : unmanaged
{
return MessageDepot.First<TMessage>();
}
protected bool SomeMessage<TMessage>() where TMessage : struct
protected bool SomeMessage<TMessage>() where TMessage : unmanaged
{
return MessageDepot.Some<TMessage>();
}
protected IEnumerable<TMessage> ReadMessagesWithEntity<TMessage>(in Entity entity) where TMessage : struct, IHasEntity
protected IEnumerable<TMessage> ReadMessagesWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged, IHasEntity
{
return MessageDepot.WithEntity<TMessage>(entity.ID);
}
protected ref readonly TMessage ReadMessageWithEntity<TMessage>(in Entity entity) where TMessage : struct, IHasEntity
protected ref readonly TMessage ReadMessageWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged, IHasEntity
{
return ref MessageDepot.FirstWithEntity<TMessage>(entity.ID);
}
protected bool SomeMessageWithEntity<TMessage>(in Entity entity) where TMessage : struct, IHasEntity
protected bool SomeMessageWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged, IHasEntity
{
return MessageDepot.SomeWithEntity<TMessage>(entity.ID);
}
protected void Send<TMessage>(in TMessage message) where TMessage : struct
protected void Send<TMessage>(in TMessage message) where TMessage : unmanaged
{
MessageDepot.Add(message);
}
protected void Relate<TRelationKind>(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : struct
protected void Relate<TRelationKind>(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged
{
RelationDepot.Add<TRelationKind>(new Relation(entityA, entityB), relationData);
RelationDepot.Set<TRelationKind>(new Relation(entityA, entityB), relationData);
}
protected void Unrelate<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : struct
protected void Unrelate<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged
{
RelationDepot.Remove<TRelationKind>(new Relation(entityA, entityB));
}

View File

@ -2,37 +2,22 @@
{
public class World
{
private readonly EntityStorage EntityStorage = new EntityStorage();
private readonly ComponentDepot ComponentDepot = new ComponentDepot();
private readonly MessageDepot MessageDepot = new MessageDepot();
private readonly RelationDepot RelationDepot = new RelationDepot();
internal void AddSystem(System system)
{
system.RegisterEntityStorage(EntityStorage);
system.RegisterComponentDepot(ComponentDepot);
system.RegisterMessageDepot(MessageDepot);
system.RegisterRelationDepot(RelationDepot);
}
internal void AddRenderer(Renderer renderer)
{
renderer.RegisterEntityStorage(EntityStorage);
renderer.RegisterComponentDepot(ComponentDepot);
renderer.RegisterRelationDepot(RelationDepot);
}
internal readonly EntityStorage EntityStorage = new EntityStorage();
internal readonly ComponentDepot ComponentDepot = new ComponentDepot();
internal readonly MessageDepot MessageDepot = new MessageDepot();
internal readonly RelationDepot RelationDepot = new RelationDepot();
public Entity CreateEntity()
{
return EntityStorage.Create();
}
public void Set<TComponent>(Entity entity, in TComponent component) where TComponent : struct
public void Set<TComponent>(Entity entity, in TComponent component) where TComponent : unmanaged
{
ComponentDepot.Set(entity.ID, component);
}
public void Send<TMessage>(in TMessage message) where TMessage : struct
public void Send<TMessage>(in TMessage message) where TMessage : unmanaged
{
MessageDepot.Add(message);
}
@ -41,5 +26,29 @@
{
MessageDepot.Clear();
}
public void DisableSerialization<TComponent>() where TComponent : unmanaged
{
ComponentDepot.DisableSerialization<TComponent>();
}
public WorldState CreateState()
{
return new WorldState();
}
public void Save(WorldState state)
{
ComponentDepot.Save(state.ComponentDepotState);
EntityStorage.Save(state.EntityStorageState);
RelationDepot.Save(state.RelationDepotState);
}
public void Load(WorldState state)
{
ComponentDepot.Load(state.ComponentDepotState);
EntityStorage.Load(state.EntityStorageState);
RelationDepot.Load(state.RelationDepotState);
}
}
}