complete world state storage

pull/2/head
cosmonaut 2022-05-02 18:41:59 -07:00
parent 190d1413ca
commit 8f620c7a20
13 changed files with 217 additions and 74 deletions

View File

@ -221,7 +221,7 @@ namespace MoonTools.ECS
TypesWithDisabledSerialization.Add(typeof(TComponent));
}
public void Serialize(ComponentDepotState state)
public void Save(ComponentDepotState state)
{
foreach (var (type, storage) in storages)
{
@ -232,7 +232,7 @@ namespace MoonTools.ECS
state.StorageStates.Add(type, storage.CreateState());
}
storage.Serialize(state.StorageStates[type]);
storage.Save(state.StorageStates[type]);
}
}
@ -250,11 +250,11 @@ namespace MoonTools.ECS
}
}
public void Deserialize(ComponentDepotState state)
public void Load(ComponentDepotState state)
{
foreach (var (type, storageState) in state.StorageStates)
{
storages[type].Deserialize(storageState);
storages[type].Load(storageState);
}
foreach (var (signature, setState) in state.FilterStates)

View File

@ -10,8 +10,8 @@ namespace MoonTools.ECS
public abstract bool Remove(int entityID);
public abstract object Debug_Get(int entityID);
public abstract ComponentStorageState CreateState();
public abstract void Serialize(ComponentStorageState state);
public abstract void Deserialize(ComponentStorageState state);
public abstract void Save(ComponentStorageState state);
public abstract void Load(ComponentStorageState state);
}
internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : unmanaged
@ -127,44 +127,38 @@ namespace MoonTools.ECS
return ComponentStorageState.Create<TComponent>(nextID);
}
public override void Serialize(ComponentStorageState serializedComponentStorage)
public override void Save(ComponentStorageState state)
{
ReadOnlySpan<byte> entityIDBytes = MemoryMarshal.Cast<int, byte>(new ReadOnlySpan<int>(entityIDs, 0, nextID));
if (entityIDBytes.Length > serializedComponentStorage.EntityIDs.Length)
if (entityIDBytes.Length > state.EntityIDs.Length)
{
Array.Resize(ref serializedComponentStorage.EntityIDs, entityIDBytes.Length);
Array.Resize(ref state.EntityIDs, entityIDBytes.Length);
}
entityIDBytes.CopyTo(serializedComponentStorage.EntityIDs);
entityIDBytes.CopyTo(state.EntityIDs);
ReadOnlySpan<byte> componentBytes = MemoryMarshal.Cast<TComponent, byte>(AllComponents());
if (componentBytes.Length > serializedComponentStorage.Components.Length)
if (componentBytes.Length > state.Components.Length)
{
Array.Resize(ref serializedComponentStorage.Components, componentBytes.Length);
Array.Resize(ref state.Components, componentBytes.Length);
}
componentBytes.CopyTo(serializedComponentStorage.Components);
componentBytes.CopyTo(state.Components);
serializedComponentStorage.EntityIdToStorageIndex.Clear();
foreach (var kvp in entityIDToStorageIndex)
{
serializedComponentStorage.EntityIdToStorageIndex[kvp.Key] = kvp.Value;
}
serializedComponentStorage.Count = nextID;
state.Count = nextID;
}
public override void Deserialize(ComponentStorageState serializedComponentStorage)
public override void Load(ComponentStorageState state)
{
serializedComponentStorage.EntityIDs.CopyTo(MemoryMarshal.Cast<int, byte>(entityIDs));
serializedComponentStorage.Components.CopyTo(MemoryMarshal.Cast<TComponent, byte>(components));
state.EntityIDs.CopyTo(MemoryMarshal.Cast<int, byte>(entityIDs));
state.Components.CopyTo(MemoryMarshal.Cast<TComponent, byte>(components));
entityIDToStorageIndex.Clear();
foreach (var kvp in serializedComponentStorage.EntityIdToStorageIndex)
for (var i = 0; i < state.Count; i += 1)
{
entityIDToStorageIndex[kvp.Key] = kvp.Value;
entityIDToStorageIndex[entityIDs[i]] = i;
}
nextID = serializedComponentStorage.Count;
nextID = state.Count;
}
}
}

View File

@ -23,7 +23,7 @@ namespace MoonTools.ECS
Release(entity.ID);
}
public void Serialize(EntityStorageState state)
public void Save(EntityStorageState state)
{
state.NextID = nextID;
state.availableIDs.Clear();
@ -33,7 +33,7 @@ namespace MoonTools.ECS
}
}
public void Deserialize(EntityStorageState state)
public void Load(EntityStorageState state)
{
nextID = state.NextID;
availableIDs.Clear();

View File

@ -82,12 +82,6 @@ namespace MoonTools.ECS
public void Save(IndexableSetState<T> state)
{
state.Indices.Clear();
foreach (var (key, value) in indices)
{
state.Indices[key] = value;
}
ReadOnlySpan<byte> arrayBytes = MemoryMarshal.Cast<T, byte>(array);
if (arrayBytes.Length > state.Array.Length)
@ -102,14 +96,14 @@ namespace MoonTools.ECS
public void Load(IndexableSetState<T> state)
{
indices.Clear();
foreach (var kvp in state.Indices)
{
indices[kvp.Key] = kvp.Value;
}
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,61 @@ 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);
// FIXME: incorrect comparison
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

@ -5,7 +5,6 @@ namespace MoonTools.ECS
internal class ComponentStorageState
{
public int Count;
public Dictionary<int, int> EntityIdToStorageIndex;
public byte[] EntityIDs;
public byte[] Components;
@ -21,7 +20,6 @@ namespace MoonTools.ECS
private ComponentStorageState(int count, int entityIDSize, int componentSize)
{
Count = count;
EntityIdToStorageIndex = new Dictionary<int, int>(count);
EntityIDs = new byte[entityIDSize];
Components = new byte[componentSize];
}

View File

@ -5,13 +5,11 @@ namespace MoonTools.ECS
internal class IndexableSetState<T> where T : unmanaged
{
public int Count;
public Dictionary<T, int> Indices;
public byte[] Array;
public unsafe IndexableSetState(int count)
{
Count = count;
Indices = new Dictionary<T, int>(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];
}
}
}

View File

@ -2,13 +2,15 @@ namespace MoonTools.ECS
{
public class WorldState
{
internal ComponentDepotState ComponentDepotState;
internal EntityStorageState EntityStorageState;
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

@ -70,7 +70,7 @@ namespace MoonTools.ECS
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 : unmanaged

View File

@ -37,16 +37,18 @@
return new WorldState();
}
public void Serialize(WorldState state)
public void Save(WorldState state)
{
ComponentDepot.Serialize(state.ComponentDepotState);
EntityStorage.Serialize(state.EntityStorageState);
ComponentDepot.Save(state.ComponentDepotState);
EntityStorage.Save(state.EntityStorageState);
RelationDepot.Save(state.RelationDepotState);
}
public void Deserialize(WorldState state)
public void Load(WorldState state)
{
ComponentDepot.Deserialize(state.ComponentDepotState);
EntityStorage.Deserialize(state.EntityStorageState);
ComponentDepot.Load(state.ComponentDepotState);
EntityStorage.Load(state.EntityStorageState);
RelationDepot.Load(state.RelationDepotState);
}
}
}