Storage refactor, snapshot system, experimental template system (#3)
- Major storage refactor to improve performance and reduce garbage collection - Snapshot system to facilitate rollback implementation - Experimental template system to instantiate entities based on a template. Use at your own risk, this may change significantly or be removed! Reviewed-on: #3pull/4/head
parent
71a95cb2d7
commit
f69d132a5e
|
@ -6,41 +6,36 @@ 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
|
ComponentTypeIndices = componentTypeIndices;
|
||||||
private Dictionary<Type, Filter> singleComponentFilters = new Dictionary<Type, Filter>();
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
private HashSet<Type> TypesWithDisabledSerialization = new HashSet<Type>();
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal void Register<TComponent>() where TComponent : unmanaged
|
internal void Register<TComponent>(int index) where TComponent : unmanaged
|
||||||
{
|
{
|
||||||
if (!storages.ContainsKey(typeof(TComponent)))
|
if (index >= storages.Length)
|
||||||
{
|
{
|
||||||
storages.Add(typeof(TComponent), new ComponentStorage<TComponent>());
|
Array.Resize(ref storages, storages.Length * 2);
|
||||||
#if DEBUG
|
|
||||||
singleComponentFilters.Add(typeof(TComponent), CreateFilter(new HashSet<Type>() { typeof(TComponent) }, new HashSet<Type>()));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ComponentStorage Lookup(Type type)
|
storages[index] = new ComponentStorage<TComponent>();
|
||||||
{
|
|
||||||
return storages[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 +43,19 @@ 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
|
public ref readonly TComponent GetFirst<TComponent>() where TComponent : unmanaged
|
||||||
{
|
{
|
||||||
return ref Lookup<TComponent>().Get();
|
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,208 +68,61 @@ 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < ComponentTypeIndices.Count; i += 1)
|
||||||
|
{
|
||||||
|
if (storages[i] != null)
|
||||||
|
{
|
||||||
|
storages[i].Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: is there some way to optimize this without complicating serialization?
|
// these methods used to implement snapshots, templates, and debugging
|
||||||
public void OnEntityDestroy(int entityID)
|
|
||||||
|
internal unsafe void* UntypedGet(int entityID, int componentTypeIndex)
|
||||||
{
|
{
|
||||||
foreach (var type in storages.Keys)
|
return storages[componentTypeIndex].UntypedGet(entityID);
|
||||||
{
|
|
||||||
Remove(type, entityID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Filter CreateFilter(HashSet<Type> included, HashSet<Type> excluded)
|
internal unsafe void Set(int entityID, int componentTypeIndex, void* component)
|
||||||
{
|
{
|
||||||
var filterSignature = new FilterSignature(included, excluded);
|
storages[componentTypeIndex].Set(entityID, component);
|
||||||
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);
|
public void CreateMissingStorages(ComponentDepot other)
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var type in excluded)
|
|
||||||
{
|
{
|
||||||
if (!typeToFilterSignatures.ContainsKey(type))
|
for (var i = 0; i < ComponentTypeIndices.Count; i += 1)
|
||||||
{
|
{
|
||||||
typeToFilterSignatures.Add(type, new HashSet<FilterSignature>());
|
if (storages[i] == null && other.storages[i] != null)
|
||||||
}
|
|
||||||
|
|
||||||
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])
|
storages[i] = other.storages[i].CreateStorage();
|
||||||
{
|
|
||||||
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)
|
// this method is used to iterate components of an entity, only for use with a debug inspector
|
||||||
{
|
|
||||||
foreach (var (type, storageState) in state.StorageStates)
|
|
||||||
{
|
|
||||||
storages[type].Load(storageState);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (signature, setState) in state.FilterStates)
|
|
||||||
{
|
|
||||||
filterSignatureToEntityIDs[signature].Load(setState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
public IEnumerable<object> Debug_GetAllComponents(int entityID)
|
public object Debug_Get(int entityID, int componentTypeIndex)
|
||||||
{
|
{
|
||||||
foreach (var (type, storage) in storages)
|
return storages[componentTypeIndex].Debug_Get(entityID);
|
||||||
{
|
|
||||||
if (storage.Has(entityID))
|
|
||||||
{
|
|
||||||
yield return storage.Debug_Get(entityID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Entity> Debug_GetEntities(Type componentType)
|
public IEnumerable<int> Debug_GetEntityIDs(int componentTypeIndex)
|
||||||
{
|
{
|
||||||
return singleComponentFilters[componentType].Entities;
|
return storages[componentTypeIndex].Debug_GetEntityIDs();
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<Type> Debug_SearchComponentType(string typeString)
|
|
||||||
{
|
|
||||||
foreach (var type in storages.Keys)
|
|
||||||
{
|
|
||||||
if (type.ToString().ToLower().Contains(typeString.ToLower()))
|
|
||||||
{
|
|
||||||
yield return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MoonTools.ECS
|
namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
internal abstract class ComponentStorage
|
internal abstract class ComponentStorage
|
||||||
{
|
{
|
||||||
public abstract bool Has(int entityID);
|
internal abstract unsafe void Set(int entityID, void* component);
|
||||||
public abstract bool Remove(int entityID);
|
public abstract bool Remove(int entityID);
|
||||||
public abstract object Debug_Get(int entityID);
|
public abstract void Clear();
|
||||||
public abstract ComponentStorageState CreateState();
|
|
||||||
public abstract void Save(ComponentStorageState state);
|
// used for debugging and template instantiation
|
||||||
public abstract void Load(ComponentStorageState state);
|
internal abstract unsafe void* UntypedGet(int entityID);
|
||||||
|
// used to create correctly typed storage on snapshot
|
||||||
|
public abstract ComponentStorage CreateStorage();
|
||||||
|
#if DEBUG
|
||||||
|
internal abstract object Debug_Get(int entityID);
|
||||||
|
internal abstract IEnumerable<int> Debug_GetEntityIDs();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : unmanaged
|
internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : unmanaged
|
||||||
|
@ -26,22 +31,20 @@ 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)
|
internal override unsafe void* UntypedGet(int entityID)
|
||||||
{
|
{
|
||||||
return components[entityIDToStorageIndex[entityID]];
|
fixed (void* p = &components[entityIDToStorageIndex[entityID]])
|
||||||
|
{
|
||||||
|
return p;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref readonly TComponent Get()
|
public ref readonly TComponent GetFirst()
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (nextID == 0)
|
if (nextID == 0)
|
||||||
|
@ -52,11 +55,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,21 +70,21 @@ 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;
|
internal override unsafe void Set(int entityID, void* component)
|
||||||
|
{
|
||||||
|
Set(entityID, *((TComponent*) component));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the entity had this component.
|
// Returns true if the entity had this component.
|
||||||
public override bool Remove(int entityID)
|
public override bool Remove(int entityID)
|
||||||
{
|
{
|
||||||
if (entityIDToStorageIndex.ContainsKey(entityID))
|
if (entityIDToStorageIndex.TryGetValue(entityID, out int storageIndex))
|
||||||
{
|
{
|
||||||
var storageIndex = entityIDToStorageIndex[entityID];
|
|
||||||
entityIDToStorageIndex.Remove(entityID);
|
entityIDToStorageIndex.Remove(entityID);
|
||||||
|
|
||||||
var lastElementIndex = nextID - 1;
|
var lastElementIndex = nextID - 1;
|
||||||
|
@ -106,7 +106,7 @@ namespace MoonTools.ECS
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear()
|
public override void Clear()
|
||||||
{
|
{
|
||||||
nextID = 0;
|
nextID = 0;
|
||||||
entityIDToStorageIndex.Clear();
|
entityIDToStorageIndex.Clear();
|
||||||
|
@ -128,43 +128,21 @@ namespace MoonTools.ECS
|
||||||
return new Entity(entityIDs[0]);
|
return new Entity(entityIDs[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ComponentStorageState CreateState()
|
public override ComponentStorage<TComponent> CreateStorage()
|
||||||
{
|
{
|
||||||
return ComponentStorageState.Create<TComponent>(nextID);
|
return new ComponentStorage<TComponent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Save(ComponentStorageState state)
|
#if DEBUG
|
||||||
|
internal override object Debug_Get(int entityID)
|
||||||
{
|
{
|
||||||
ReadOnlySpan<byte> entityIDBytes = MemoryMarshal.Cast<int, byte>(new ReadOnlySpan<int>(entityIDs, 0, nextID));
|
return components[entityIDToStorageIndex[entityID]];
|
||||||
|
}
|
||||||
|
|
||||||
if (entityIDBytes.Length > state.EntityIDs.Length)
|
internal override IEnumerable<int> Debug_GetEntityIDs()
|
||||||
{
|
{
|
||||||
Array.Resize(ref state.EntityIDs, entityIDBytes.Length);
|
return entityIDToStorageIndex.Keys;
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,19 +13,31 @@ namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IEnumerable<object> Debug_GetAllComponents(Entity entity)
|
protected IEnumerable<dynamic> 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);
|
foreach (var entityID in ComponentDepot.Debug_GetEntityIDs(ComponentTypeIndices.GetIndex(componentType)))
|
||||||
|
{
|
||||||
|
yield return new Entity(entityID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MoonTools.ECS
|
||||||
|
{
|
||||||
|
public class DynamicArray<T> where T : unmanaged
|
||||||
|
{
|
||||||
|
private T[] Array;
|
||||||
|
public int Count { get; private set; }
|
||||||
|
|
||||||
|
public Span<T> ToSpan() => new Span<T>(Array, 0, Count);
|
||||||
|
public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(Array, 0, Count));
|
||||||
|
|
||||||
|
public DynamicArray(int capacity = 16)
|
||||||
|
{
|
||||||
|
Array = new T[capacity];
|
||||||
|
Count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ref T this[int i]
|
||||||
|
{
|
||||||
|
get { return ref Array[i]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(T item)
|
||||||
|
{
|
||||||
|
if (Count >= Array.Length)
|
||||||
|
{
|
||||||
|
global::System.Array.Resize(ref Array, Array.Length * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Array[Count] = item;
|
||||||
|
Count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
Count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,5 +35,15 @@ namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
return !a.Equals(b);
|
return !a.Equals(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static implicit operator int(Entity e)
|
||||||
|
{
|
||||||
|
return e.ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator Entity(int i)
|
||||||
|
{
|
||||||
|
return new Entity(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,12 @@ 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;
|
||||||
|
internal TemplateStorage TemplateStorage => World.TemplateStorage;
|
||||||
|
internal ComponentDepot TemplateComponentDepot => World.TemplateComponentDepot;
|
||||||
|
|
||||||
public EntityComponentReader(World world)
|
public EntityComponentReader(World world)
|
||||||
{
|
{
|
||||||
|
@ -23,7 +28,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 +44,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
|
||||||
|
@ -46,12 +52,7 @@ namespace MoonTools.ECS
|
||||||
return ComponentDepot.GetSingletonEntity<TComponent>();
|
return ComponentDepot.GetSingletonEntity<TComponent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool Exists(in Entity entity)
|
protected ReverseSpanEnumerator<(Entity, Entity)> Relations<TRelationKind>() where TRelationKind : unmanaged
|
||||||
{
|
|
||||||
return EntityStorage.Exists(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected IEnumerable<(Entity, Entity, TRelationKind)> Relations<TRelationKind>() where TRelationKind : unmanaged
|
|
||||||
{
|
{
|
||||||
return RelationDepot.Relations<TRelationKind>();
|
return RelationDepot.Relations<TRelationKind>();
|
||||||
}
|
}
|
||||||
|
@ -61,13 +62,18 @@ namespace MoonTools.ECS
|
||||||
return RelationDepot.Related<TRelationKind>(a.ID, b.ID);
|
return RelationDepot.Related<TRelationKind>(a.ID, b.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// relations go A->B, so given A, will give all outgoing B relations.
|
protected TRelationKind GetRelationData<TRelationKind>(in Entity a, in Entity b) where TRelationKind : unmanaged
|
||||||
protected IEnumerable<(Entity, TRelationKind)> OutRelations<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
|
{
|
||||||
|
return RelationDepot.Get<TRelationKind>(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// relations go A->B, so given A, will give all entities in outgoing relations of this kind.
|
||||||
|
protected ReverseSpanEnumerator<Entity> OutRelations<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
|
||||||
{
|
{
|
||||||
return RelationDepot.OutRelations<TRelationKind>(entity.ID);
|
return RelationDepot.OutRelations<TRelationKind>(entity.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected (Entity, TRelationKind) OutRelationSingleton<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
|
protected Entity OutRelationSingleton<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
|
||||||
{
|
{
|
||||||
return RelationDepot.OutRelationSingleton<TRelationKind>(entity.ID);
|
return RelationDepot.OutRelationSingleton<TRelationKind>(entity.ID);
|
||||||
}
|
}
|
||||||
|
@ -82,13 +88,13 @@ namespace MoonTools.ECS
|
||||||
return RelationDepot.OutRelationCount<TRelationKind>(entity.ID);
|
return RelationDepot.OutRelationCount<TRelationKind>(entity.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relations go A->B, so given B, will give all incoming A relations.
|
// Relations go A->B, so given B, will give all entities in incoming A relations of this kind.
|
||||||
protected IEnumerable<(Entity, TRelationKind)> InRelations<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
|
protected ReverseSpanEnumerator<Entity> InRelations<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
|
||||||
{
|
{
|
||||||
return RelationDepot.InRelations<TRelationKind>(entity.ID);
|
return RelationDepot.InRelations<TRelationKind>(entity.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected (Entity, TRelationKind) InRelationSingleton<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
|
protected Entity InRelationSingleton<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
|
||||||
{
|
{
|
||||||
return RelationDepot.InRelationSingleton<TRelationKind>(entity.ID);
|
return RelationDepot.InRelationSingleton<TRelationKind>(entity.ID);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,22 @@ 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>();
|
||||||
|
// FIXME: this is only needed in debug mode
|
||||||
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 int Count => nextID - availableIDs.Count;
|
||||||
|
|
||||||
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 +30,61 @@ 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 componentTypeIndex)
|
||||||
{
|
{
|
||||||
state.NextID = nextID;
|
return EntityToComponentTypeIndices[entityID].Add(componentTypeIndex);
|
||||||
state.availableIDs.Clear();
|
|
||||||
foreach (var id in availableIDs)
|
|
||||||
{
|
|
||||||
state.availableIDs.Add(id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Load(EntityStorageState state)
|
public bool HasComponent(int entityID, int componentTypeIndex)
|
||||||
{
|
{
|
||||||
nextID = state.NextID;
|
return EntityToComponentTypeIndices[entityID].Contains(componentTypeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the component existed.
|
||||||
|
public bool RemoveComponent(int entityID, int componentTypeIndex)
|
||||||
|
{
|
||||||
|
return EntityToComponentTypeIndices[entityID].Remove(componentTypeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRelationKind(int entityID, int relationIndex)
|
||||||
|
{
|
||||||
|
EntityToRelationTypeIndices[entityID].Add(relationIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveRelation(int entityId, int relationIndex)
|
||||||
|
{
|
||||||
|
EntityToRelationTypeIndices[entityId].Remove(relationIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashSet<int> ComponentTypeIndices(int entityID)
|
||||||
|
{
|
||||||
|
return EntityToComponentTypeIndices[entityID];
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashSet<int> RelationTypeIndices(int entityID)
|
||||||
|
{
|
||||||
|
return EntityToRelationTypeIndices[entityID];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
nextID = 0;
|
||||||
|
foreach (var componentSet in EntityToComponentTypeIndices.Values)
|
||||||
|
{
|
||||||
|
componentSet.Clear();
|
||||||
|
}
|
||||||
|
foreach (var relationSet in EntityToRelationTypeIndices.Values)
|
||||||
|
{
|
||||||
|
relationSet.Clear();
|
||||||
|
}
|
||||||
availableIDs.Clear();
|
availableIDs.Clear();
|
||||||
availableIDHash.Clear();
|
availableIDHash.Clear();
|
||||||
foreach (var id in state.availableIDs)
|
|
||||||
{
|
|
||||||
availableIDs.Push(id);
|
|
||||||
availableIDHash.Add(id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int NextID()
|
private int NextID()
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace MoonTools.ECS
|
||||||
|
{
|
||||||
|
public ref struct ReverseSpanEnumerator<T>
|
||||||
|
{
|
||||||
|
private ReadOnlySpan<T> Span;
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
public ReverseSpanEnumerator<T> GetEnumerator() => this;
|
||||||
|
|
||||||
|
public T Current => Span[index];
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (index > 0)
|
||||||
|
{
|
||||||
|
index -= 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ReverseSpanEnumerator(Span<T> span)
|
||||||
|
{
|
||||||
|
Span = span;
|
||||||
|
index = span.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReverseSpanEnumerator<T> Empty => new ReverseSpanEnumerator<T>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,22 +6,22 @@ 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 ReverseSpanEnumerator<Entity> Entities => FilterStorage.FilterEntities(Signature);
|
||||||
public IEnumerable<Entity> EntitiesInRandomOrder => ComponentDepot.FilterEntitiesRandom(this);
|
public LinearCongruentialEnumerator 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,12 @@ using System.Collections.Generic;
|
||||||
|
|
||||||
namespace MoonTools.ECS
|
namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
public struct FilterSignature
|
public struct FilterSignature : IEquatable<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;
|
||||||
|
@ -26,26 +24,31 @@ namespace MoonTools.ECS
|
||||||
return Included.SetEquals(other.Included) && Excluded.SetEquals(other.Excluded);
|
return Included.SetEquals(other.Included) && Excluded.SetEquals(other.Excluded);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GuidToInt(Guid guid)
|
|
||||||
{
|
|
||||||
return BitConverter.ToInt32(guid.ToByteArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(FilterSignature left, FilterSignature right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(FilterSignature left, FilterSignature right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MoonTools.ECS
|
||||||
|
{
|
||||||
|
internal class FilterStorage
|
||||||
|
{
|
||||||
|
private EntityStorage EntityStorage;
|
||||||
|
private TypeIndices ComponentTypeIndices;
|
||||||
|
private Dictionary<FilterSignature, IndexableSet<Entity>> filterSignatureToEntityIDs = new Dictionary<FilterSignature, IndexableSet<Entity>>();
|
||||||
|
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<Entity>());
|
||||||
|
|
||||||
|
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 ReverseSpanEnumerator<Entity> FilterEntities(FilterSignature filterSignature)
|
||||||
|
{
|
||||||
|
return filterSignatureToEntityIDs[filterSignature].GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinearCongruentialEnumerator FilterEntitiesRandom(FilterSignature filterSignature)
|
||||||
|
{
|
||||||
|
return RandomGenerator.LinearCongruentialGenerator(FilterCount(filterSignature));
|
||||||
|
}
|
||||||
|
|
||||||
|
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.TryGetValue(componentTypeIndex, out var filterSignatures))
|
||||||
|
{
|
||||||
|
foreach (var filterSignature in filterSignatures)
|
||||||
|
{
|
||||||
|
CheckFilter(entityID, filterSignature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Check<TComponent>(int entityID) where TComponent : unmanaged
|
||||||
|
{
|
||||||
|
Check(entityID, ComponentTypeIndices.GetIndex<TComponent>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CheckSatisfied(int entityID, FilterSignature filterSignature)
|
||||||
|
{
|
||||||
|
foreach (var type in filterSignature.Included)
|
||||||
|
{
|
||||||
|
if (!EntityStorage.HasComponent(entityID, type))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var type in filterSignature.Excluded)
|
||||||
|
{
|
||||||
|
if (EntityStorage.HasComponent(entityID, type))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.TryGetValue(componentTypeIndex, out var filterSignatures))
|
||||||
|
{
|
||||||
|
foreach (var filterSignature in filterSignatures)
|
||||||
|
{
|
||||||
|
filterSignatureToEntityIDs[filterSignature].Remove(entityID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
namespace MoonTools.ECS
|
|
||||||
{
|
|
||||||
public interface IHasEntity
|
|
||||||
{
|
|
||||||
Entity Entity { get; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace MoonTools.ECS
|
namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
internal class IndexableSet<T> : IEnumerable<T> where T : unmanaged
|
internal class IndexableSet<T> where T : unmanaged
|
||||||
{
|
{
|
||||||
private Dictionary<T, int> indices;
|
private Dictionary<T, int> indices;
|
||||||
private T[] array;
|
private T[] array;
|
||||||
public int Count { get; private set; }
|
public int Count { get; private set; }
|
||||||
|
public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(array, 0, Count));
|
||||||
|
|
||||||
public IndexableSet(int size = 32)
|
public IndexableSet(int size = 32)
|
||||||
{
|
{
|
||||||
|
@ -64,52 +64,47 @@ namespace MoonTools.ECS
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator()
|
|
||||||
{
|
|
||||||
for (var i = Count - 1; i >= 0; i -= 1)
|
|
||||||
{
|
|
||||||
yield return array[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
for (var i = Count - 1; i >= 0; i -= 1)
|
|
||||||
{
|
|
||||||
yield return array[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
Count = 0;
|
Count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save(IndexableSetState<T> state)
|
public struct Enumerator
|
||||||
{
|
{
|
||||||
ReadOnlySpan<byte> arrayBytes = MemoryMarshal.Cast<T, byte>(array);
|
/// <summary>The set being enumerated.</summary>
|
||||||
|
private readonly IndexableSet<T> _set;
|
||||||
|
/// <summary>The next index to yield.</summary>
|
||||||
|
private int _index;
|
||||||
|
|
||||||
if (arrayBytes.Length > state.Array.Length)
|
/// <summary>Initialize the enumerator.</summary>
|
||||||
|
/// <param name="set">The set to enumerate.</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal Enumerator(IndexableSet<T> set)
|
||||||
{
|
{
|
||||||
Array.Resize(ref state.Array, arrayBytes.Length);
|
_set = set;
|
||||||
|
_index = _set.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
arrayBytes.CopyTo(state.Array);
|
/// <summary>Advances the enumerator to the next element of the span.</summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
state.Count = Count;
|
public bool MoveNext()
|
||||||
}
|
|
||||||
|
|
||||||
public void Load(IndexableSetState<T> state)
|
|
||||||
{
|
{
|
||||||
state.Array.CopyTo(MemoryMarshal.Cast<T, byte>(array));
|
int index = _index - 1;
|
||||||
|
if (index >= 0)
|
||||||
indices.Clear();
|
|
||||||
for (var i = 0; i < state.Count; i += 1)
|
|
||||||
{
|
{
|
||||||
indices[array[i]] = i;
|
_index = index;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Count = state.Count;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the element at the current position of the enumerator.</summary>
|
||||||
|
public T Current
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _set[_index];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
private Dictionary<Type, MessageStorage> storages = new Dictionary<Type, MessageStorage>();
|
private Dictionary<Type, MessageStorage> storages = new Dictionary<Type, MessageStorage>();
|
||||||
|
|
||||||
private MessageStorage<TMessage> Lookup<TMessage>() where TMessage : struct
|
private MessageStorage<TMessage> Lookup<TMessage>() where TMessage : unmanaged
|
||||||
{
|
{
|
||||||
if (!storages.ContainsKey(typeof(TMessage)))
|
if (!storages.ContainsKey(typeof(TMessage)))
|
||||||
{
|
{
|
||||||
|
@ -17,37 +17,42 @@ namespace MoonTools.ECS
|
||||||
return storages[typeof(TMessage)] as MessageStorage<TMessage>;
|
return storages[typeof(TMessage)] as MessageStorage<TMessage>;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add<TMessage>(in TMessage message) where TMessage : struct
|
public void Add<TMessage>(in TMessage message) where TMessage : unmanaged
|
||||||
{
|
{
|
||||||
Lookup<TMessage>().Add(message);
|
Lookup<TMessage>().Add(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Some<TMessage>() where TMessage : struct
|
public void Add<TMessage>(int entityID, in TMessage message) where TMessage : unmanaged
|
||||||
|
{
|
||||||
|
Lookup<TMessage>().Add(entityID, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Some<TMessage>() where TMessage : unmanaged
|
||||||
{
|
{
|
||||||
return Lookup<TMessage>().Some();
|
return Lookup<TMessage>().Some();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<TMessage> All<TMessage>() where TMessage : struct
|
public ReadOnlySpan<TMessage> All<TMessage>() where TMessage : unmanaged
|
||||||
{
|
{
|
||||||
return Lookup<TMessage>().All();
|
return Lookup<TMessage>().All();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TMessage First<TMessage>() where TMessage : struct
|
public TMessage First<TMessage>() where TMessage : unmanaged
|
||||||
{
|
{
|
||||||
return Lookup<TMessage>().First();
|
return Lookup<TMessage>().First();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<TMessage> WithEntity<TMessage>(int entityID) where TMessage : struct, IHasEntity
|
public ReverseSpanEnumerator<TMessage> WithEntity<TMessage>(int entityID) where TMessage : unmanaged
|
||||||
{
|
{
|
||||||
return Lookup<TMessage>().WithEntity(entityID);
|
return Lookup<TMessage>().WithEntity(entityID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref readonly TMessage FirstWithEntity<TMessage>(int entityID) where TMessage : struct, IHasEntity
|
public ref readonly TMessage FirstWithEntity<TMessage>(int entityID) where TMessage : unmanaged
|
||||||
{
|
{
|
||||||
return ref Lookup<TMessage>().FirstWithEntity(entityID);
|
return ref Lookup<TMessage>().FirstWithEntity(entityID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SomeWithEntity<TMessage>(int entityID) where TMessage : struct, IHasEntity
|
public bool SomeWithEntity<TMessage>(int entityID) where TMessage : unmanaged
|
||||||
{
|
{
|
||||||
return Lookup<TMessage>().SomeWithEntity(entityID);
|
return Lookup<TMessage>().SomeWithEntity(entityID);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,13 @@ namespace MoonTools.ECS
|
||||||
public abstract void Clear();
|
public abstract void Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class MessageStorage<TMessage> : MessageStorage where TMessage : struct
|
internal class MessageStorage<TMessage> : MessageStorage where TMessage : unmanaged
|
||||||
{
|
{
|
||||||
private int count = 0;
|
private int count = 0;
|
||||||
private int capacity = 128;
|
private int capacity = 128;
|
||||||
private TMessage[] messages;
|
private TMessage[] messages;
|
||||||
private Dictionary<int, List<int>> entityToIndices = new Dictionary<int, List<int>>();
|
// duplicating storage here for fast iteration
|
||||||
|
private Dictionary<int, DynamicArray<TMessage>> entityToMessages = new Dictionary<int, DynamicArray<TMessage>>();
|
||||||
|
|
||||||
public MessageStorage()
|
public MessageStorage()
|
||||||
{
|
{
|
||||||
|
@ -29,20 +30,20 @@ namespace MoonTools.ECS
|
||||||
}
|
}
|
||||||
|
|
||||||
messages[count] = message;
|
messages[count] = message;
|
||||||
|
|
||||||
if (message is IHasEntity entityMessage)
|
|
||||||
{
|
|
||||||
if (!entityToIndices.ContainsKey(entityMessage.Entity.ID))
|
|
||||||
{
|
|
||||||
entityToIndices.Add(entityMessage.Entity.ID, new List<int>());
|
|
||||||
}
|
|
||||||
|
|
||||||
entityToIndices[entityMessage.Entity.ID].Add(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Add(int entityID, in TMessage message)
|
||||||
|
{
|
||||||
|
if (!entityToMessages.ContainsKey(entityID))
|
||||||
|
{
|
||||||
|
entityToMessages.Add(entityID, new DynamicArray<TMessage>());
|
||||||
|
}
|
||||||
|
entityToMessages[entityID].Add(message);
|
||||||
|
|
||||||
|
Add(message);
|
||||||
|
}
|
||||||
|
|
||||||
public bool Some()
|
public bool Some()
|
||||||
{
|
{
|
||||||
return count > 0;
|
return count > 0;
|
||||||
|
@ -58,31 +59,32 @@ namespace MoonTools.ECS
|
||||||
return messages[0];
|
return messages[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<TMessage> WithEntity(int entityID)
|
public ReverseSpanEnumerator<TMessage> WithEntity(int entityID)
|
||||||
{
|
{
|
||||||
if (entityToIndices.ContainsKey(entityID))
|
if (entityToMessages.TryGetValue(entityID, out var messages))
|
||||||
{
|
{
|
||||||
foreach (var index in entityToIndices[entityID])
|
return messages.GetEnumerator();
|
||||||
{
|
|
||||||
yield return messages[index];
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ReverseSpanEnumerator<TMessage>.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref readonly TMessage FirstWithEntity(int entityID)
|
public ref readonly TMessage FirstWithEntity(int entityID)
|
||||||
{
|
{
|
||||||
return ref messages[entityToIndices[entityID][0]];
|
return ref entityToMessages[entityID][0];
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SomeWithEntity(int entityID)
|
public bool SomeWithEntity(int entityID)
|
||||||
{
|
{
|
||||||
return entityToIndices.ContainsKey(entityID) && entityToIndices[entityID].Count > 0;
|
return entityToMessages.ContainsKey(entityID) && entityToMessages[entityID].Count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Clear()
|
public override void Clear()
|
||||||
{
|
{
|
||||||
count = 0;
|
count = 0;
|
||||||
foreach (var set in entityToIndices.Values)
|
foreach (var set in entityToMessages.Values)
|
||||||
{
|
{
|
||||||
set.Clear();
|
set.Clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace MoonTools.ECS
|
namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
|
@ -20,7 +20,7 @@ namespace MoonTools.ECS
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A psuedorandom nonrepeating sequence of integers from 0 to n.
|
/// A psuedorandom nonrepeating sequence of integers from 0 to n.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IEnumerable<int> LinearCongruentialGenerator(int n)
|
public static LinearCongruentialEnumerator LinearCongruentialGenerator(int n)
|
||||||
{
|
{
|
||||||
var x = Primes[random.Next(Primes.Length - 1)];
|
var x = Primes[random.Next(Primes.Length - 1)];
|
||||||
while (x % n == 0)
|
while (x % n == 0)
|
||||||
|
@ -29,12 +29,44 @@ namespace MoonTools.ECS
|
||||||
x = Primes[random.Next(Primes.Length - 1)];
|
x = Primes[random.Next(Primes.Length - 1)];
|
||||||
}
|
}
|
||||||
|
|
||||||
var start = random.Next(n);
|
return new LinearCongruentialEnumerator(random.Next(n), x, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = start; i < start + n; i++)
|
public struct LinearCongruentialEnumerator
|
||||||
{
|
{
|
||||||
yield return (i * x) % n;
|
private readonly int start;
|
||||||
}
|
private readonly int count;
|
||||||
|
private readonly int prime;
|
||||||
|
private int current;
|
||||||
|
|
||||||
|
public LinearCongruentialEnumerator GetEnumerator() => this;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal LinearCongruentialEnumerator(int start, int prime, int count)
|
||||||
|
{
|
||||||
|
current = start;
|
||||||
|
this.start = start;
|
||||||
|
this.prime = prime;
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
current += 1;
|
||||||
|
if (current < start + count)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Current
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => (current * prime) % count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace MoonTools.ECS
|
|
||||||
{
|
|
||||||
internal struct Relation : IEquatable<Relation>
|
|
||||||
{
|
|
||||||
public Entity A { get; }
|
|
||||||
public Entity B { get; }
|
|
||||||
|
|
||||||
internal Relation(Entity entityA, Entity entityB)
|
|
||||||
{
|
|
||||||
A = entityA;
|
|
||||||
B = entityB;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal Relation(int idA, int idB)
|
|
||||||
{
|
|
||||||
A = new Entity(idA);
|
|
||||||
B = new Entity(idB);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
return obj is Relation relation && Equals(relation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(Relation other)
|
|
||||||
{
|
|
||||||
return A.ID == other.A.ID && B.ID == other.B.ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return HashCode.Combine(A.ID, B.ID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,31 +6,49 @@ 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;
|
||||||
{
|
|
||||||
storages.Add(typeof(TRelationKind), new RelationStorage<TRelationKind>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Register<TRelationKind>(int index) where TRelationKind : unmanaged
|
||||||
|
{
|
||||||
|
if (index >= storages.Length)
|
||||||
|
{
|
||||||
|
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>(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged
|
||||||
{
|
{
|
||||||
Lookup<TRelationKind>().Set(relation, relationData);
|
Lookup<TRelationKind>().Set(entityA, entityB, relationData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove<TRelationKind>(Relation relation) where TRelationKind : unmanaged
|
public TRelationKind Get<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged
|
||||||
{
|
{
|
||||||
Lookup<TRelationKind>().Remove(relation);
|
return Lookup<TRelationKind>().Get(entityA, entityB);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (bool, bool) Remove<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged
|
||||||
|
{
|
||||||
|
return Lookup<TRelationKind>().Remove(entityA, entityB);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnrelateAll<TRelationKind>(int entityID) where TRelationKind : unmanaged
|
public void UnrelateAll<TRelationKind>(int entityID) where TRelationKind : unmanaged
|
||||||
|
@ -38,31 +56,22 @@ namespace MoonTools.ECS
|
||||||
Lookup<TRelationKind>().UnrelateAll(entityID);
|
Lookup<TRelationKind>().UnrelateAll(entityID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: optimize this
|
public ReverseSpanEnumerator<(Entity, Entity)> Relations<TRelationKind>() where TRelationKind : unmanaged
|
||||||
public void OnEntityDestroy(int entityID)
|
|
||||||
{
|
|
||||||
foreach (var storage in storages.Values)
|
|
||||||
{
|
|
||||||
storage.OnEntityDestroy(entityID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<(Entity, Entity, TRelationKind)> Relations<TRelationKind>() where TRelationKind : unmanaged
|
|
||||||
{
|
{
|
||||||
return Lookup<TRelationKind>().All();
|
return Lookup<TRelationKind>().All();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Related<TRelationKind>(int idA, int idB) where TRelationKind : unmanaged
|
public bool Related<TRelationKind>(int idA, int idB) where TRelationKind : unmanaged
|
||||||
{
|
{
|
||||||
return Lookup<TRelationKind>().Has(new Relation(idA, idB));
|
return Lookup<TRelationKind>().Has((idA, idB));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<(Entity, TRelationKind)> OutRelations<TRelationKind>(int entityID) where TRelationKind : unmanaged
|
public ReverseSpanEnumerator<Entity> OutRelations<TRelationKind>(int entityID) where TRelationKind : unmanaged
|
||||||
{
|
{
|
||||||
return Lookup<TRelationKind>().OutRelations(entityID);
|
return Lookup<TRelationKind>().OutRelations(entityID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public (Entity, TRelationKind) OutRelationSingleton<TRelationKind>(int entityID) where TRelationKind : unmanaged
|
public Entity OutRelationSingleton<TRelationKind>(int entityID) where TRelationKind : unmanaged
|
||||||
{
|
{
|
||||||
return Lookup<TRelationKind>().OutFirst(entityID);
|
return Lookup<TRelationKind>().OutFirst(entityID);
|
||||||
}
|
}
|
||||||
|
@ -77,12 +86,12 @@ namespace MoonTools.ECS
|
||||||
return Lookup<TRelationKind>().HasOutRelation(entityID);
|
return Lookup<TRelationKind>().HasOutRelation(entityID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<(Entity, TRelationKind)> InRelations<TRelationKind>(int entityID) where TRelationKind : unmanaged
|
public ReverseSpanEnumerator<Entity> InRelations<TRelationKind>(int entityID) where TRelationKind : unmanaged
|
||||||
{
|
{
|
||||||
return Lookup<TRelationKind>().InRelations(entityID);
|
return Lookup<TRelationKind>().InRelations(entityID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public (Entity, TRelationKind) InRelationSingleton<TRelationKind>(int entityID) where TRelationKind : unmanaged
|
public Entity InRelationSingleton<TRelationKind>(int entityID) where TRelationKind : unmanaged
|
||||||
{
|
{
|
||||||
return Lookup<TRelationKind>().InFirst(entityID);
|
return Lookup<TRelationKind>().InFirst(entityID);
|
||||||
}
|
}
|
||||||
|
@ -97,24 +106,52 @@ namespace MoonTools.ECS
|
||||||
return Lookup<TRelationKind>().InRelationCount(entityID);
|
return Lookup<TRelationKind>().InRelationCount(entityID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save(RelationDepotState state)
|
// untyped methods used for destroying and snapshots
|
||||||
|
|
||||||
|
public unsafe void Set(int entityA, int entityB, int relationTypeIndex, void* relationData)
|
||||||
{
|
{
|
||||||
foreach (var (type, storage) in storages)
|
storages[relationTypeIndex].Set(entityA, entityB, relationData);
|
||||||
{
|
|
||||||
if (!state.StorageStates.ContainsKey(type))
|
|
||||||
{
|
|
||||||
state.StorageStates.Add(type, storage.CreateState());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
storage.Save(state.StorageStates[type]);
|
public int GetStorageIndex(int relationTypeIndex, int entityA, int entityB)
|
||||||
|
{
|
||||||
|
return storages[relationTypeIndex].GetStorageIndex(entityA, entityB);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void* Get(int relationTypeIndex, int relationStorageIndex)
|
||||||
|
{
|
||||||
|
return storages[relationTypeIndex].Get(relationStorageIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnrelateAll(int entityID, int relationTypeIndex)
|
||||||
|
{
|
||||||
|
storages[relationTypeIndex].UnrelateAll(entityID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReverseSpanEnumerator<Entity> OutRelations(int entityID, int relationTypeIndex)
|
||||||
|
{
|
||||||
|
return storages[relationTypeIndex].OutRelations(entityID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < RelationTypeIndices.Count; i += 1)
|
||||||
|
{
|
||||||
|
if (storages[i] != null)
|
||||||
|
{
|
||||||
|
storages[i].Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Load(RelationDepotState state)
|
public void CreateMissingStorages(RelationDepot other)
|
||||||
{
|
{
|
||||||
foreach (var (type, storageState) in state.StorageStates)
|
for (var i = 0; i < RelationTypeIndices.Count; i += 1)
|
||||||
{
|
{
|
||||||
storages[type].Load(storageState);
|
if (storages[i] == null && other.storages[i] != null)
|
||||||
|
{
|
||||||
|
storages[i] = other.storages[i].CreateStorage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MoonTools.ECS
|
namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
internal abstract class RelationStorage
|
internal abstract class RelationStorage
|
||||||
{
|
{
|
||||||
public abstract RelationStorageState CreateState();
|
public abstract unsafe void Set(int entityA, int entityB, void* relationData);
|
||||||
public abstract void Save(RelationStorageState state);
|
public abstract int GetStorageIndex(int entityA, int entityB);
|
||||||
public abstract void Load(RelationStorageState state);
|
public abstract unsafe void* Get(int relationStorageIndex);
|
||||||
public abstract void OnEntityDestroy(int entityID);
|
public abstract void UnrelateAll(int entityID);
|
||||||
|
public abstract ReverseSpanEnumerator<Entity> OutRelations(int entityID);
|
||||||
|
public abstract RelationStorage CreateStorage();
|
||||||
|
public abstract void Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relation is the two entities, A related to B.
|
// Relation is the two entities, A related to B.
|
||||||
|
@ -17,33 +19,30 @@ namespace MoonTools.ECS
|
||||||
internal class RelationStorage<TRelation> : RelationStorage where TRelation : unmanaged
|
internal class RelationStorage<TRelation> : RelationStorage where TRelation : unmanaged
|
||||||
{
|
{
|
||||||
private int count = 0;
|
private int count = 0;
|
||||||
private Dictionary<Relation, int> indices = new Dictionary<Relation, int>(16);
|
private Dictionary<(Entity, Entity), int> indices = new Dictionary<(Entity, Entity), int>(16);
|
||||||
private Relation[] relations = new Relation[16];
|
private (Entity, Entity)[] relations = new (Entity, Entity)[16];
|
||||||
private TRelation[] relationDatas = new TRelation[16];
|
private TRelation[] relationDatas = new TRelation[16];
|
||||||
private Dictionary<int, IndexableSet<int>> outRelations = new Dictionary<int, IndexableSet<int>>(16);
|
private Dictionary<int, IndexableSet<Entity>> outRelations = new Dictionary<int, IndexableSet<Entity>>(16);
|
||||||
private Dictionary<int, IndexableSet<int>> inRelations = new Dictionary<int, IndexableSet<int>>(16);
|
private Dictionary<int, IndexableSet<Entity>> inRelations = new Dictionary<int, IndexableSet<Entity>>(16);
|
||||||
private Stack<IndexableSet<int>> listPool = new Stack<IndexableSet<int>>();
|
private Stack<IndexableSet<Entity>> listPool = new Stack<IndexableSet<Entity>>();
|
||||||
|
|
||||||
public IEnumerable<(Entity, Entity, TRelation)> All()
|
public ReverseSpanEnumerator<(Entity, Entity)> All()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < count; i += 1)
|
return new ReverseSpanEnumerator<(Entity, Entity)>(new Span<(Entity, Entity)>(relations, 0, count));
|
||||||
{
|
|
||||||
var relation = relations[i];
|
|
||||||
yield return (relation.A, relation.B, relationDatas[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Set(Relation relation, TRelation relationData)
|
public void Set(in Entity entityA, in Entity entityB, TRelation relationData)
|
||||||
{
|
{
|
||||||
if (indices.ContainsKey(relation))
|
var relation = (entityA, entityB);
|
||||||
|
|
||||||
|
if (indices.TryGetValue(relation, out var index))
|
||||||
{
|
{
|
||||||
var index = indices[relation];
|
|
||||||
relationDatas[index] = relationData;
|
relationDatas[index] = relationData;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var idA = relation.A.ID;
|
var idA = entityA.ID;
|
||||||
var idB = relation.B.ID;
|
var idB = entityB.ID;
|
||||||
|
|
||||||
if (!outRelations.ContainsKey(idA))
|
if (!outRelations.ContainsKey(idA))
|
||||||
{
|
{
|
||||||
|
@ -69,34 +68,37 @@ namespace MoonTools.ECS
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Has(Relation relation)
|
public TRelation Get(in Entity entityA, in Entity entityB)
|
||||||
|
{
|
||||||
|
return relationDatas[indices[(entityA, entityB)]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Has((Entity, Entity) relation)
|
||||||
{
|
{
|
||||||
return indices.ContainsKey(relation);
|
return indices.ContainsKey(relation);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: creating the new Relation in here is slightly deranged
|
public override ReverseSpanEnumerator<Entity> OutRelations(int entityID)
|
||||||
public IEnumerable<(Entity, TRelation)> OutRelations(int entityID)
|
|
||||||
{
|
{
|
||||||
if (outRelations.ContainsKey(entityID))
|
if (outRelations.TryGetValue(entityID, out var entityOutRelations))
|
||||||
{
|
{
|
||||||
foreach (var id in outRelations[entityID])
|
return entityOutRelations.GetEnumerator();
|
||||||
{
|
|
||||||
var relation = new Relation(entityID, id);
|
|
||||||
yield return (relation.B, relationDatas[indices[relation]]);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ReverseSpanEnumerator<Entity>.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public (Entity, TRelation) OutFirst(int entityID)
|
public Entity OutFirst(int entityID)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (!outRelations.ContainsKey(entityID))
|
if (!outRelations.ContainsKey(entityID) || outRelations[entityID].Count == 0)
|
||||||
{
|
{
|
||||||
throw new KeyNotFoundException("No out relations to this entity!");
|
throw new KeyNotFoundException("No out relations to this entity!");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
var relation = new Relation(entityID, outRelations[entityID][0]);
|
return outRelations[entityID][0];
|
||||||
return (relation.B, relationDatas[indices[relation]]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasOutRelation(int entityID)
|
public bool HasOutRelation(int entityID)
|
||||||
|
@ -106,32 +108,31 @@ namespace MoonTools.ECS
|
||||||
|
|
||||||
public int OutRelationCount(int entityID)
|
public int OutRelationCount(int entityID)
|
||||||
{
|
{
|
||||||
return outRelations.ContainsKey(entityID) ? outRelations[entityID].Count : 0;
|
return outRelations.TryGetValue(entityID, out var entityOutRelations) ? entityOutRelations.Count : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<(Entity, TRelation)> InRelations(int entityID)
|
public ReverseSpanEnumerator<Entity> InRelations(int entityID)
|
||||||
{
|
{
|
||||||
if (inRelations.ContainsKey(entityID))
|
if (inRelations.TryGetValue(entityID, out var entityInRelations))
|
||||||
{
|
{
|
||||||
foreach (var id in inRelations[entityID])
|
return entityInRelations.GetEnumerator();
|
||||||
{
|
|
||||||
var relation = new Relation(id, entityID);
|
|
||||||
yield return (relation.A, relationDatas[indices[relation]]);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ReverseSpanEnumerator<Entity>.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public (Entity, TRelation) InFirst(int entityID)
|
public Entity InFirst(int entityID)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (!inRelations.ContainsKey(entityID))
|
if (!inRelations.ContainsKey(entityID) || inRelations[entityID].Count == 0)
|
||||||
{
|
{
|
||||||
throw new KeyNotFoundException("No out relations to this entity!");
|
throw new KeyNotFoundException("No out relations to this entity!");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var relation = new Relation(inRelations[entityID][0], entityID);
|
return inRelations[entityID][0];
|
||||||
return (relation.A, relationDatas[indices[relation]]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasInRelation(int entityID)
|
public bool HasInRelation(int entityID)
|
||||||
|
@ -141,24 +142,35 @@ namespace MoonTools.ECS
|
||||||
|
|
||||||
public int InRelationCount(int entityID)
|
public int InRelationCount(int entityID)
|
||||||
{
|
{
|
||||||
return inRelations.ContainsKey(entityID) ? inRelations[entityID].Count : 0;
|
return inRelations.TryGetValue(entityID, out var entityInRelations) ? entityInRelations.Count : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Remove(Relation relation)
|
public (bool, bool) Remove(in Entity entityA, in Entity entityB)
|
||||||
{
|
{
|
||||||
if (outRelations.ContainsKey(relation.A.ID))
|
var aEmpty = false;
|
||||||
|
var bEmpty = false;
|
||||||
|
var relation = (entityA, entityB);
|
||||||
|
|
||||||
|
if (outRelations.TryGetValue(entityA.ID, out var entityOutRelations))
|
||||||
{
|
{
|
||||||
outRelations[relation.A.ID].Remove(relation.B.ID);
|
entityOutRelations.Remove(entityB.ID);
|
||||||
|
if (outRelations[entityA.ID].Count == 0)
|
||||||
|
{
|
||||||
|
aEmpty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inRelations.ContainsKey(relation.B.ID))
|
if (inRelations.TryGetValue(entityB.ID, out var entityInRelations))
|
||||||
{
|
{
|
||||||
inRelations[relation.B.ID].Remove(relation.A.ID);
|
entityInRelations.Remove(entityA.ID);
|
||||||
|
if (inRelations[entityB.ID].Count == 0)
|
||||||
|
{
|
||||||
|
bEmpty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (indices.ContainsKey(relation))
|
if (indices.TryGetValue(relation, out var index))
|
||||||
{
|
{
|
||||||
var index = indices[relation];
|
|
||||||
var lastElementIndex = count - 1;
|
var lastElementIndex = count - 1;
|
||||||
|
|
||||||
// move an element into the hole
|
// move an element into the hole
|
||||||
|
@ -172,111 +184,93 @@ 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)
|
private IndexableSet<Entity> AcquireHashSetFromPool()
|
||||||
{
|
|
||||||
if (outRelations.ContainsKey(entityID))
|
|
||||||
{
|
|
||||||
foreach (var entityB in outRelations[entityID])
|
|
||||||
{
|
|
||||||
Remove(new Relation(entityID, entityB));
|
|
||||||
}
|
|
||||||
|
|
||||||
ReturnHashSetToPool(outRelations[entityID]);
|
|
||||||
outRelations.Remove(entityID);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inRelations.ContainsKey(entityID))
|
|
||||||
{
|
|
||||||
foreach (var entityA in inRelations[entityID])
|
|
||||||
{
|
|
||||||
Remove(new Relation(entityA, entityID));
|
|
||||||
}
|
|
||||||
|
|
||||||
ReturnHashSetToPool(inRelations[entityID]);
|
|
||||||
inRelations.Remove(entityID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnEntityDestroy(int entityID)
|
|
||||||
{
|
|
||||||
UnrelateAll(entityID);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IndexableSet<int> AcquireHashSetFromPool()
|
|
||||||
{
|
{
|
||||||
if (listPool.Count == 0)
|
if (listPool.Count == 0)
|
||||||
{
|
{
|
||||||
listPool.Push(new IndexableSet<int>());
|
listPool.Push(new IndexableSet<Entity>());
|
||||||
}
|
}
|
||||||
|
|
||||||
return listPool.Pop();
|
return listPool.Pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReturnHashSetToPool(IndexableSet<int> hashSet)
|
private void ReturnHashSetToPool(IndexableSet<Entity> hashSet)
|
||||||
{
|
{
|
||||||
hashSet.Clear();
|
hashSet.Clear();
|
||||||
listPool.Push(hashSet);
|
listPool.Push(hashSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override RelationStorageState CreateState()
|
// untyped methods used for internal implementation
|
||||||
|
|
||||||
|
public override unsafe void Set(int entityA, int entityB, void* relationData)
|
||||||
{
|
{
|
||||||
return RelationStorageState.Create<TRelation>(count);
|
Set(entityA, entityB, *((TRelation*) relationData));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Save(RelationStorageState state)
|
public override int GetStorageIndex(int entityA, int entityB)
|
||||||
{
|
{
|
||||||
ReadOnlySpan<byte> relationBytes = MemoryMarshal.Cast<Relation, byte>(relations);
|
return indices[(entityA, entityB)];
|
||||||
|
|
||||||
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)
|
public override unsafe void* Get(int relationStorageIndex)
|
||||||
{
|
{
|
||||||
state.Relations.CopyTo(MemoryMarshal.Cast<Relation, byte>(relations));
|
fixed (void* p = &relations[relationStorageIndex])
|
||||||
state.RelationDatas.CopyTo(MemoryMarshal.Cast<TRelation, byte>(relationDatas));
|
{
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UnrelateAll(int entityID)
|
||||||
|
{
|
||||||
|
if (outRelations.TryGetValue(entityID, out var entityOutRelations))
|
||||||
|
{
|
||||||
|
foreach (var entityB in entityOutRelations)
|
||||||
|
{
|
||||||
|
Remove(entityID, entityB);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReturnHashSetToPool(entityOutRelations);
|
||||||
|
outRelations.Remove(entityID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inRelations.TryGetValue(entityID, out var entityInRelations))
|
||||||
|
{
|
||||||
|
foreach (var entityA in entityInRelations)
|
||||||
|
{
|
||||||
|
Remove(entityA, entityID);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReturnHashSetToPool(entityInRelations);
|
||||||
|
inRelations.Remove(entityID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override RelationStorage<TRelation> CreateStorage()
|
||||||
|
{
|
||||||
|
return new RelationStorage<TRelation>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Clear()
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
indices.Clear();
|
indices.Clear();
|
||||||
outRelations.Clear();
|
|
||||||
|
foreach (var set in inRelations.Values)
|
||||||
|
{
|
||||||
|
ReturnHashSetToPool(set);
|
||||||
|
}
|
||||||
inRelations.Clear();
|
inRelations.Clear();
|
||||||
for (var i = 0; i < state.Count; i += 1)
|
|
||||||
{
|
|
||||||
var relation = relations[i];
|
|
||||||
indices[relation] = i;
|
|
||||||
|
|
||||||
if (!outRelations.ContainsKey(relation.A.ID))
|
foreach (var set in outRelations.Values)
|
||||||
{
|
{
|
||||||
outRelations[relation.A.ID] = AcquireHashSetFromPool();
|
ReturnHashSetToPool(set);
|
||||||
}
|
}
|
||||||
outRelations[relation.A.ID].Add(relation.B.ID);
|
outRelations.Clear();
|
||||||
|
|
||||||
if (!inRelations.ContainsKey(relation.B.ID))
|
|
||||||
{
|
|
||||||
inRelations[relation.B.ID] = AcquireHashSetFromPool();
|
|
||||||
}
|
|
||||||
inRelations[relation.B.ID].Add(relation.A.ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
count = state.Count;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MoonTools.ECS
|
||||||
|
{
|
||||||
|
public class Snapshot
|
||||||
|
{
|
||||||
|
private World World;
|
||||||
|
private Filter? Filter;
|
||||||
|
|
||||||
|
private EntityStorage SnapshotEntityStorage;
|
||||||
|
private ComponentDepot SnapshotComponentDepot;
|
||||||
|
private RelationDepot SnapshotRelationDepot;
|
||||||
|
|
||||||
|
private List<int> SnapshotToWorldID = new List<int>();
|
||||||
|
private Dictionary<int, int> WorldToSnapshotID = new Dictionary<int, int>();
|
||||||
|
|
||||||
|
internal Snapshot(World world)
|
||||||
|
{
|
||||||
|
World = world;
|
||||||
|
SnapshotEntityStorage = new EntityStorage();
|
||||||
|
SnapshotComponentDepot = new ComponentDepot(World.ComponentTypeIndices);
|
||||||
|
SnapshotRelationDepot = new RelationDepot(World.RelationTypeIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Take(Filter filter)
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
Filter = filter;
|
||||||
|
SnapshotComponentDepot.CreateMissingStorages(World.ComponentDepot);
|
||||||
|
SnapshotRelationDepot.CreateMissingStorages(World.RelationDepot);
|
||||||
|
|
||||||
|
foreach (var worldEntity in filter.Entities)
|
||||||
|
{
|
||||||
|
var snapshotEntity = SnapshotEntityStorage.Create();
|
||||||
|
WorldToSnapshotID.Add(worldEntity.ID, snapshotEntity.ID);
|
||||||
|
|
||||||
|
foreach (var componentTypeIndex in World.EntityStorage.ComponentTypeIndices(worldEntity.ID))
|
||||||
|
{
|
||||||
|
SnapshotEntityStorage.SetComponent(snapshotEntity.ID, componentTypeIndex);
|
||||||
|
SnapshotComponentDepot.Set(snapshotEntity.ID, componentTypeIndex, World.ComponentDepot.UntypedGet(worldEntity.ID, componentTypeIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var worldEntity in filter.Entities)
|
||||||
|
{
|
||||||
|
var snapshotEntityID = WorldToSnapshotID[worldEntity.ID];
|
||||||
|
|
||||||
|
foreach (var relationTypeIndex in World.EntityStorage.RelationTypeIndices(worldEntity.ID))
|
||||||
|
{
|
||||||
|
SnapshotEntityStorage.AddRelationKind(snapshotEntityID, relationTypeIndex);
|
||||||
|
|
||||||
|
foreach (var otherEntityID in World.RelationDepot.OutRelations(worldEntity.ID, relationTypeIndex))
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
if (!World.FilterStorage.CheckSatisfied(otherEntityID, Filter.Signature))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Snapshot entity {worldEntity.ID} is related to non-snapshot entity {otherEntityID}!");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
var relationStorageIndex = World.RelationDepot.GetStorageIndex(relationTypeIndex, worldEntity, otherEntityID);
|
||||||
|
var otherSnapshotID = WorldToSnapshotID[otherEntityID];
|
||||||
|
SnapshotEntityStorage.AddRelationKind(otherSnapshotID, relationTypeIndex);
|
||||||
|
SnapshotRelationDepot.Set(snapshotEntityID, otherSnapshotID, relationTypeIndex, World.RelationDepot.Get(relationTypeIndex, relationStorageIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Restore()
|
||||||
|
{
|
||||||
|
if (Filter == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var entity in Filter.Entities)
|
||||||
|
{
|
||||||
|
World.Destroy(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < SnapshotEntityStorage.Count; i += 1)
|
||||||
|
{
|
||||||
|
var entity = World.CreateEntity();
|
||||||
|
SnapshotToWorldID.Add(entity.ID);
|
||||||
|
|
||||||
|
foreach (var componentTypeIndex in SnapshotEntityStorage.ComponentTypeIndices(i))
|
||||||
|
{
|
||||||
|
World.EntityStorage.SetComponent(entity.ID, componentTypeIndex);
|
||||||
|
World.FilterStorage.Check(entity.ID, componentTypeIndex);
|
||||||
|
World.ComponentDepot.Set(entity.ID, componentTypeIndex, SnapshotComponentDepot.UntypedGet(i, componentTypeIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < SnapshotEntityStorage.Count; i += 1)
|
||||||
|
{
|
||||||
|
var worldID = SnapshotToWorldID[i];
|
||||||
|
|
||||||
|
foreach (var relationTypeIndex in SnapshotEntityStorage.RelationTypeIndices(i))
|
||||||
|
{
|
||||||
|
World.EntityStorage.AddRelationKind(worldID, relationTypeIndex);
|
||||||
|
|
||||||
|
foreach (var otherEntityID in SnapshotRelationDepot.OutRelations(i, relationTypeIndex))
|
||||||
|
{
|
||||||
|
var relationStorageIndex = SnapshotRelationDepot.GetStorageIndex(relationTypeIndex, i, otherEntityID);
|
||||||
|
var otherEntityWorldID = SnapshotToWorldID[otherEntityID];
|
||||||
|
World.RelationDepot.Set(worldID, otherEntityWorldID, relationTypeIndex, SnapshotRelationDepot.Get(relationTypeIndex, relationStorageIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Clear()
|
||||||
|
{
|
||||||
|
SnapshotEntityStorage.Clear();
|
||||||
|
SnapshotComponentDepot.Clear();
|
||||||
|
SnapshotRelationDepot.Clear();
|
||||||
|
SnapshotToWorldID.Clear();
|
||||||
|
WorldToSnapshotID.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MoonTools.ECS
|
|
||||||
{
|
|
||||||
internal class ComponentDepotState
|
|
||||||
{
|
|
||||||
public Dictionary<Type, ComponentStorageState> StorageStates = new Dictionary<Type, ComponentStorageState>();
|
|
||||||
public Dictionary<FilterSignature, IndexableSetState<int>> FilterStates = new Dictionary<FilterSignature, IndexableSetState<int>>();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MoonTools.ECS
|
|
||||||
{
|
|
||||||
internal class ComponentStorageState
|
|
||||||
{
|
|
||||||
public int Count;
|
|
||||||
public byte[] EntityIDs;
|
|
||||||
public byte[] Components;
|
|
||||||
|
|
||||||
public unsafe static ComponentStorageState Create<TComponent>(int count) where TComponent : unmanaged
|
|
||||||
{
|
|
||||||
return new ComponentStorageState(
|
|
||||||
count,
|
|
||||||
count * sizeof(int),
|
|
||||||
count * sizeof(TComponent)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ComponentStorageState(int count, int entityIDSize, int componentSize)
|
|
||||||
{
|
|
||||||
Count = count;
|
|
||||||
EntityIDs = new byte[entityIDSize];
|
|
||||||
Components = new byte[componentSize];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MoonTools.ECS
|
|
||||||
{
|
|
||||||
internal class EntityStorageState
|
|
||||||
{
|
|
||||||
public int NextID;
|
|
||||||
public List<int> availableIDs = new List<int>();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MoonTools.ECS
|
|
||||||
{
|
|
||||||
internal class IndexableSetState<T> where T : unmanaged
|
|
||||||
{
|
|
||||||
public int Count;
|
|
||||||
public byte[] Array;
|
|
||||||
|
|
||||||
public unsafe IndexableSetState(int count)
|
|
||||||
{
|
|
||||||
Count = count;
|
|
||||||
Array = new byte[sizeof(T) * count];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MoonTools.ECS
|
|
||||||
{
|
|
||||||
internal class RelationDepotState
|
|
||||||
{
|
|
||||||
public Dictionary<Type, RelationStorageState> StorageStates = new Dictionary<Type, RelationStorageState>();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MoonTools.ECS
|
|
||||||
{
|
|
||||||
internal class RelationStorageState
|
|
||||||
{
|
|
||||||
public int Count;
|
|
||||||
public byte[] Relations;
|
|
||||||
public byte[] RelationDatas;
|
|
||||||
|
|
||||||
public unsafe static RelationStorageState Create<TRelation>(int count) where TRelation : unmanaged
|
|
||||||
{
|
|
||||||
return new RelationStorageState(
|
|
||||||
count,
|
|
||||||
count * sizeof(Relation),
|
|
||||||
count * sizeof(TRelation)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private RelationStorageState(int count, int relationSize, int relationDataSize)
|
|
||||||
{
|
|
||||||
Count = count;
|
|
||||||
Relations = new byte[relationSize];
|
|
||||||
RelationDatas = new byte[relationDataSize];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
namespace MoonTools.ECS
|
|
||||||
{
|
|
||||||
public class WorldState
|
|
||||||
{
|
|
||||||
internal readonly ComponentDepotState ComponentDepotState;
|
|
||||||
internal readonly EntityStorageState EntityStorageState;
|
|
||||||
internal readonly RelationDepotState RelationDepotState;
|
|
||||||
|
|
||||||
public WorldState()
|
|
||||||
{
|
|
||||||
ComponentDepotState = new ComponentDepotState();
|
|
||||||
EntityStorageState = new EntityStorageState();
|
|
||||||
RelationDepotState = new RelationDepotState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,27 +11,23 @@ namespace MoonTools.ECS
|
||||||
|
|
||||||
public abstract void Update(TimeSpan delta);
|
public abstract void Update(TimeSpan delta);
|
||||||
|
|
||||||
protected Entity CreateEntity()
|
protected Entity CreateEntity() => World.CreateEntity();
|
||||||
{
|
|
||||||
return EntityStorage.Create();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void Set<TComponent>(in Entity entity, in TComponent component) where TComponent : unmanaged
|
protected void Set<TComponent>(in Entity entity, in TComponent component) where TComponent : unmanaged => World.Set<TComponent>(entity, component);
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
// check for use after destroy
|
|
||||||
if (!Exists(entity))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("This entity is not valid!");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
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
|
||||||
|
{
|
||||||
|
if (EntityStorage.RemoveComponent(entity.ID, ComponentTypeIndices.GetIndex<TComponent>()))
|
||||||
{
|
{
|
||||||
ComponentDepot.Remove<TComponent>(entity.ID);
|
ComponentDepot.Remove<TComponent>(entity.ID);
|
||||||
|
FilterStorage.Check<TComponent>(entity.ID);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Set<TComponent>(in Template template, in TComponent component) where TComponent : unmanaged => World.Set<TComponent>(template, component);
|
||||||
|
|
||||||
|
// This feature is EXPERIMENTAL. USe at your own risk!!
|
||||||
|
protected Entity Instantiate(in Template template) => World.Instantiate(template);
|
||||||
|
|
||||||
protected ReadOnlySpan<TMessage> ReadMessages<TMessage>() where TMessage : unmanaged
|
protected ReadOnlySpan<TMessage> ReadMessages<TMessage>() where TMessage : unmanaged
|
||||||
{
|
{
|
||||||
|
@ -48,17 +44,17 @@ namespace MoonTools.ECS
|
||||||
return MessageDepot.Some<TMessage>();
|
return MessageDepot.Some<TMessage>();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IEnumerable<TMessage> ReadMessagesWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged, IHasEntity
|
protected ReverseSpanEnumerator<TMessage> ReadMessagesWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged
|
||||||
{
|
{
|
||||||
return MessageDepot.WithEntity<TMessage>(entity.ID);
|
return MessageDepot.WithEntity<TMessage>(entity.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ref readonly TMessage ReadMessageWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged, IHasEntity
|
protected ref readonly TMessage ReadMessageWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged
|
||||||
{
|
{
|
||||||
return ref MessageDepot.FirstWithEntity<TMessage>(entity.ID);
|
return ref MessageDepot.FirstWithEntity<TMessage>(entity.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool SomeMessageWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged, IHasEntity
|
protected bool SomeMessageWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged
|
||||||
{
|
{
|
||||||
return MessageDepot.SomeWithEntity<TMessage>(entity.ID);
|
return MessageDepot.SomeWithEntity<TMessage>(entity.ID);
|
||||||
}
|
}
|
||||||
|
@ -68,27 +64,40 @@ namespace MoonTools.ECS
|
||||||
MessageDepot.Add(message);
|
MessageDepot.Add(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void Send<TMessage>(in Entity entity, in TMessage message) where TMessage : unmanaged
|
||||||
|
{
|
||||||
|
MessageDepot.Add(entity.ID, message);
|
||||||
|
}
|
||||||
|
|
||||||
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(entityA, entityB, relationData);
|
||||||
|
var relationTypeIndex = RelationTypeIndices.GetIndex<TRelationKind>();
|
||||||
|
EntityStorage.AddRelationKind(entityA.ID, relationTypeIndex);
|
||||||
|
EntityStorage.AddRelationKind(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>(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) => World.Destroy(entity);
|
||||||
protected void Destroy(in Entity entity)
|
|
||||||
{
|
|
||||||
ComponentDepot.OnEntityDestroy(entity.ID);
|
|
||||||
RelationDepot.OnEntityDestroy(entity.ID);
|
|
||||||
EntityStorage.Destroy(entity);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
namespace MoonTools.ECS
|
||||||
|
{
|
||||||
|
// This feature is EXPERIMENTAL. Use at your own risk!!
|
||||||
|
public struct Template
|
||||||
|
{
|
||||||
|
public int ID { get; }
|
||||||
|
|
||||||
|
internal Template(int id)
|
||||||
|
{
|
||||||
|
ID = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MoonTools.ECS
|
||||||
|
{
|
||||||
|
public class TemplateStorage
|
||||||
|
{
|
||||||
|
private int nextID = 0;
|
||||||
|
|
||||||
|
private Dictionary<int, HashSet<int>> TemplateToComponentTypeIndices = new Dictionary<int, HashSet<int>>();
|
||||||
|
|
||||||
|
public Template Create()
|
||||||
|
{
|
||||||
|
TemplateToComponentTypeIndices.Add(nextID, new HashSet<int>());
|
||||||
|
return new Template(NextID());
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetComponent(int templateID, int componentTypeIndex)
|
||||||
|
{
|
||||||
|
return TemplateToComponentTypeIndices[templateID].Add(componentTypeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashSet<int> ComponentTypeIndices(int templateID)
|
||||||
|
{
|
||||||
|
return TemplateToComponentTypeIndices[templateID];
|
||||||
|
}
|
||||||
|
|
||||||
|
private int NextID()
|
||||||
|
{
|
||||||
|
var id = nextID;
|
||||||
|
nextID += 1;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
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 Count => TypeToIndex.Count;
|
||||||
|
|
||||||
|
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 Dictionary<Type, int>.KeyCollection Types => TypeToIndex.Keys;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
104
src/World.cs
104
src/World.cs
|
@ -1,11 +1,28 @@
|
||||||
namespace MoonTools.ECS
|
using System;
|
||||||
|
|
||||||
|
namespace MoonTools.ECS
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
public FilterBuilder FilterBuilder => new FilterBuilder(FilterStorage, ComponentTypeIndices);
|
||||||
|
|
||||||
|
internal readonly TemplateStorage TemplateStorage = new TemplateStorage();
|
||||||
|
internal readonly ComponentDepot TemplateComponentDepot;
|
||||||
|
|
||||||
|
public World()
|
||||||
|
{
|
||||||
|
ComponentDepot = new ComponentDepot(ComponentTypeIndices);
|
||||||
|
RelationDepot = new RelationDepot(RelationTypeIndices);
|
||||||
|
FilterStorage = new FilterStorage(EntityStorage, ComponentTypeIndices);
|
||||||
|
TemplateComponentDepot = new ComponentDepot(ComponentTypeIndices);
|
||||||
|
}
|
||||||
|
|
||||||
public Entity CreateEntity()
|
public Entity CreateEntity()
|
||||||
{
|
{
|
||||||
|
@ -14,7 +31,46 @@
|
||||||
|
|
||||||
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 DEBUG
|
||||||
|
// check for use after destroy
|
||||||
|
if (!EntityStorage.Exists(entity))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("This entity is not valid!");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex<TComponent>()))
|
||||||
|
{
|
||||||
|
FilterStorage.Check<TComponent>(entity.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
ComponentDepot.Set<TComponent>(entity.ID, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Template CreateTemplate()
|
||||||
|
{
|
||||||
|
return TemplateStorage.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Set<TComponent>(in Template template, in TComponent component) where TComponent : unmanaged
|
||||||
|
{
|
||||||
|
var componentTypeIndex = ComponentTypeIndices.GetIndex<TComponent>();
|
||||||
|
TemplateStorage.SetComponent(template.ID, componentTypeIndex);
|
||||||
|
TemplateComponentDepot.Set(template.ID, component);
|
||||||
|
ComponentDepot.Register<TComponent>(componentTypeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe Entity Instantiate(in Template template)
|
||||||
|
{
|
||||||
|
var entity = EntityStorage.Create();
|
||||||
|
|
||||||
|
foreach (var componentTypeIndex in TemplateStorage.ComponentTypeIndices(template.ID))
|
||||||
|
{
|
||||||
|
EntityStorage.SetComponent(entity.ID, componentTypeIndex);
|
||||||
|
FilterStorage.Check(entity.ID, componentTypeIndex);
|
||||||
|
ComponentDepot.Set(entity.ID, componentTypeIndex, TemplateComponentDepot.UntypedGet(template.ID, componentTypeIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Send<TMessage>(in TMessage message) where TMessage : unmanaged
|
public void Send<TMessage>(in TMessage message) where TMessage : unmanaged
|
||||||
|
@ -22,33 +78,31 @@
|
||||||
MessageDepot.Add(message);
|
MessageDepot.Add(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Destroy(in Entity entity)
|
||||||
|
{
|
||||||
|
foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID))
|
||||||
|
{
|
||||||
|
ComponentDepot.Remove(entity.ID, componentTypeIndex);
|
||||||
|
FilterStorage.RemoveEntity(entity.ID, componentTypeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID))
|
||||||
|
{
|
||||||
|
RelationDepot.UnrelateAll(entity.ID, relationTypeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityStorage.Destroy(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void FinishUpdate()
|
public void FinishUpdate()
|
||||||
{
|
{
|
||||||
MessageDepot.Clear();
|
MessageDepot.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisableSerialization<TComponent>() where TComponent : unmanaged
|
public Snapshot CreateSnapshot()
|
||||||
{
|
{
|
||||||
ComponentDepot.DisableSerialization<TComponent>();
|
return new Snapshot(this);
|
||||||
}
|
|
||||||
|
|
||||||
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