More Dynamic system

This commit is contained in:
Booklordofthedings 2024-11-14 15:35:33 +01:00
parent 9bf9452cbe
commit 55077fc7c3
13 changed files with 420 additions and 386 deletions

View file

@ -1,109 +0,0 @@
namespace Theater_ECS;
using System;
using System.Threading;
using System.Collections;
class ComponentManager
{
private Monitor _Lock = new .() ~ delete _;
private int_cosize _ComponentSize = -1;
private readonly int _IndexSize = sizeof(int_cosize);
private int_cosize _Next = 0;
private List<uint8> _Data = null ~ delete _;
private List<int_cosize> _Available = new .() ~ delete _;
private HashSet<int_cosize> _Deleted = new .() ~ delete _;
///Make the component manager useable for a specific type of component
public void Initialize<T>(int_cosize count) where T : struct
{
_ComponentSize = sizeof(T);
_Data = new .( //So that we dont have to realloc as frequently
(_IndexSize + _ComponentSize) * count
);
}
///Creates space for a new component instance and returns a pointer to it
public (Component, void*) Create(Entity owner)
{
if (_Available.Count > 0)
{
var idx = _Available.PopFront();
_Deleted.Remove(idx);
*(int_cosize*)(void*)&_Data[idx * (_ComponentSize + _IndexSize)] = (.)owner;
void* toReturn = (void*)&_Data[idx * (_ComponentSize + _IndexSize) + _IndexSize];
return (
idx,
toReturn
);
}
else
{
Grow();
*(int_cosize*)(void*)(_Data.Ptr + (_Next * (_ComponentSize + _IndexSize))) = (.)owner;
//This calculates to the next biggest position + the offset of the index
void* toReturn = (void*)(_Data.Ptr + (_Next * (_ComponentSize + _IndexSize) + _IndexSize));
return (
_Next++,
toReturn
);
}
}
///Remove an entry, and returns a pointer to the removed object, if you want to
public Result<void*> Remove(int_cosize idx)
{
if (idx >= _Next || _Deleted.Contains(idx))
return .Err;
*(int_cosize*)(void*)&_Data[idx * (_ComponentSize + _IndexSize)] = -1;
_Deleted.Add(idx);
return .Ok(
(void*)(_Data.Ptr + idx * (_ComponentSize + _IndexSize) + _IndexSize)
);
}
public int_cosize Count
{
get
{
return (.)(_Data.Count / (_ComponentSize + _IndexSize));
}
}
public Span<(Entity, T)> GetAll<T>() where T : struct
{
return .((.)(void*)_Data.Ptr, Count);
}
[Unchecked]
public T* Get<T>(int_cosize idx) where T : struct
{
return (T*)(void*)(_Data.Ptr + idx * (_ComponentSize + _IndexSize) + _IndexSize);
}
[Unchecked]
public void GetEntities(List<Entity> list)
{
var ec = (_Data.Count / (_ComponentSize + _IndexSize));
list.GrowUninitialized(ec);
for (int i < ec)
*(list.Ptr + (i)) = *(Entity*)(void*)(_Data.Ptr +i * (_ComponentSize + _IndexSize));
}
public void Use() => _Lock.Enter();
public void StopUsing() => _Lock.Exit();
public void Grow(int_cosize amount = 1)
{ //Make more size useable
_Data.GrowUninitialized((_ComponentSize + _IndexSize) * amount);
}
}

52
src/ComponentRegistry.bf Normal file
View file

@ -0,0 +1,52 @@
namespace Theater_ECS;
using Theater_ECS.Containers;
using System;
using System.Collections;
typealias Component = int_cosize;
class ComponentRegistry
{
private Dictionary<String, Component> _componentLookup = new .() ~ DeleteDictionaryAndKeys!(_);
public List<PagedSparseSet> Components = new .() ~ DeleteContainerAndItems!(_);
///Registers a component based on a name and a size of that component
public Result<Component> RegisterComponent(StringView name, int_cosize size)
{
///Return the existing component if it matches
if(_componentLookup.ContainsKeyAlt<StringView>(name)
&& Components[_componentLookup[scope .(name)]].[Friend]_packedEntities.[Friend]_size == size)
return _componentLookup[scope .(name)];
else if(_componentLookup.ContainsKeyAlt<StringView>(name))
{
return .Err; //There already is a component of the same name but with a different size
}
//Attempt to find a nulled out entry
//This could be made faster but thats unlikely to ever matter
for(var i in ref Components)
if(i == null)
{
i = new .(size);
_componentLookup.Add(new .(name), (.)@i.Index);
return (Component)@i.Index;
}
//Make a new one and return it
Components.Add(new .(size));
_componentLookup.Add(new .(name), (.)(Components.Count-1));
return (.)(Components.Count-1);
}
///Retrieves the id of a component
public Result<Component> GetComponent(StringView name)
{
if(!_componentLookup.ContainsKeyAlt<StringView>(name))
return .Err;
return _componentLookup[scope .(name)];
}
}

View file

@ -1,19 +1,19 @@
namespace Theater_ECS;
namespace Theater_ECS.Containers;
using System;
using System.Collections;
class PagedSparseSet<T> where T : struct
class PagedSparseSet
{
public const uint32 PageSize = 4096;
private List<List<Entity>> _sparse = new .()~ DeleteContainerAndItems!(_);
private List<(Entity, T)> _packed = new .() ~ delete _;
private List<T> _packedEntities = new .() ~ delete _;
private List<Entity> _packed = new .() ~ delete _;
private UList _packedEntities ~ delete _;
public (Entity, T) this[Entity e]
public this(int_cosize size)
{
public get => _packed[_sparse[e.Index/PageSize][e.Index % PageSize].Index];
_packedEntities = new .(size);
}
public bool Contains(Entity e)
@ -21,10 +21,11 @@ class PagedSparseSet<T> where T : struct
return _sparse[e.Index/PageSize][e.Index % PageSize] != Entity.Null;
}
public void Add(Entity e, T toAdd)
public void Add(Entity e, void* toAdd)
{
EnsureLoadedPage(e.Index/PageSize);
_packed.Add((e, toAdd));
_packed.Add(e);
_packedEntities.Add(toAdd);
_sparse[e.Index/PageSize][e.Index % PageSize] = (.)_packed.Count-1;
}
@ -39,22 +40,25 @@ class PagedSparseSet<T> where T : struct
_packed[_sparse[e.Index/PageSize][e.Index % PageSize].Index] = b;
_packed.Back = toRm;
_sparse[b.0.Index/PageSize][b.0.Index % PageSize] = Entity(e.Index, b.0.Version);
_sparse[b.Index/PageSize][b.Index % PageSize] = Entity(e.Index, b.Version);
_sparse[e.Index/PageSize][e.Index % PageSize] = Entity.Null;
_packed.PopBack();
}
public T* Get(Entity e)
[Inline]
public void* Get(Entity e)
{
return &_packed[_sparse[e.Index/PageSize][e.Index % PageSize].Index].1;
return _packedEntities[(.)(_sparse[e.Index/PageSize][e.Index % PageSize].Index)];
}
public Span<(Entity, T)> GetAll()
public Span<Entity> GetAll()
{
return _packed;
}
public int_cosize Count() => (.)_packed.Count;
///Ensure that a page exist and is loaded
public void EnsureLoadedPage(uint32 page)
{

162
src/Containers/UList.bf Normal file
View file

@ -0,0 +1,162 @@
namespace Theater_ECS.Containers;
using System;
using System.Collections;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Threading;
using System.Reflection;
//A variant of list, which has no type associated with it
class UList
{
private const int_cosize _defaultCapacity = 4;
private const int_cosize _max = int_cosize.MaxValue;
private void* _items; //The raw data contained by this object
private readonly int_cosize _sizeof; //The size of a single object
private int_cosize _size; //How much data is actually used
private int_cosize _alloc; //How much data is actually allocated
/// Adds an item to the back of the list.
public void Add<T>(T* toAdd)
{
if (_size == _alloc)
Realloc(_alloc * 2);
Internal.MemCpy(&((uint8*)_items)[_size], toAdd, sizeof(T));
_size += _sizeof;
}
/// Adds an item to the back of the list.
public void Add(void* toAdd)
{
if (_size == _alloc)
Realloc(_alloc * 2);
Internal.MemCpy(&((uint8*)_items)[_size], toAdd, _sizeof);
_size += _sizeof;
}
public int_cosize Capacity
{
[Inline]
get => (_alloc / _sizeof);
set
{
if (value != _alloc / _sizeof)
Realloc(value * _sizeof);
}
}
public int_cosize Count
{
[Inline]
get => (_size / _sizeof);
}
public void* this[int_cosize index]
{
[Checked]
get
{
Runtime.Assert(index < (_size/_sizeof));
return GetItem(_items, index * _sizeof);
}
[Unchecked, Inline]
get
{
return GetItem(_items, index* _sizeof);
}
[Checked]
set
{
Runtime.Assert(index < (_size/_sizeof));
MemCpy(GetItem(_items, index * _sizeof), value, _sizeof);
}
[Unchecked, Inline]
set
{
MemCpy(GetItem(_items, index * _sizeof), value, _sizeof);
}
}
private void* GetItem(void* i, int32 index) => (uint8*)i + index;
private void MemCpy(void* dest, void* src, int lenght) => Internal.MemCpy(dest, src, lenght);
#region Initializers
public this(int_cosize size)
{
_sizeof = size;
Realloc(size * _defaultCapacity);
_alloc = size * _defaultCapacity;
}
public this(int_cosize size, int_cosize capacity)
{
_sizeof = size;
Debug.Assert((uint)capacity <= (uint)_max);
Realloc(size * capacity);
_alloc = size * capacity;
}
public ~this()
{
var items = _items;
#if BF_ENABLE_REALTIME_LEAK_CHECK
// To avoid scanning items being deleted
_items = null;
Interlocked.Fence();
#endif
Free(items);
}
#endregion
#region MemoryAllocation
private void Realloc(int_cosize newSize)
{
void* oldAlloc = null;
if (newSize > 0)
{
void* newItems = Alloc(newSize);
if (_size > 0)
Internal.MemCpy(newItems, _items, _size);
oldAlloc = _items;
_items = (.)newItems;
_alloc = newSize;
}
else
{
oldAlloc = _items;
_items = null;
_alloc = 0;
}
Free(oldAlloc);
return;
}
private void* Alloc(int_cosize size)
{
return Internal.AllocRawArrayUnmarked<uint8>(size);
}
private void Free(void* val)
{
delete val;
}
#endregion
}
/*
*/

View file

@ -1,102 +1,38 @@
namespace Theater_ECS;
using Theater_ECS.Containers;
using System;
using System.Collections;
//public typealias Entity = int_cosize;
public typealias Component = int_cosize;
class ECS
{
public int_cosize MaxComponents;
private EntityRegister _registry = new .() ~ delete _;
private int_cosize _ComponentsNext = 0;
private List<ComponentManager> _ComponentsPacked = new .() ~ DeleteContainerAndItems!(_);
private Dictionary<Type, Component> _ComponentMap = new .() ~ delete _;
///Create a new Entity
public Entity Entity_Create() => _registry.Create();
private int_cosize _EntityNext = 0;
private List<List<int_cosize>> _EntityList = new .() ~ DeleteContainerAndItems!(_);
private Queue<Entity> _EntityAvailable = new .() ~ delete _;
private HashSet<Entity> _DeletedEntities = new .() ~ delete _;
///Delete an Entity
public void Entity_Delete(Entity e) => _registry.Remove(e);
public Component RegisterComponent<T>(int_cosize count) where T : struct
{
_ComponentsPacked.Add(new ComponentManager()..Initialize<T>(count));
_EntityList.Add(new .(MaxComponents));
_ComponentMap.Add(typeof(T), _ComponentsNext);
return _ComponentsNext++;
}
///Returns wether the given entity is still alive
public bool Entity_Alive(Entity e) => _registry.IsAlive(e);
public Component GetComponent<T>()
{
if(_ComponentMap.ContainsKey(typeof(T)))
return _ComponentMap[typeof(T)];
return -1;
}
//TODO: Entity_GetAllComponents
public Entity CreateEntity()
{
if(_EntityAvailable.Count > 0)
{
var e = _EntityAvailable.PopFront();
_DeletedEntities.Remove(e);
return e;
}
else
{
for(var i in _EntityList)
i.Add(-1);
return (.)_EntityNext++;
}
}
public void RemoveEntity(Entity e)
{
if(e >= (.)_EntityNext || _DeletedEntities.Contains(e))
return;
private ComponentRegistry _compRegistry = new .() ~ delete _;
_DeletedEntities.Add(e);
_EntityAvailable.Add(e);
///Registers a component but hides some of the work behind generics
public Component RegisterComponent<T>() => _compRegistry.RegisterComponent(typeof(T).GetFullName(.. scope .()), sizeof(T));
public Component RegisterComponent(StringView name, int_cosize size) => _compRegistry.RegisterComponent(name, size);
for(int i < _EntityList.Count)
{
if(_EntityList[i][(.)e] != -1)
{
_ComponentsPacked[i].Remove(_EntityList[i][(.)e]);
_EntityList[i][(.)e] = -1;
}
}
}
///Retrieves the id of a component
public Result<Component> GetComponent<T>() => _compRegistry.GetComponent(typeof(T).GetFullName(.. scope .()));
public Result<Component> GetComponent(StringView name) => _compRegistry.GetComponent(name);
public Result<void*> AddComponentToEntity(Entity e, Component c)
{
if(e >= (.)_EntityNext || _DeletedEntities.Contains(e))
return .Err;
public int_cosize GetComponentCount(Component c) => _compRegistry.Components[c].Count();
var res = _ComponentsPacked[c].Create(e);
_EntityList[c][(.)e] = res.0;
return res.1;
}
public Span<Entity> GetComponentEntities(Component c) => _compRegistry.Components[c].GetAll();
public Result<void*> RemoveComponentFromEntity(Entity e, Component c)
{
if(e >= (.)_EntityNext || _DeletedEntities.Contains(e))
return .Err;
return _ComponentsPacked[c].Remove((.)e);
}
public void GetEntityFromComponent(Component c, List<Entity> e)
{
_ComponentsPacked[c].[Unchecked]GetEntities(e);
}
public void ExecuteSystem<A>(delegate void(ECS, Entity, ref A) func) where A : struct
{
var idx = GetComponent<A>();
var data = _ComponentsPacked[idx].GetAll<A>();
for(var i in ref data)
func.Invoke(this, i.0, ref i.1);
}
[Inline] public void* GetComponentData(Entity e, Component c) => _compRegistry.Components[c].Get(e);
}

View file

@ -5,7 +5,7 @@ using System;
public struct Entity : uint32
{
public static readonly Entity Null => 0x000FFFFF;
public static readonly uint32 VersionMask => 0xFFF00000;
public static readonly Entity VersionMask => (.)0xFFF00000;
public static readonly uint32 IndexMask => 0x000FFFFF;
public static readonly uint8 VersionOffset => 20;

View file

@ -14,8 +14,10 @@ class EntityRegister
{
if(_available == 0)
{
#if DEBUG
if(_entities.Count + 1 > Entity.IndexMask)
return .Null; //It would be beneficial if we could avoid this check
#endif
return .((.)_entities..Add(.((.)_entities.Count, 0)).Count, 0);
}
else

View file

@ -1,13 +0,0 @@
namespace Theater_ECS.Example;
class MoverSystem : System
{
[SystemMethod("MoveItems", typeof(Velocity), typeof(Position))]
public void MoveItems(ref Velocity v,ref Position p)
{
p.x = v.x + p.x;
p.y = v.y = p.y;
}
}

View file

@ -6,4 +6,10 @@ struct Position
{
public float x = (.)gRand.NextI32() % 25;
public float y = (.)gRand.NextI32() % 25;
public void Update(float a, float b) mut
{
x += a;
y += b;
}
}

View file

@ -1,5 +1,6 @@
namespace Theater_ECS;
using Theater_ECS.Example;
using Theater_ECS.Containers;
using System;
@ -7,37 +8,32 @@ using System.Collections;
class Program
{
public static uint64 counter = 0;
public static void Main()
{
EntityRegister e = scope .();
PagedSparseSet<Position> p = scope .();
PagedSparseSet<Velocity> v =scope .();
List<Entity> es = new .();
defer delete es;
for(int i < 1000000)
ECS e = scope .();
MovementSystem s = scope .();
s.RegisterSystem(e);
for(int32 i < 500000)
{
var ent = e.Create();
p.Add(ent, .());
v.Add(ent, .());
es.Add(ent);
var entity = e.Entity_Create();
e.[Friend]_compRegistry.Components[s.[Friend]_Components[0]].Add(entity, &Position());
e.[Friend]_compRegistry.Components[s.[Friend]_Components[1]].Add(entity, &Velocity());
/*if((gRand.NextI32() % 3) == 1)
e.[Friend]_compRegistry.Components[s.[Friend]_Components[2]].Add(entity, &waste());*/
}
System.Diagnostics.Stopwatch watch = scope .();
for(int a < 10)
for(int o < 10)
{
watch.Start();
for(int i < 10)
for(int ii < 400)
{
var pos = p.GetAll();
var vel = v.GetAll();
for(var ps in pos)
{
ps.1.x += vel[@ps.Index].1.x;
ps.1.y += vel[@ps.Index].1.y;
}
s.RunSystem(e);
}
Console.WriteLine(scope $"{watch.ElapsedMilliseconds}ms");
watch.Stop();
@ -45,9 +41,55 @@ class Program
}
Console.Read();
}
}
public struct waste
struct waste
{
private float[10] a;
uint32[4] asd;
}
/*
A definition of the functionality and usage of the Theater-ECS
Goals:
- Fast
The target is handling the maximum alive at one point amount ~1 048 000 components in a single frame
- Use what you need
Allocations should be kept at a minimum (Paging), and components and systems should not be tracked unless they
are used and the user explicitly asks for them
- Dynamic
The ecs should by default be fully dynamic, meaning supporting abitrary systems and components.
Also Types should not be hardlocked to a single component, so that one type can be used for multiple
components.
Components:
- ECS
-CreateEntity
Create a new entity and return it
-DeleteEntity
Delete the input identity
-IsAlive
Check wether a given identity is alive
-GetAllData
Return Span(ComponentId, void*) containing every component that has data on this entity
-RegisterComponent
Create a new container and return the id of the component
-GetComponentId
Returns the component id of a given input
-AddComponentToEntity
creates a component data for the entry and returns a pointer to it
-RemoveComponentFromEntity
Deataches a component from an entity
-GetComponentData
Returns a Span<Entity, ComponentData> for a given component
-RegisterSystem
Add a system the the ecs object
-RunSystem
Run a single run of a system without registering it
-RunSystems
Run all available systems
-GetSystemEnumerator
Retrieve an IEnumerable for a given set of component IDS
*/

View file

@ -1,15 +1,96 @@
namespace Theater_ECS;
using Theater_ECS.Containers;
using Theater_ECS.Example;
using System;
using System.Collections;
abstract class System
{
protected Dictionary<Type, Component> _ComponentMap = new .() ~ delete _;
protected List<Entity> _Target = new .(100) ~ delete _;
private List<Component> _Components = new .(10) ~ delete _;
public abstract void RunSystem(ECS ecs);
///Get the compoent id and cache it inside of this system
public void RegisterComponent<T>(ECS ecs) where T : struct => _Components.Add(ecs.RegisterComponent<T>());
public abstract bool IntializeSystem(ECS ecs);
public abstract void RegisterSystem(ECS ecs);
public virtual void Run(void* p1) { };
public virtual void Run(void* p1, void* p2) { };
public virtual void Run(void* p1, void* p2, void* p3) { };
public void RunSystem(ECS ecs)
{
int_cosize count = int_cosize.MaxValue;
Component comp = _Components[0];
for(var c in _Components)
if(ecs.GetComponentCount(c) < count)
{
count = ecs.GetComponentCount(c);
comp = c;
}
//We now have the list of entities to loop through
Span<Entity> entities = ecs.GetComponentEntities(comp);
UList main = ecs.[Friend]_compRegistry.Components[comp].[Friend]_packedEntities;
switch(_Components.Count)
{
case 1:
case 2:
if(comp == _Components[0])
{
var cun = entities.Length-1;
for(int ii < cun)
{
this.Run(
main[(.)entities[ii].Index],
ecs.GetComponentData(entities[ii], _Components[1]));
}
}
else
{
}
case 3:
var cun = entities.Length-1;
for(int ii < cun)
{
this.Run(
ecs.GetComponentData(entities[ii], _Components[0]),
ecs.GetComponentData(entities[ii], _Components[1]),
ecs.GetComponentData(entities[ii], _Components[2]));
}
}
}
}
class MovementSystem : System
{
public override void RegisterSystem(ECS ecs)
{
RegisterComponent<Position>(ecs);
//RegisterComponent<waste>(ecs);
RegisterComponent<Velocity>(ecs);
}
public override void Run(void* pos, void* vel)
{
((Position*)pos).x += ((Velocity*)vel).x;
((Position*)pos).y += ((Velocity*)vel).y;
}
public override void Run(void* pos, void* waste, void* vel)
{
((Position*)pos).x += ((Velocity*)vel).x;
((Position*)pos).y += ((Velocity*)vel).y;
}
}

View file

@ -1,117 +0,0 @@
namespace Theater_ECS;
using System;
using System.Reflection;
using System.Collections;
[AttributeUsage(.Method)]
struct SystemMethodAttribute : Attribute, IOnTypeInit
{
private StringView _Name;
private Span<Type> _Types;
public this(StringView name, params Span<Type> types)
{
_Name = name;
_Types = types;
}
[Comptime]
public void OnTypeInit(Type type, Self* prev)
{
//InitializeFunction
Compiler.EmitTypeBody(type, scope $"""
public override bool IntializeSystem(ECS ecs)
\{
{InitializeSystemTypes( .. scope .())}
return true;
\}
""");
Compiler.EmitTypeBody(type, scope $"""
[System.DisableChecks]
public override void RunSystem(ECS ecs)
\{
Lock(ecs);
Component lowest = System.int_cosize.MaxValue;
System.int_cosize lowestAmount = System.int_cosize.MaxValue;
for(var c in _ComponentMap)
\{
if(ecs.[System.Friend]_ComponentsPacked[c.value].Count < lowestAmount)
\{
lowest = c.value;
lowestAmount = ecs.[System.Friend]_ComponentsPacked[c.value].Count;
\}
\}
ecs.GetEntityFromComponent(lowest, _Target);
{CacheComponentData(.. scope .())}
for(var e in _Target)
\{
{_Name}(
{GetComponentData(.. scope .())}
);
\}
_Target.Clear();
Unlock(ecs);
\}
private void Lock(ECS ecs)
\{
for(var i in _ComponentMap)
ecs.[System.Friend]_ComponentsPacked[i.value].Use();
\}
private void Unlock(ECS ecs)
\{
for(var i in _ComponentMap)
ecs.[System.Friend]_ComponentsPacked[i.value].StopUsing();
\}
""");
}
private void InitializeSystemTypes(String buffer)
{
for(var i in _Types)
{
buffer.Append(scope $"""
if(ecs.GetComponent<{i.GetName( .. scope .())}>() == -1)
return false;
_ComponentMap.Add(typeof({i.GetName(.. scope .())}), ecs.GetComponent<{i.GetName( .. scope .())}>());
""");
}
}
private void GetComponentData(String buffer)
{
for(var i in _Types)
{
buffer.Append(scope $"""
ref *ecs.[System.Friend]_ComponentsPacked[_{i.GetName(.. scope .())}].Get<{i.GetName(.. scope .())}>(ecs.[System.Friend]_EntityList[_{i.GetName(.. scope .())}][(.)e])
""");
if(@i.Index+1 < _Types.Length)
buffer.Append(",\n");
}
}
private void CacheComponentData(String buffer)
{
for(var i in _Types)
{
buffer.Append(scope $"Component _{i.GetName(.. scope .())} = _ComponentMap[typeof({i.GetName(.. scope .())})];\n");
}
}
}

View file

@ -1,12 +0,0 @@
namespace Theater_ECS;
using System;
using System.Collections;
class World
{
EntityRegister Registry = new .() ~ delete _;
private Dictionary<Type, uint32> _components = new .() ~ delete _;
private List<Variant> _componentStorage = new .() ~ DeleteContainerAndDisposeItems!(_);
}