initial commit

pull/1/head
cosmonaut 2022-03-04 18:01:44 -08:00
commit 0f4052728e
12 changed files with 358 additions and 0 deletions

14
.editorconfig Normal file
View File

@ -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

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
bin/
obj/

9
MoonTools.ECS.csproj Normal file
View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

76
src/ComponentDepot.cs Normal file
View File

@ -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);
}
}
}

89
src/ComponentStorage.cs Normal file
View File

@ -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);
}
}

11
src/Entity.cs Normal file
View File

@ -0,0 +1,11 @@
namespace MoonTools.ECS;
public struct Entity
{
public int ID { get; }
internal Entity(int id)
{
ID = id;
}
}

View File

@ -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);
}
}

16
src/EntityStorage.cs Normal file
View File

@ -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);
}
}

27
src/IDStorage.cs Normal file
View File

@ -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);
}
}

6
src/Renderer.cs Normal file
View File

@ -0,0 +1,6 @@
namespace MoonTools.ECS;
public abstract class Renderer : EntityComponentReader
{
public abstract void Draw(TimeSpan delta);
}

27
src/System.cs Normal file
View File

@ -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);
}
}

39
src/World.cs Normal file
View File

@ -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);
}
}
}