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