initial commit
commit
0f4052728e
|
@ -0,0 +1,14 @@
|
||||||
|
# EditorConfig is awesome: https://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = tab
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.cs]
|
||||||
|
csharp_space_after_cast = true
|
||||||
|
charset = utf-8-bom
|
||||||
|
max_line_length = 100
|
|
@ -0,0 +1,2 @@
|
||||||
|
bin/
|
||||||
|
obj/
|
|
@ -0,0 +1,9 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,76 @@
|
||||||
|
namespace MoonTools.ECS;
|
||||||
|
|
||||||
|
internal class ComponentDepot
|
||||||
|
{
|
||||||
|
private Dictionary<Type, ComponentStorage> storages = new Dictionary<Type, ComponentStorage>();
|
||||||
|
|
||||||
|
private Dictionary<int, HashSet<Type>> entityComponentMap = new Dictionary<int, HashSet<Type>>();
|
||||||
|
|
||||||
|
private ComponentStorage<TComponent> Lookup<TComponent>() where TComponent : struct
|
||||||
|
{
|
||||||
|
// TODO: is it possible to optimize this?
|
||||||
|
if (!storages.ContainsKey(typeof(TComponent)))
|
||||||
|
{
|
||||||
|
storages.Add(typeof(TComponent), new ComponentStorage<TComponent>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return storages[typeof(TComponent)] as ComponentStorage<TComponent>;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Some<TComponent>() where TComponent : struct
|
||||||
|
{
|
||||||
|
return Lookup<TComponent>().Any();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Has<TComponent>(int entityID) where TComponent : struct
|
||||||
|
{
|
||||||
|
return Lookup<TComponent>().Has(entityID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ref readonly TComponent Get<TComponent>(int entityID) where TComponent : struct
|
||||||
|
{
|
||||||
|
return ref Lookup<TComponent>().Get(entityID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Set<TComponent>(int entityID, in TComponent component) where TComponent : struct
|
||||||
|
{
|
||||||
|
Lookup<TComponent>().Set(entityID, component);
|
||||||
|
|
||||||
|
if (!entityComponentMap.ContainsKey(entityID))
|
||||||
|
{
|
||||||
|
entityComponentMap.Add(entityID, new HashSet<Type>());
|
||||||
|
}
|
||||||
|
|
||||||
|
entityComponentMap[entityID].Add(typeof(TComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<Entity> ReadEntities<TComponent>() where TComponent : struct
|
||||||
|
{
|
||||||
|
return Lookup<TComponent>().AllEntities();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<TComponent> ReadComponents<TComponent>() where TComponent : struct
|
||||||
|
{
|
||||||
|
return Lookup<TComponent>().AllComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove<TComponent>(int entityID) where TComponent : struct
|
||||||
|
{
|
||||||
|
Lookup<TComponent>().Remove(entityID);
|
||||||
|
|
||||||
|
entityComponentMap[entityID].Remove(typeof(TComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnEntityDestroy(int entityID)
|
||||||
|
{
|
||||||
|
if (entityComponentMap.ContainsKey(entityID))
|
||||||
|
{
|
||||||
|
foreach (var type in entityComponentMap[entityID])
|
||||||
|
{
|
||||||
|
storages[type].Remove(entityID);
|
||||||
|
}
|
||||||
|
|
||||||
|
entityComponentMap.Remove(entityID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
namespace MoonTools.ECS;
|
||||||
|
|
||||||
|
internal abstract class ComponentStorage
|
||||||
|
{
|
||||||
|
public abstract void Remove(int entityID);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : struct
|
||||||
|
{
|
||||||
|
private int nextID;
|
||||||
|
private IDStorage idStorage = new IDStorage();
|
||||||
|
private readonly Dictionary<int, int> entityIDToStorageIndex = new Dictionary<int, int>();
|
||||||
|
private Entity[] storageIndexToEntities = new Entity[64];
|
||||||
|
private TComponent[] components = new TComponent[64];
|
||||||
|
|
||||||
|
public bool Any()
|
||||||
|
{
|
||||||
|
return nextID > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Has(int entityID)
|
||||||
|
{
|
||||||
|
return entityIDToStorageIndex.ContainsKey(entityID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ref readonly TComponent Get(int entityID)
|
||||||
|
{
|
||||||
|
return ref components[entityIDToStorageIndex[entityID]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Set(int entityID, in TComponent component)
|
||||||
|
{
|
||||||
|
if (!entityIDToStorageIndex.ContainsKey(entityID))
|
||||||
|
{
|
||||||
|
var index = nextID;
|
||||||
|
nextID += 1;
|
||||||
|
|
||||||
|
if (index >= components.Length)
|
||||||
|
{
|
||||||
|
Array.Resize(ref components, components.Length * 2);
|
||||||
|
Array.Resize(ref storageIndexToEntities, storageIndexToEntities.Length * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
entityIDToStorageIndex[entityID] = index;
|
||||||
|
storageIndexToEntities[index] = new Entity(entityID);
|
||||||
|
}
|
||||||
|
|
||||||
|
components[entityIDToStorageIndex[entityID]] = component;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Remove(int entityID)
|
||||||
|
{
|
||||||
|
if (entityIDToStorageIndex.ContainsKey(entityID))
|
||||||
|
{
|
||||||
|
var storageIndex = entityIDToStorageIndex[entityID];
|
||||||
|
entityIDToStorageIndex.Remove(entityID);
|
||||||
|
|
||||||
|
var lastElementIndex = nextID - 1;
|
||||||
|
|
||||||
|
// move a component into the hole to maintain contiguous memory
|
||||||
|
if (entityIDToStorageIndex.Count > 0 && storageIndex != lastElementIndex)
|
||||||
|
{
|
||||||
|
var lastEntity = storageIndexToEntities[lastElementIndex];
|
||||||
|
|
||||||
|
entityIDToStorageIndex[lastEntity.ID] = storageIndex;
|
||||||
|
storageIndexToEntities[storageIndex] = lastEntity;
|
||||||
|
components[storageIndex] = components[lastElementIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
nextID -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
nextID = 0;
|
||||||
|
entityIDToStorageIndex.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<Entity> AllEntities()
|
||||||
|
{
|
||||||
|
return new ReadOnlySpan<Entity>(storageIndexToEntities, 0, nextID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<TComponent> AllComponents()
|
||||||
|
{
|
||||||
|
return new ReadOnlySpan<TComponent>(components, 0, nextID);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
namespace MoonTools.ECS;
|
||||||
|
|
||||||
|
public struct Entity
|
||||||
|
{
|
||||||
|
public int ID { get; }
|
||||||
|
|
||||||
|
internal Entity(int id)
|
||||||
|
{
|
||||||
|
ID = id;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
namespace MoonTools.ECS;
|
||||||
|
|
||||||
|
public abstract class EntityComponentReader
|
||||||
|
{
|
||||||
|
internal EntityStorage EntityStorage;
|
||||||
|
internal ComponentDepot ComponentDepot;
|
||||||
|
|
||||||
|
internal void RegisterEntityStorage(EntityStorage entityStorage)
|
||||||
|
{
|
||||||
|
EntityStorage = entityStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RegisterComponentDepot(ComponentDepot componentDepot)
|
||||||
|
{
|
||||||
|
ComponentDepot = componentDepot;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ReadOnlySpan<Entity> ReadEntities<TComponent>() where TComponent : struct
|
||||||
|
{
|
||||||
|
return ComponentDepot.ReadEntities<TComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ReadOnlySpan<TComponent> ReadComponents<TComponent>() where TComponent : struct
|
||||||
|
{
|
||||||
|
return ComponentDepot.ReadComponents<TComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool Has<TComponent>(in Entity entity) where TComponent : struct
|
||||||
|
{
|
||||||
|
return ComponentDepot.Has<TComponent>(entity.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool Some<TComponent>() where TComponent : struct
|
||||||
|
{
|
||||||
|
return ComponentDepot.Some<TComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TComponent Get<TComponent>(in Entity entity) where TComponent : struct
|
||||||
|
{
|
||||||
|
return ComponentDepot.Get<TComponent>(entity.ID);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
namespace MoonTools.ECS;
|
||||||
|
|
||||||
|
internal class EntityStorage
|
||||||
|
{
|
||||||
|
public IDStorage idStorage = new IDStorage();
|
||||||
|
|
||||||
|
public Entity Create()
|
||||||
|
{
|
||||||
|
return new Entity(idStorage.NextID());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Destroy(in Entity entity)
|
||||||
|
{
|
||||||
|
idStorage.Release(entity.ID);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
namespace MoonTools.ECS;
|
||||||
|
|
||||||
|
internal class IDStorage
|
||||||
|
{
|
||||||
|
private int nextID = 0;
|
||||||
|
|
||||||
|
private readonly Stack<int> availableIDs = new Stack<int>();
|
||||||
|
|
||||||
|
public int NextID()
|
||||||
|
{
|
||||||
|
if (availableIDs.Count > 0)
|
||||||
|
{
|
||||||
|
return availableIDs.Pop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var id = nextID;
|
||||||
|
nextID += 1;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Release(int id)
|
||||||
|
{
|
||||||
|
availableIDs.Push(id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace MoonTools.ECS;
|
||||||
|
|
||||||
|
public abstract class Renderer : EntityComponentReader
|
||||||
|
{
|
||||||
|
public abstract void Draw(TimeSpan delta);
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
namespace MoonTools.ECS;
|
||||||
|
|
||||||
|
public abstract class System : EntityComponentReader
|
||||||
|
{
|
||||||
|
public abstract void Update(TimeSpan delta);
|
||||||
|
|
||||||
|
protected Entity CreateEntity()
|
||||||
|
{
|
||||||
|
return EntityStorage.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Set<TComponent>(in Entity entity, in TComponent component) where TComponent : struct
|
||||||
|
{
|
||||||
|
ComponentDepot.Set<TComponent>(entity.ID, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Remove<TComponent>(in Entity entity) where TComponent : struct
|
||||||
|
{
|
||||||
|
ComponentDepot.Remove<TComponent>(entity.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Destroy(in Entity entity)
|
||||||
|
{
|
||||||
|
ComponentDepot.OnEntityDestroy(entity.ID);
|
||||||
|
EntityStorage.Destroy(entity);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
namespace MoonTools.ECS;
|
||||||
|
|
||||||
|
public class World
|
||||||
|
{
|
||||||
|
private readonly List<System> systems = new List<System>();
|
||||||
|
private readonly List<Renderer> renderers = new List<Renderer>();
|
||||||
|
private EntityStorage EntityStorage { get; } = new EntityStorage();
|
||||||
|
private ComponentDepot ComponentDepot { get; } = new ComponentDepot();
|
||||||
|
|
||||||
|
public void AddSystem(System system)
|
||||||
|
{
|
||||||
|
system.RegisterEntityStorage(EntityStorage);
|
||||||
|
system.RegisterComponentDepot(ComponentDepot);
|
||||||
|
systems.Add(system);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRenderer(Renderer renderer)
|
||||||
|
{
|
||||||
|
renderer.RegisterEntityStorage(EntityStorage);
|
||||||
|
renderer.RegisterComponentDepot(ComponentDepot);
|
||||||
|
renderers.Add(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(TimeSpan delta)
|
||||||
|
{
|
||||||
|
foreach (var system in systems)
|
||||||
|
{
|
||||||
|
system.Update(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw(TimeSpan delta)
|
||||||
|
{
|
||||||
|
foreach (var renderer in renderers)
|
||||||
|
{
|
||||||
|
renderer.Draw(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue