World State Save and Load #2
|
@ -4,6 +4,7 @@
|
|||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -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,17 +126,13 @@ 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 entityComponentMap[entityID])
|
||||
foreach (var type in storages.Keys)
|
||||
{
|
||||
Remove(type, entityID);
|
||||
}
|
||||
|
||||
entityComponentMap.Remove(entityID);
|
||||
}
|
||||
}
|
||||
|
||||
public Filter CreateFilter(HashSet<Type> included, HashSet<Type> excluded)
|
||||
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
{
|
||||
public abstract class Renderer : EntityComponentReader
|
||||
{
|
||||
public Renderer(World world)
|
||||
{
|
||||
world.AddRenderer(this);
|
||||
}
|
||||
public Renderer(World world) : base(world) { }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>>();
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace MoonTools.ECS
|
||||
{
|
||||
internal class EntityStorageState
|
||||
{
|
||||
public int NextID;
|
||||
public List<int> availableIDs = new List<int>();
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>();
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
|
51
src/World.cs
51
src/World.cs
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue