storage optimization
parent
71a95cb2d7
commit
91cf93e1c9
|
@ -1,46 +1,46 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace MoonTools.ECS
|
namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
internal class ComponentDepot
|
internal class ComponentDepot
|
||||||
{
|
{
|
||||||
private Dictionary<Type, ComponentStorage> storages = new Dictionary<Type, ComponentStorage>();
|
private TypeIndices ComponentTypeIndices;
|
||||||
|
|
||||||
private Dictionary<FilterSignature, IndexableSet<int>> filterSignatureToEntityIDs = new Dictionary<FilterSignature, IndexableSet<int>>();
|
private ComponentStorage[] storages = new ComponentStorage[256];
|
||||||
|
|
||||||
private Dictionary<Type, HashSet<FilterSignature>> typeToFilterSignatures = new Dictionary<Type, HashSet<FilterSignature>>();
|
public ComponentDepot(TypeIndices componentTypeIndices)
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
private Dictionary<Type, Filter> singleComponentFilters = new Dictionary<Type, Filter>();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private HashSet<Type> TypesWithDisabledSerialization = new HashSet<Type>();
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
internal void Register<TComponent>() where TComponent : unmanaged
|
|
||||||
{
|
{
|
||||||
if (!storages.ContainsKey(typeof(TComponent)))
|
ComponentTypeIndices = componentTypeIndices;
|
||||||
{
|
|
||||||
storages.Add(typeof(TComponent), new ComponentStorage<TComponent>());
|
|
||||||
#if DEBUG
|
|
||||||
singleComponentFilters.Add(typeof(TComponent), CreateFilter(new HashSet<Type>() { typeof(TComponent) }, new HashSet<Type>()));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void Register<TComponent>(int index) where TComponent : unmanaged
|
||||||
|
{
|
||||||
|
if (index >= storages.Length)
|
||||||
|
{
|
||||||
|
Array.Resize(ref storages, storages.Length * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
storages[index] = new ComponentStorage<TComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: is this necessary?
|
||||||
private ComponentStorage Lookup(Type type)
|
private ComponentStorage Lookup(Type type)
|
||||||
{
|
{
|
||||||
return storages[type];
|
return storages[ComponentTypeIndices.GetIndex(type)];
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private ComponentStorage<TComponent> Lookup<TComponent>() where TComponent : unmanaged
|
private ComponentStorage<TComponent> Lookup<TComponent>() where TComponent : unmanaged
|
||||||
{
|
{
|
||||||
// TODO: is it possible to optimize this?
|
var storageIndex = ComponentTypeIndices.GetIndex<TComponent>();
|
||||||
Register<TComponent>();
|
// TODO: is there some way to avoid this null check?
|
||||||
return (ComponentStorage<TComponent>) storages[typeof(TComponent)];
|
if (storages[storageIndex] == null)
|
||||||
|
{
|
||||||
|
Register<TComponent>(storageIndex);
|
||||||
|
}
|
||||||
|
return (ComponentStorage<TComponent>) storages[storageIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Some<TComponent>() where TComponent : unmanaged
|
public bool Some<TComponent>() where TComponent : unmanaged
|
||||||
|
@ -48,41 +48,26 @@ namespace MoonTools.ECS
|
||||||
return Lookup<TComponent>().Any();
|
return Lookup<TComponent>().Any();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Has<TComponent>(int entityID) where TComponent : unmanaged
|
|
||||||
{
|
|
||||||
return Lookup<TComponent>().Has(entityID);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool Has(Type type, int entityID)
|
|
||||||
{
|
|
||||||
return Lookup(type).Has(entityID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ref readonly TComponent Get<TComponent>(int entityID) where TComponent : unmanaged
|
public ref readonly TComponent Get<TComponent>(int entityID) where TComponent : unmanaged
|
||||||
{
|
{
|
||||||
return ref Lookup<TComponent>().Get(entityID);
|
return ref Lookup<TComponent>().Get(entityID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref readonly TComponent Get<TComponent>() where TComponent : unmanaged
|
#if DEBUG
|
||||||
|
public object Debug_Get(int entityID, int componentTypeIndex)
|
||||||
{
|
{
|
||||||
return ref Lookup<TComponent>().Get();
|
return storages[componentTypeIndex].Debug_Get(entityID);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public ref readonly TComponent GetFirst<TComponent>() where TComponent : unmanaged
|
||||||
|
{
|
||||||
|
return ref Lookup<TComponent>().GetFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Set<TComponent>(int entityID, in TComponent component) where TComponent : unmanaged
|
public void Set<TComponent>(int entityID, in TComponent component) where TComponent : unmanaged
|
||||||
{
|
{
|
||||||
var existed = Lookup<TComponent>().Set(entityID, component);
|
Lookup<TComponent>().Set(entityID, component);
|
||||||
|
|
||||||
// update filters
|
|
||||||
if (!existed)
|
|
||||||
{
|
|
||||||
if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures))
|
|
||||||
{
|
|
||||||
foreach (var filterSignature in filterSignatures)
|
|
||||||
{
|
|
||||||
CheckFilter(filterSignature, entityID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entity GetSingletonEntity<TComponent>() where TComponent : unmanaged
|
public Entity GetSingletonEntity<TComponent>() where TComponent : unmanaged
|
||||||
|
@ -95,209 +80,14 @@ namespace MoonTools.ECS
|
||||||
return Lookup<TComponent>().AllComponents();
|
return Lookup<TComponent>().AllComponents();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Remove(Type type, int entityID)
|
public void Remove(int entityID, int storageIndex)
|
||||||
{
|
{
|
||||||
var existed = Lookup(type).Remove(entityID);
|
storages[storageIndex].Remove(entityID);
|
||||||
|
|
||||||
// update filters
|
|
||||||
if (existed)
|
|
||||||
{
|
|
||||||
if (typeToFilterSignatures.TryGetValue(type, out var filterSignatures))
|
|
||||||
{
|
|
||||||
foreach (var filterSignature in filterSignatures)
|
|
||||||
{
|
|
||||||
CheckFilter(filterSignature, entityID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove<TComponent>(int entityID) where TComponent : unmanaged
|
public void Remove<TComponent>(int entityID) where TComponent : unmanaged
|
||||||
{
|
{
|
||||||
var existed = Lookup<TComponent>().Remove(entityID);
|
Lookup<TComponent>().Remove(entityID);
|
||||||
|
|
||||||
// update filters
|
|
||||||
if (existed)
|
|
||||||
{
|
|
||||||
if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures))
|
|
||||||
{
|
|
||||||
foreach (var filterSignature in filterSignatures)
|
|
||||||
{
|
|
||||||
CheckFilter(filterSignature, entityID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: is there some way to optimize this without complicating serialization?
|
|
||||||
public void OnEntityDestroy(int entityID)
|
|
||||||
{
|
|
||||||
foreach (var type in storages.Keys)
|
|
||||||
{
|
|
||||||
Remove(type, entityID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Filter CreateFilter(HashSet<Type> included, HashSet<Type> excluded)
|
|
||||||
{
|
|
||||||
var filterSignature = new FilterSignature(included, excluded);
|
|
||||||
if (!filterSignatureToEntityIDs.ContainsKey(filterSignature))
|
|
||||||
{
|
|
||||||
filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet<int>());
|
|
||||||
|
|
||||||
foreach (var type in included)
|
|
||||||
{
|
|
||||||
if (!typeToFilterSignatures.ContainsKey(type))
|
|
||||||
{
|
|
||||||
typeToFilterSignatures.Add(type, new HashSet<FilterSignature>());
|
|
||||||
}
|
|
||||||
|
|
||||||
typeToFilterSignatures[type].Add(filterSignature);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var type in excluded)
|
|
||||||
{
|
|
||||||
if (!typeToFilterSignatures.ContainsKey(type))
|
|
||||||
{
|
|
||||||
typeToFilterSignatures.Add(type, new HashSet<FilterSignature>());
|
|
||||||
}
|
|
||||||
|
|
||||||
typeToFilterSignatures[type].Add(filterSignature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Filter(this, included, excluded);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: this dictionary should probably just store entities
|
|
||||||
public IEnumerable<Entity> FilterEntities(Filter filter)
|
|
||||||
{
|
|
||||||
foreach (var id in filterSignatureToEntityIDs[filter.Signature])
|
|
||||||
{
|
|
||||||
yield return new Entity(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<Entity> FilterEntitiesRandom(Filter filter)
|
|
||||||
{
|
|
||||||
foreach (var index in RandomGenerator.LinearCongruentialGenerator(FilterCount(filter)))
|
|
||||||
{
|
|
||||||
yield return new Entity(filterSignatureToEntityIDs[filter.Signature][index]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Entity FilterNthEntity(Filter filter, int index)
|
|
||||||
{
|
|
||||||
return new Entity(filterSignatureToEntityIDs[filter.Signature][index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Entity FilterRandomEntity(Filter filter)
|
|
||||||
{
|
|
||||||
var randomIndex = RandomGenerator.Next(FilterCount(filter));
|
|
||||||
return new Entity(filterSignatureToEntityIDs[filter.Signature][randomIndex]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int FilterCount(Filter filter)
|
|
||||||
{
|
|
||||||
return filterSignatureToEntityIDs[filter.Signature].Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CheckFilter(FilterSignature filterSignature, int entityID)
|
|
||||||
{
|
|
||||||
foreach (var type in filterSignature.Included)
|
|
||||||
{
|
|
||||||
if (!Has(type, entityID))
|
|
||||||
{
|
|
||||||
filterSignatureToEntityIDs[filterSignature].Remove(entityID);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var type in filterSignature.Excluded)
|
|
||||||
{
|
|
||||||
if (Has(type, entityID))
|
|
||||||
{
|
|
||||||
filterSignatureToEntityIDs[filterSignature].Remove(entityID);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
foreach (var (type, storage) in storages)
|
|
||||||
{
|
|
||||||
if (storage.Has(entityID))
|
|
||||||
{
|
|
||||||
yield return storage.Debug_Get(entityID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<Entity> Debug_GetEntities(Type componentType)
|
|
||||||
{
|
|
||||||
return singleComponentFilters[componentType].Entities;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<Type> Debug_SearchComponentType(string typeString)
|
|
||||||
{
|
|
||||||
foreach (var type in storages.Keys)
|
|
||||||
{
|
|
||||||
if (type.ToString().ToLower().Contains(typeString.ToLower()))
|
|
||||||
{
|
|
||||||
yield return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,14 @@ namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
internal abstract class ComponentStorage
|
internal abstract class ComponentStorage
|
||||||
{
|
{
|
||||||
public abstract bool Has(int entityID);
|
|
||||||
public abstract bool Remove(int entityID);
|
public abstract bool Remove(int entityID);
|
||||||
public abstract object Debug_Get(int entityID);
|
|
||||||
public abstract ComponentStorageState CreateState();
|
public abstract ComponentStorageState CreateState();
|
||||||
public abstract void Save(ComponentStorageState state);
|
public abstract void Save(ComponentStorageState state);
|
||||||
public abstract void Load(ComponentStorageState state);
|
public abstract void Load(ComponentStorageState state);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
public abstract object Debug_Get(int entityID);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : unmanaged
|
internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : unmanaged
|
||||||
|
@ -26,22 +28,12 @@ namespace MoonTools.ECS
|
||||||
return nextID > 0;
|
return nextID > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Has(int entityID)
|
|
||||||
{
|
|
||||||
return entityIDToStorageIndex.ContainsKey(entityID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ref readonly TComponent Get(int entityID)
|
public ref readonly TComponent Get(int entityID)
|
||||||
{
|
{
|
||||||
return ref components[entityIDToStorageIndex[entityID]];
|
return ref components[entityIDToStorageIndex[entityID]];
|
||||||
}
|
}
|
||||||
|
|
||||||
public override object Debug_Get(int entityID)
|
public ref readonly TComponent GetFirst()
|
||||||
{
|
|
||||||
return components[entityIDToStorageIndex[entityID]];
|
|
||||||
}
|
|
||||||
|
|
||||||
public ref readonly TComponent Get()
|
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (nextID == 0)
|
if (nextID == 0)
|
||||||
|
@ -52,11 +44,8 @@ namespace MoonTools.ECS
|
||||||
return ref components[0];
|
return ref components[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the entity already had this component.
|
public void Set(int entityID, in TComponent component)
|
||||||
public bool Set(int entityID, in TComponent component)
|
|
||||||
{
|
{
|
||||||
bool result = true;
|
|
||||||
|
|
||||||
if (!entityIDToStorageIndex.ContainsKey(entityID))
|
if (!entityIDToStorageIndex.ContainsKey(entityID))
|
||||||
{
|
{
|
||||||
var index = nextID;
|
var index = nextID;
|
||||||
|
@ -70,13 +59,9 @@ namespace MoonTools.ECS
|
||||||
|
|
||||||
entityIDToStorageIndex[entityID] = index;
|
entityIDToStorageIndex[entityID] = index;
|
||||||
entityIDs[index] = entityID;
|
entityIDs[index] = entityID;
|
||||||
|
|
||||||
result = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
components[entityIDToStorageIndex[entityID]] = component;
|
components[entityIDToStorageIndex[entityID]] = component;
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the entity had this component.
|
// Returns true if the entity had this component.
|
||||||
|
@ -166,5 +151,12 @@ namespace MoonTools.ECS
|
||||||
|
|
||||||
nextID = state.Count;
|
nextID = state.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
public override object Debug_Get(int entityID)
|
||||||
|
{
|
||||||
|
return components[entityIDToStorageIndex[entityID]];
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,23 +9,40 @@ namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
public abstract class DebugSystem : System
|
public abstract class DebugSystem : System
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
|
private Dictionary<Type, Filter> singleComponentFilters = new Dictionary<Type, Filter>();
|
||||||
|
#endif
|
||||||
|
|
||||||
protected DebugSystem(World world) : base(world)
|
protected DebugSystem(World world) : base(world)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IEnumerable<object> Debug_GetAllComponents(Entity entity)
|
protected IEnumerable<object> Debug_GetAllComponents(Entity entity)
|
||||||
{
|
{
|
||||||
return ComponentDepot.Debug_GetAllComponents(entity.ID);
|
foreach (var typeIndex in EntityStorage.ComponentTypeIndices(entity.ID))
|
||||||
|
{
|
||||||
|
yield return ComponentDepot.Debug_Get(entity.ID, typeIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IEnumerable<Entity> Debug_GetEntities(Type componentType)
|
protected IEnumerable<Entity> Debug_GetEntities(Type componentType)
|
||||||
{
|
{
|
||||||
return ComponentDepot.Debug_GetEntities(componentType);
|
if (!singleComponentFilters.ContainsKey(componentType))
|
||||||
|
{
|
||||||
|
singleComponentFilters.Add(componentType, new Filter(FilterStorage, new HashSet<int>(ComponentTypeIndices.GetIndex(componentType)), new HashSet<int>()));
|
||||||
|
}
|
||||||
|
return singleComponentFilters[componentType].Entities;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IEnumerable<Type> Debug_SearchComponentType(string typeString)
|
protected IEnumerable<Type> Debug_SearchComponentType(string typeString)
|
||||||
{
|
{
|
||||||
return ComponentDepot.Debug_SearchComponentType(typeString);
|
foreach (var type in ComponentTypeIndices.Types)
|
||||||
|
{
|
||||||
|
if (type.ToString().ToLower().Contains(typeString.ToLower()))
|
||||||
|
{
|
||||||
|
yield return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,10 @@ namespace MoonTools.ECS
|
||||||
internal EntityStorage EntityStorage => World.EntityStorage;
|
internal EntityStorage EntityStorage => World.EntityStorage;
|
||||||
internal ComponentDepot ComponentDepot => World.ComponentDepot;
|
internal ComponentDepot ComponentDepot => World.ComponentDepot;
|
||||||
internal RelationDepot RelationDepot => World.RelationDepot;
|
internal RelationDepot RelationDepot => World.RelationDepot;
|
||||||
protected FilterBuilder FilterBuilder => new FilterBuilder(ComponentDepot);
|
protected FilterBuilder FilterBuilder => new FilterBuilder(FilterStorage, ComponentTypeIndices);
|
||||||
|
internal FilterStorage FilterStorage => World.FilterStorage;
|
||||||
|
internal TypeIndices ComponentTypeIndices => World.ComponentTypeIndices;
|
||||||
|
internal TypeIndices RelationTypeIndices => World.RelationTypeIndices;
|
||||||
|
|
||||||
public EntityComponentReader(World world)
|
public EntityComponentReader(World world)
|
||||||
{
|
{
|
||||||
|
@ -23,7 +26,8 @@ namespace MoonTools.ECS
|
||||||
|
|
||||||
protected bool Has<TComponent>(in Entity entity) where TComponent : unmanaged
|
protected bool Has<TComponent>(in Entity entity) where TComponent : unmanaged
|
||||||
{
|
{
|
||||||
return ComponentDepot.Has<TComponent>(entity.ID);
|
var storageIndex = ComponentTypeIndices.GetIndex<TComponent>();
|
||||||
|
return EntityStorage.HasComponent(entity.ID, storageIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool Some<TComponent>() where TComponent : unmanaged
|
protected bool Some<TComponent>() where TComponent : unmanaged
|
||||||
|
@ -38,7 +42,7 @@ namespace MoonTools.ECS
|
||||||
|
|
||||||
protected ref readonly TComponent GetSingleton<TComponent>() where TComponent : unmanaged
|
protected ref readonly TComponent GetSingleton<TComponent>() where TComponent : unmanaged
|
||||||
{
|
{
|
||||||
return ref ComponentDepot.Get<TComponent>();
|
return ref ComponentDepot.GetFirst<TComponent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Entity GetSingletonEntity<TComponent>() where TComponent : unmanaged
|
protected Entity GetSingletonEntity<TComponent>() where TComponent : unmanaged
|
||||||
|
|
|
@ -5,12 +5,19 @@ namespace MoonTools.ECS
|
||||||
internal class EntityStorage
|
internal class EntityStorage
|
||||||
{
|
{
|
||||||
private int nextID = 0;
|
private int nextID = 0;
|
||||||
|
// FIXME: why is this duplicated?
|
||||||
private readonly Stack<int> availableIDs = new Stack<int>();
|
private readonly Stack<int> availableIDs = new Stack<int>();
|
||||||
private readonly HashSet<int> availableIDHash = new HashSet<int>();
|
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 Entity Create()
|
public Entity Create()
|
||||||
{
|
{
|
||||||
return new Entity(NextID());
|
var entity = new Entity(NextID());
|
||||||
|
EntityToComponentTypeIndices.TryAdd(entity.ID, new HashSet<int>());
|
||||||
|
EntityToRelationTypeIndices.TryAdd(entity.ID, new HashSet<int>());
|
||||||
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Exists(in Entity entity)
|
public bool Exists(in Entity entity)
|
||||||
|
@ -20,29 +27,47 @@ namespace MoonTools.ECS
|
||||||
|
|
||||||
public void Destroy(in Entity entity)
|
public void Destroy(in Entity entity)
|
||||||
{
|
{
|
||||||
|
EntityToComponentTypeIndices[entity.ID].Clear();
|
||||||
|
EntityToRelationTypeIndices[entity.ID].Clear();
|
||||||
Release(entity.ID);
|
Release(entity.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save(EntityStorageState state)
|
// Returns true if the component is new.
|
||||||
|
public bool SetComponent(int entityID, int storageIndex)
|
||||||
{
|
{
|
||||||
state.NextID = nextID;
|
return EntityToComponentTypeIndices[entityID].Add(storageIndex);
|
||||||
state.availableIDs.Clear();
|
|
||||||
foreach (var id in availableIDs)
|
|
||||||
{
|
|
||||||
state.availableIDs.Add(id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Load(EntityStorageState state)
|
public bool HasComponent(int entityID, int storageIndex)
|
||||||
{
|
{
|
||||||
nextID = state.NextID;
|
return EntityToComponentTypeIndices[entityID].Contains(storageIndex);
|
||||||
availableIDs.Clear();
|
}
|
||||||
availableIDHash.Clear();
|
|
||||||
foreach (var id in state.availableIDs)
|
// Returns true if the component existed.
|
||||||
{
|
public bool RemoveComponent(int entityID, int storageIndex)
|
||||||
availableIDs.Push(id);
|
{
|
||||||
availableIDHash.Add(id);
|
return EntityToComponentTypeIndices[entityID].Remove(storageIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddRelation(int entityID, int relationIndex)
|
||||||
|
{
|
||||||
|
EntityToRelationTypeIndices[entityID].Add(relationIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveRelation(int entityId, int relationIndex)
|
||||||
|
{
|
||||||
|
EntityToRelationTypeIndices[entityId].Remove(relationIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: should these ints be ID types?
|
||||||
|
public IEnumerable<int> ComponentTypeIndices(int entityID)
|
||||||
|
{
|
||||||
|
return EntityToComponentTypeIndices[entityID];
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<int> RelationTypeIndices(int entityID)
|
||||||
|
{
|
||||||
|
return EntityToRelationTypeIndices[entityID];
|
||||||
}
|
}
|
||||||
|
|
||||||
private int NextID()
|
private int NextID()
|
||||||
|
|
|
@ -1,27 +1,26 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MoonTools.ECS
|
namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
public class Filter
|
public class Filter
|
||||||
{
|
{
|
||||||
internal FilterSignature Signature;
|
internal FilterSignature Signature;
|
||||||
private ComponentDepot ComponentDepot;
|
private FilterStorage FilterStorage;
|
||||||
|
|
||||||
internal Filter(ComponentDepot componentDepot, HashSet<Type> included, HashSet<Type> excluded)
|
internal Filter(FilterStorage filterStorage, HashSet<int> included, HashSet<int> excluded)
|
||||||
{
|
{
|
||||||
ComponentDepot = componentDepot;
|
FilterStorage = filterStorage;
|
||||||
Signature = new FilterSignature(included, excluded);
|
Signature = new FilterSignature(included, excluded);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Entity> Entities => ComponentDepot.FilterEntities(this);
|
public IEnumerable<Entity> Entities => FilterStorage.FilterEntities(Signature);
|
||||||
public IEnumerable<Entity> EntitiesInRandomOrder => ComponentDepot.FilterEntitiesRandom(this);
|
public IEnumerable<Entity> EntitiesInRandomOrder => FilterStorage.FilterEntitiesRandom(Signature);
|
||||||
public Entity RandomEntity => ComponentDepot.FilterRandomEntity(this);
|
public Entity RandomEntity => FilterStorage.FilterRandomEntity(Signature);
|
||||||
|
|
||||||
public int Count => ComponentDepot.FilterCount(this);
|
public int Count => FilterStorage.FilterCount(Signature);
|
||||||
public bool Empty => Count == 0;
|
public bool Empty => Count == 0;
|
||||||
|
|
||||||
// WARNING: this WILL crash if the index is out of range!
|
// WARNING: this WILL crash if the index is out of range!
|
||||||
public Entity NthEntity(int index) => ComponentDepot.FilterNthEntity(this, index);
|
public Entity NthEntity(int index) => FilterStorage.FilterNthEntity(Signature, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,41 +5,42 @@ namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
public struct FilterBuilder
|
public struct FilterBuilder
|
||||||
{
|
{
|
||||||
private ComponentDepot ComponentDepot;
|
private TypeIndices ComponentTypeIndices;
|
||||||
private HashSet<Type> Included;
|
private FilterStorage FilterStorage;
|
||||||
private HashSet<Type> Excluded;
|
private HashSet<int> Included;
|
||||||
|
private HashSet<int> Excluded;
|
||||||
|
|
||||||
internal FilterBuilder(ComponentDepot componentDepot)
|
internal FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices)
|
||||||
{
|
{
|
||||||
ComponentDepot = componentDepot;
|
FilterStorage = filterStorage;
|
||||||
Included = new HashSet<Type>();
|
ComponentTypeIndices = componentTypeIndices;
|
||||||
Excluded = new HashSet<Type>();
|
Included = new HashSet<int>();
|
||||||
|
Excluded = new HashSet<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private FilterBuilder(ComponentDepot componentDepot, HashSet<Type> included, HashSet<Type> excluded)
|
private FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices, HashSet<int> included, HashSet<int> excluded)
|
||||||
{
|
{
|
||||||
ComponentDepot = componentDepot;
|
FilterStorage = filterStorage;
|
||||||
|
ComponentTypeIndices = componentTypeIndices;
|
||||||
Included = included;
|
Included = included;
|
||||||
Excluded = excluded;
|
Excluded = excluded;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FilterBuilder Include<TComponent>() where TComponent : unmanaged
|
public FilterBuilder Include<TComponent>() where TComponent : unmanaged
|
||||||
{
|
{
|
||||||
ComponentDepot.Register<TComponent>();
|
Included.Add(ComponentTypeIndices.GetIndex<TComponent>());
|
||||||
Included.Add(typeof(TComponent));
|
return new FilterBuilder(FilterStorage, ComponentTypeIndices, Included, Excluded);
|
||||||
return new FilterBuilder(ComponentDepot, Included, Excluded);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public FilterBuilder Exclude<TComponent>() where TComponent : unmanaged
|
public FilterBuilder Exclude<TComponent>() where TComponent : unmanaged
|
||||||
{
|
{
|
||||||
ComponentDepot.Register<TComponent>();
|
Excluded.Add(ComponentTypeIndices.GetIndex<TComponent>());
|
||||||
Excluded.Add(typeof(TComponent));
|
return new FilterBuilder(FilterStorage, ComponentTypeIndices, Included, Excluded);
|
||||||
return new FilterBuilder(ComponentDepot, Included, Excluded);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Filter Build()
|
public Filter Build()
|
||||||
{
|
{
|
||||||
return ComponentDepot.CreateFilter(Included, Excluded);
|
return FilterStorage.CreateFilter(Included, Excluded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,10 @@ namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
public struct FilterSignature
|
public struct FilterSignature
|
||||||
{
|
{
|
||||||
private const int HASH_FACTOR = 97;
|
public readonly HashSet<int> Included;
|
||||||
|
public readonly HashSet<int> Excluded;
|
||||||
|
|
||||||
public readonly HashSet<Type> Included;
|
public FilterSignature(HashSet<int> included, HashSet<int> excluded)
|
||||||
public readonly HashSet<Type> Excluded;
|
|
||||||
|
|
||||||
public FilterSignature(HashSet<Type> included, HashSet<Type> excluded)
|
|
||||||
{
|
{
|
||||||
Included = included;
|
Included = included;
|
||||||
Excluded = excluded;
|
Excluded = excluded;
|
||||||
|
@ -33,19 +31,19 @@ namespace MoonTools.ECS
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
{
|
{
|
||||||
int result = 1;
|
var hashcode = 1;
|
||||||
|
|
||||||
foreach (var type in Included)
|
foreach (var type in Included)
|
||||||
{
|
{
|
||||||
result *= HASH_FACTOR + GuidToInt(type.GUID);
|
hashcode = HashCode.Combine(hashcode, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Is there a way to avoid collisions when this is the same set as included?
|
|
||||||
foreach (var type in Excluded)
|
foreach (var type in Excluded)
|
||||||
{
|
{
|
||||||
result *= HASH_FACTOR + GuidToInt(type.GUID);
|
hashcode = HashCode.Combine(hashcode, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return hashcode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MoonTools.ECS
|
||||||
|
{
|
||||||
|
internal class FilterStorage
|
||||||
|
{
|
||||||
|
private EntityStorage EntityStorage;
|
||||||
|
private TypeIndices ComponentTypeIndices;
|
||||||
|
private Dictionary<FilterSignature, IndexableSet<int>> filterSignatureToEntityIDs = new Dictionary<FilterSignature, IndexableSet<int>>();
|
||||||
|
private Dictionary<int, HashSet<FilterSignature>> typeToFilterSignatures = new Dictionary<int, HashSet<FilterSignature>>();
|
||||||
|
|
||||||
|
public FilterStorage(EntityStorage entityStorage, TypeIndices componentTypeIndices)
|
||||||
|
{
|
||||||
|
EntityStorage = entityStorage;
|
||||||
|
ComponentTypeIndices = componentTypeIndices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Filter CreateFilter(HashSet<int> included, HashSet<int> excluded)
|
||||||
|
{
|
||||||
|
var filterSignature = new FilterSignature(included, excluded);
|
||||||
|
if (!filterSignatureToEntityIDs.ContainsKey(filterSignature))
|
||||||
|
{
|
||||||
|
filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet<int>());
|
||||||
|
|
||||||
|
foreach (var type in included)
|
||||||
|
{
|
||||||
|
if (!typeToFilterSignatures.ContainsKey(type))
|
||||||
|
{
|
||||||
|
typeToFilterSignatures.Add(type, new HashSet<FilterSignature>());
|
||||||
|
}
|
||||||
|
|
||||||
|
typeToFilterSignatures[type].Add(filterSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var type in excluded)
|
||||||
|
{
|
||||||
|
if (!typeToFilterSignatures.ContainsKey(type))
|
||||||
|
{
|
||||||
|
typeToFilterSignatures.Add(type, new HashSet<FilterSignature>());
|
||||||
|
}
|
||||||
|
|
||||||
|
typeToFilterSignatures[type].Add(filterSignature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Filter(this, included, excluded);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Entity> FilterEntities(FilterSignature filterSignature)
|
||||||
|
{
|
||||||
|
foreach (var id in filterSignatureToEntityIDs[filterSignature])
|
||||||
|
{
|
||||||
|
yield return new Entity(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Entity> FilterEntitiesRandom(FilterSignature filterSignature)
|
||||||
|
{
|
||||||
|
foreach (var index in RandomGenerator.LinearCongruentialGenerator(FilterCount(filterSignature)))
|
||||||
|
{
|
||||||
|
yield return new Entity(filterSignatureToEntityIDs[filterSignature][index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity FilterNthEntity(FilterSignature filterSignature, int index)
|
||||||
|
{
|
||||||
|
return new Entity(filterSignatureToEntityIDs[filterSignature][index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity FilterRandomEntity(FilterSignature filterSignature)
|
||||||
|
{
|
||||||
|
var randomIndex = RandomGenerator.Next(FilterCount(filterSignature));
|
||||||
|
return new Entity(filterSignatureToEntityIDs[filterSignature][randomIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int FilterCount(FilterSignature filterSignature)
|
||||||
|
{
|
||||||
|
return filterSignatureToEntityIDs[filterSignature].Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Check(int entityID, int componentTypeIndex)
|
||||||
|
{
|
||||||
|
if (typeToFilterSignatures.ContainsKey(componentTypeIndex))
|
||||||
|
{
|
||||||
|
foreach (var filterSignature in typeToFilterSignatures[componentTypeIndex])
|
||||||
|
{
|
||||||
|
CheckFilter(entityID, filterSignature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Check<TComponent>(int entityID) where TComponent : unmanaged
|
||||||
|
{
|
||||||
|
Check(entityID, ComponentTypeIndices.GetIndex<TComponent>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckFilter(int entityID, FilterSignature filterSignature)
|
||||||
|
{
|
||||||
|
foreach (var type in filterSignature.Included)
|
||||||
|
{
|
||||||
|
if (!EntityStorage.HasComponent(entityID, type))
|
||||||
|
{
|
||||||
|
filterSignatureToEntityIDs[filterSignature].Remove(entityID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var type in filterSignature.Excluded)
|
||||||
|
{
|
||||||
|
if (EntityStorage.HasComponent(entityID, type))
|
||||||
|
{
|
||||||
|
filterSignatureToEntityIDs[filterSignature].Remove(entityID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterSignatureToEntityIDs[filterSignature].Add(entityID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveEntity(int entityID, int componentTypeIndex)
|
||||||
|
{
|
||||||
|
if (typeToFilterSignatures.ContainsKey(componentTypeIndex))
|
||||||
|
{
|
||||||
|
foreach (var filterSignature in typeToFilterSignatures[componentTypeIndex])
|
||||||
|
{
|
||||||
|
filterSignatureToEntityIDs[filterSignature].Remove(entityID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,21 +6,34 @@ namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
internal class RelationDepot
|
internal class RelationDepot
|
||||||
{
|
{
|
||||||
private Dictionary<Type, RelationStorage> storages = new Dictionary<Type, RelationStorage>();
|
private TypeIndices RelationTypeIndices;
|
||||||
|
private RelationStorage[] storages = new RelationStorage[256];
|
||||||
|
|
||||||
private void Register<TRelationKind>() where TRelationKind : unmanaged
|
public RelationDepot(TypeIndices relationTypeIndices)
|
||||||
{
|
{
|
||||||
if (!storages.ContainsKey(typeof(TRelationKind)))
|
RelationTypeIndices = relationTypeIndices;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Register<TRelationKind>(int index) where TRelationKind : unmanaged
|
||||||
|
{
|
||||||
|
if (index >= storages.Length)
|
||||||
{
|
{
|
||||||
storages.Add(typeof(TRelationKind), new RelationStorage<TRelationKind>());
|
Array.Resize(ref storages, storages.Length * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storages[index] = new RelationStorage<TRelationKind>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private RelationStorage<TRelationKind> Lookup<TRelationKind>() where TRelationKind : unmanaged
|
private RelationStorage<TRelationKind> Lookup<TRelationKind>() where TRelationKind : unmanaged
|
||||||
{
|
{
|
||||||
Register<TRelationKind>();
|
var storageIndex = RelationTypeIndices.GetIndex<TRelationKind>();
|
||||||
return (RelationStorage<TRelationKind>) storages[typeof(TRelationKind)];
|
// TODO: is there some way to avoid this null check?
|
||||||
|
if (storages[storageIndex] == null)
|
||||||
|
{
|
||||||
|
Register<TRelationKind>(storageIndex);
|
||||||
|
}
|
||||||
|
return (RelationStorage<TRelationKind>) storages[storageIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Set<TRelationKind>(Relation relation, TRelationKind relationData) where TRelationKind : unmanaged
|
public void Set<TRelationKind>(Relation relation, TRelationKind relationData) where TRelationKind : unmanaged
|
||||||
|
@ -28,9 +41,9 @@ namespace MoonTools.ECS
|
||||||
Lookup<TRelationKind>().Set(relation, relationData);
|
Lookup<TRelationKind>().Set(relation, relationData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove<TRelationKind>(Relation relation) where TRelationKind : unmanaged
|
public (bool, bool) Remove<TRelationKind>(Relation relation) where TRelationKind : unmanaged
|
||||||
{
|
{
|
||||||
Lookup<TRelationKind>().Remove(relation);
|
return Lookup<TRelationKind>().Remove(relation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnrelateAll<TRelationKind>(int entityID) where TRelationKind : unmanaged
|
public void UnrelateAll<TRelationKind>(int entityID) where TRelationKind : unmanaged
|
||||||
|
@ -38,13 +51,9 @@ namespace MoonTools.ECS
|
||||||
Lookup<TRelationKind>().UnrelateAll(entityID);
|
Lookup<TRelationKind>().UnrelateAll(entityID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: optimize this
|
public void UnrelateAll(int entityID, int relationStorageIndex)
|
||||||
public void OnEntityDestroy(int entityID)
|
|
||||||
{
|
{
|
||||||
foreach (var storage in storages.Values)
|
storages[relationStorageIndex].UnrelateAll(entityID);
|
||||||
{
|
|
||||||
storage.OnEntityDestroy(entityID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<(Entity, Entity, TRelationKind)> Relations<TRelationKind>() where TRelationKind : unmanaged
|
public IEnumerable<(Entity, Entity, TRelationKind)> Relations<TRelationKind>() where TRelationKind : unmanaged
|
||||||
|
@ -96,26 +105,5 @@ namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
return Lookup<TRelationKind>().InRelationCount(entityID);
|
return Lookup<TRelationKind>().InRelationCount(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace MoonTools.ECS
|
||||||
public abstract RelationStorageState CreateState();
|
public abstract RelationStorageState CreateState();
|
||||||
public abstract void Save(RelationStorageState state);
|
public abstract void Save(RelationStorageState state);
|
||||||
public abstract void Load(RelationStorageState state);
|
public abstract void Load(RelationStorageState state);
|
||||||
public abstract void OnEntityDestroy(int entityID);
|
public abstract void UnrelateAll(int entityID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relation is the two entities, A related to B.
|
// Relation is the two entities, A related to B.
|
||||||
|
@ -144,16 +144,27 @@ namespace MoonTools.ECS
|
||||||
return inRelations.ContainsKey(entityID) ? inRelations[entityID].Count : 0;
|
return inRelations.ContainsKey(entityID) ? inRelations[entityID].Count : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Remove(Relation relation)
|
public (bool, bool) Remove(Relation relation)
|
||||||
{
|
{
|
||||||
|
var aEmpty = false;
|
||||||
|
var bEmpty = false;
|
||||||
|
|
||||||
if (outRelations.ContainsKey(relation.A.ID))
|
if (outRelations.ContainsKey(relation.A.ID))
|
||||||
{
|
{
|
||||||
outRelations[relation.A.ID].Remove(relation.B.ID);
|
outRelations[relation.A.ID].Remove(relation.B.ID);
|
||||||
|
if (outRelations[relation.A.ID].Count == 0)
|
||||||
|
{
|
||||||
|
aEmpty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inRelations.ContainsKey(relation.B.ID))
|
if (inRelations.ContainsKey(relation.B.ID))
|
||||||
{
|
{
|
||||||
inRelations[relation.B.ID].Remove(relation.A.ID);
|
inRelations[relation.B.ID].Remove(relation.A.ID);
|
||||||
|
if (inRelations[relation.B.ID].Count == 0)
|
||||||
|
{
|
||||||
|
bEmpty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (indices.ContainsKey(relation))
|
if (indices.ContainsKey(relation))
|
||||||
|
@ -172,13 +183,12 @@ namespace MoonTools.ECS
|
||||||
|
|
||||||
count -= 1;
|
count -= 1;
|
||||||
indices.Remove(relation);
|
indices.Remove(relation);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return (aEmpty, bEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnrelateAll(int entityID)
|
public override void UnrelateAll(int entityID)
|
||||||
{
|
{
|
||||||
if (outRelations.ContainsKey(entityID))
|
if (outRelations.ContainsKey(entityID))
|
||||||
{
|
{
|
||||||
|
@ -203,11 +213,6 @@ namespace MoonTools.ECS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnEntityDestroy(int entityID)
|
|
||||||
{
|
|
||||||
UnrelateAll(entityID);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IndexableSet<int> AcquireHashSetFromPool()
|
private IndexableSet<int> AcquireHashSetFromPool()
|
||||||
{
|
{
|
||||||
if (listPool.Count == 0)
|
if (listPool.Count == 0)
|
||||||
|
|
|
@ -6,5 +6,8 @@ namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
public int NextID;
|
public int NextID;
|
||||||
public List<int> availableIDs = new List<int>();
|
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>>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,12 +25,21 @@ namespace MoonTools.ECS
|
||||||
throw new ArgumentException("This entity is not valid!");
|
throw new ArgumentException("This entity is not valid!");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex<TComponent>()))
|
||||||
|
{
|
||||||
|
FilterStorage.Check<TComponent>(entity.ID);
|
||||||
|
}
|
||||||
|
|
||||||
ComponentDepot.Set<TComponent>(entity.ID, component);
|
ComponentDepot.Set<TComponent>(entity.ID, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void Remove<TComponent>(in Entity entity) where TComponent : unmanaged
|
protected void Remove<TComponent>(in Entity entity) where TComponent : unmanaged
|
||||||
{
|
{
|
||||||
ComponentDepot.Remove<TComponent>(entity.ID);
|
if (EntityStorage.RemoveComponent(entity.ID, ComponentTypeIndices.GetIndex<TComponent>()))
|
||||||
|
{
|
||||||
|
ComponentDepot.Remove<TComponent>(entity.ID);
|
||||||
|
FilterStorage.Check<TComponent>(entity.ID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ReadOnlySpan<TMessage> ReadMessages<TMessage>() where TMessage : unmanaged
|
protected ReadOnlySpan<TMessage> ReadMessages<TMessage>() where TMessage : unmanaged
|
||||||
|
@ -71,23 +80,45 @@ namespace MoonTools.ECS
|
||||||
protected void Relate<TRelationKind>(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged
|
protected void Relate<TRelationKind>(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged
|
||||||
{
|
{
|
||||||
RelationDepot.Set<TRelationKind>(new Relation(entityA, entityB), relationData);
|
RelationDepot.Set<TRelationKind>(new Relation(entityA, entityB), relationData);
|
||||||
|
var relationTypeIndex = RelationTypeIndices.GetIndex<TRelationKind>();
|
||||||
|
EntityStorage.AddRelation(entityA.ID, relationTypeIndex);
|
||||||
|
EntityStorage.AddRelation(entityB.ID, relationTypeIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void Unrelate<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged
|
protected void Unrelate<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged
|
||||||
{
|
{
|
||||||
RelationDepot.Remove<TRelationKind>(new Relation(entityA, entityB));
|
var (aEmpty, bEmpty) = RelationDepot.Remove<TRelationKind>(new Relation(entityA, entityB));
|
||||||
|
|
||||||
|
if (aEmpty)
|
||||||
|
{
|
||||||
|
EntityStorage.RemoveRelation(entityA.ID, RelationTypeIndices.GetIndex<TRelationKind>());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bEmpty)
|
||||||
|
{
|
||||||
|
EntityStorage.RemoveRelation(entityB.ID, RelationTypeIndices.GetIndex<TRelationKind>());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void UnrelateAll<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
|
protected void UnrelateAll<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
|
||||||
{
|
{
|
||||||
RelationDepot.UnrelateAll<TRelationKind>(entity.ID);
|
RelationDepot.UnrelateAll<TRelationKind>(entity.ID);
|
||||||
|
EntityStorage.RemoveRelation(entity.ID, RelationTypeIndices.GetIndex<TRelationKind>());
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: this is insanely inefficient
|
|
||||||
protected void Destroy(in Entity entity)
|
protected void Destroy(in Entity entity)
|
||||||
{
|
{
|
||||||
ComponentDepot.OnEntityDestroy(entity.ID);
|
foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID))
|
||||||
RelationDepot.OnEntityDestroy(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);
|
EntityStorage.Destroy(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace MoonTools.ECS
|
||||||
|
{
|
||||||
|
public struct Template
|
||||||
|
{
|
||||||
|
public int ID { get; }
|
||||||
|
|
||||||
|
internal Template(int id)
|
||||||
|
{
|
||||||
|
ID = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
namespace MoonTools.ECS
|
||||||
|
{
|
||||||
|
public class TemplateStorage
|
||||||
|
{
|
||||||
|
private int nextID = 0;
|
||||||
|
|
||||||
|
public Template Create()
|
||||||
|
{
|
||||||
|
return new Template(NextID());
|
||||||
|
}
|
||||||
|
|
||||||
|
private int NextID()
|
||||||
|
{
|
||||||
|
var id = nextID;
|
||||||
|
nextID += 1;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MoonTools.ECS
|
||||||
|
{
|
||||||
|
public class TypeIndices
|
||||||
|
{
|
||||||
|
Dictionary<Type, int> TypeToIndex = new Dictionary<Type, int>();
|
||||||
|
int nextID = 0;
|
||||||
|
|
||||||
|
public int GetIndex<T>() where T : unmanaged
|
||||||
|
{
|
||||||
|
if (!TypeToIndex.ContainsKey(typeof(T)))
|
||||||
|
{
|
||||||
|
TypeToIndex.Add(typeof(T), nextID);
|
||||||
|
nextID += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TypeToIndex[typeof(T)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetIndex(Type type)
|
||||||
|
{
|
||||||
|
return TypeToIndex[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
public IEnumerable<Type> Types => TypeToIndex.Keys;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
64
src/World.cs
64
src/World.cs
|
@ -2,10 +2,25 @@
|
||||||
{
|
{
|
||||||
public class World
|
public class World
|
||||||
{
|
{
|
||||||
|
internal readonly TypeIndices ComponentTypeIndices = new TypeIndices();
|
||||||
|
internal readonly TypeIndices RelationTypeIndices = new TypeIndices();
|
||||||
internal readonly EntityStorage EntityStorage = new EntityStorage();
|
internal readonly EntityStorage EntityStorage = new EntityStorage();
|
||||||
internal readonly ComponentDepot ComponentDepot = new ComponentDepot();
|
internal readonly ComponentDepot ComponentDepot;
|
||||||
internal readonly MessageDepot MessageDepot = new MessageDepot();
|
internal readonly MessageDepot MessageDepot = new MessageDepot();
|
||||||
internal readonly RelationDepot RelationDepot = new RelationDepot();
|
internal readonly RelationDepot RelationDepot;
|
||||||
|
internal readonly FilterStorage FilterStorage;
|
||||||
|
|
||||||
|
/*
|
||||||
|
internal readonly TemplateStorage TemplateStorage = new TemplateStorage();
|
||||||
|
internal readonly ComponentDepot TemplateComponentDepot = new ComponentDepot();
|
||||||
|
*/
|
||||||
|
|
||||||
|
public World()
|
||||||
|
{
|
||||||
|
ComponentDepot = new ComponentDepot(ComponentTypeIndices);
|
||||||
|
RelationDepot = new RelationDepot(RelationTypeIndices);
|
||||||
|
FilterStorage = new FilterStorage(EntityStorage, ComponentTypeIndices);
|
||||||
|
}
|
||||||
|
|
||||||
public Entity CreateEntity()
|
public Entity CreateEntity()
|
||||||
{
|
{
|
||||||
|
@ -14,7 +29,31 @@
|
||||||
|
|
||||||
public void Set<TComponent>(Entity entity, in TComponent component) where TComponent : unmanaged
|
public void Set<TComponent>(Entity entity, in TComponent component) where TComponent : unmanaged
|
||||||
{
|
{
|
||||||
ComponentDepot.Set(entity.ID, component);
|
if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex<TComponent>()))
|
||||||
|
{
|
||||||
|
FilterStorage.Check<TComponent>(entity.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
ComponentDepot.Set<TComponent>(entity.ID, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
public Template CreateTemplate()
|
||||||
|
{
|
||||||
|
return TemplateStorage.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Set<TComponent>(Template template, in TComponent component) where TComponent : unmanaged
|
||||||
|
{
|
||||||
|
TemplateComponentDepot.Set(template.ID, component);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
public Entity Instantiate(Template template)
|
||||||
|
{
|
||||||
|
var entity = EntityStorage.Create();
|
||||||
|
|
||||||
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Send<TMessage>(in TMessage message) where TMessage : unmanaged
|
public void Send<TMessage>(in TMessage message) where TMessage : unmanaged
|
||||||
|
@ -27,28 +66,9 @@
|
||||||
MessageDepot.Clear();
|
MessageDepot.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisableSerialization<TComponent>() where TComponent : unmanaged
|
|
||||||
{
|
|
||||||
ComponentDepot.DisableSerialization<TComponent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public WorldState CreateState()
|
public WorldState CreateState()
|
||||||
{
|
{
|
||||||
return new WorldState();
|
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