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