Skip to content

Instantly share code, notes, and snippets.

@f-space
Last active May 31, 2020 09:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save f-space/f17529620fd772117b85c1b7208226ad to your computer and use it in GitHub Desktop.
Save f-space/f17529620fd772117b85c1b7208226ad to your computer and use it in GitHub Desktop.
A minimal ECS implementation with Unity-like API for a blog entry.
using System;
using System.Collections.Generic;
public readonly struct Entity
{
public readonly int ID;
public Entity(int id) => this.ID = id;
}
public interface IComponentData { }
public abstract partial class SystemBase
{
public void Update() => OnUpdate();
protected abstract void OnUpdate();
}
public sealed partial class EntityManager
{
private const int InitialCapacity = 8;
private int nextID;
private bool[] entities = new bool[InitialCapacity];
public int EntityCapacity => this.entities.Length;
public Entity CreateEntity()
{
var entity = new Entity(this.nextID++);
this.entities = EnsureCapacity(this.entities, entity.ID);
this.entities[entity.ID] = true;
return entity;
}
public void DestroyEntity(Entity entity)
{
this.entities[entity.ID] = false;
}
public bool Exists(Entity entity) => this.entities[entity.ID];
private static T[] EnsureCapacity<T>(T[] array, int index)
{
if (index < array.Length) return array;
var capacity = array.Length * 2;
while (index >= capacity) capacity *= 2;
var newArray = new T[capacity];
Array.Copy(array, newArray, array.Length);
return newArray;
}
}
public sealed partial class EntityManager
{
internal class ComponentArray<T>
{
public bool[] Used;
public T[] Data;
public ComponentArray(int capacity)
{
this.Used = new bool[capacity];
this.Data = new T[capacity];
}
}
private readonly Dictionary<Type, object> components = new Dictionary<Type, object>();
public void AddComponent<T>(Entity entity) where T : unmanaged, IComponentData
{
VerifyEntityExists(entity);
var array = GetComponentArray<T>();
if (EntityHasComponent(array, entity))
{
throw new InvalidOperationException("component already exists");
}
array.Used = EnsureCapacity(array.Used, entity.ID);
array.Data = EnsureCapacity(array.Data, entity.ID);
array.Used[entity.ID] = true;
array.Data[entity.ID] = default;
}
public void RemoveComponent<T>(Entity entity) where T : unmanaged, IComponentData
{
VerifyEntityExists(entity);
var array = GetComponentArray<T>();
if (!EntityHasComponent(array, entity))
{
throw new InvalidOperationException("component not exist");
}
array.Used[entity.ID] = false;
}
public bool HasComponent<T>(Entity entity) where T : unmanaged, IComponentData
{
VerifyEntityExists(entity);
return EntityHasComponent(GetComponentArray<T>(), entity);
}
private void VerifyEntityExists(Entity entity)
{
if (!Exists(entity))
{
throw new ArgumentException("entity already destroyed", nameof(entity));
}
}
internal ComponentArray<T> GetComponentArray<T>()
{
if (this.components.TryGetValue(typeof(T), out var result)) return (ComponentArray<T>)result;
var array = new ComponentArray<T>(this.EntityCapacity);
this.components.Add(typeof(T), array);
return array;
}
internal static bool EntityHasComponent<T>(ComponentArray<T> array, Entity entity)
=> entity.ID < array.Used.Length && array.Used[entity.ID];
}
public sealed partial class EntityManager
{
public T GetComponentData<T>(Entity entity) where T : unmanaged, IComponentData
{
VerifyEntityExists(entity);
var array = GetComponentArray<T>();
if (!EntityHasComponent(array, entity))
{
throw new InvalidOperationException("component not exist");
}
return array.Data[entity.ID];
}
public void SetComponentData<T>(Entity entity, T value) where T : unmanaged, IComponentData
{
VerifyEntityExists(entity);
var array = GetComponentArray<T>();
if (!EntityHasComponent(array, entity))
{
throw new InvalidOperationException("component not exist");
}
array.Data[entity.ID] = value;
}
}
public sealed class World
{
private readonly List<SystemBase> systems = new List<SystemBase>();
public EntityManager EntityManager { get; } = new EntityManager();
public T CreateSystem<T>() where T : SystemBase, new()
{
var system = new T() { World = this };
this.systems.Add(system);
return system;
}
public void Update()
{
foreach (var system in systems) system.Update();
}
}
public abstract partial class SystemBase
{
public World World { get; internal set; }
public EntityManager EntityManager => this.World.EntityManager;
}
public readonly struct ForEachLambdaJobDescription
{
private readonly SystemBase system;
private EntityManager EntityManager => this.system.EntityManager;
internal ForEachLambdaJobDescription(SystemBase system) => this.system = system;
public delegate void VR<T0>(Entity entity, ref T0 component0);
public ForEachLambdaJobDescription ForEach<T0>(VR<T0> action)
where T0 : unmanaged, IComponentData
{
var manager = this.EntityManager;
var capacity = manager.EntityCapacity;
var components0 = manager.GetComponentArray<T0>();
for (var i = 0; i < capacity; i++)
{
var entity = new Entity(i);
if (manager.Exists(entity))
{
if (!EntityManager.EntityHasComponent(components0, entity)) continue;
action(
entity,
ref components0.Data[entity.ID]
);
}
}
return this;
}
public delegate void VRR<T0, T1>(Entity entity, ref T0 component0, ref T1 component1);
public ForEachLambdaJobDescription ForEach<T0, T1>(VRR<T0, T1> action)
where T0 : unmanaged, IComponentData
where T1 : unmanaged, IComponentData
{
var manager = this.EntityManager;
var capacity = manager.EntityCapacity;
var components0 = manager.GetComponentArray<T0>();
var components1 = manager.GetComponentArray<T1>();
for (var i = 0; i < capacity; i++)
{
var entity = new Entity(i);
if (manager.Exists(entity))
{
if (!EntityManager.EntityHasComponent(components0, entity)) continue;
if (!EntityManager.EntityHasComponent(components1, entity)) continue;
action(
entity,
ref components0.Data[entity.ID],
ref components1.Data[entity.ID]
);
}
}
return this;
}
public void Run() { }
}
public abstract partial class SystemBase
{
protected ForEachLambdaJobDescription Entities => new ForEachLambdaJobDescription(this);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment