From e49dca0403c0c5fa00951581e3f9b2b23aec40c9 Mon Sep 17 00:00:00 2001 From: Booklordofthedings Date: Mon, 11 Nov 2024 18:00:37 +0100 Subject: [PATCH] Initial Commit --- src/ComponentManager.bf | 109 ++++++++++++++++++++++++++++++++ src/ECS.bf | 102 ++++++++++++++++++++++++++++++ src/Example/MoverSystem.bf | 13 ++++ src/Example/Position.bf | 9 +++ src/Example/Program.bf | 62 +++++++++++++++++++ src/Example/Velocity.bf | 9 +++ src/System.bf | 15 +++++ src/SystemMethodAttribute.bf | 117 +++++++++++++++++++++++++++++++++++ 8 files changed, 436 insertions(+) create mode 100644 src/ComponentManager.bf create mode 100644 src/ECS.bf create mode 100644 src/Example/MoverSystem.bf create mode 100644 src/Example/Position.bf create mode 100644 src/Example/Program.bf create mode 100644 src/Example/Velocity.bf create mode 100644 src/System.bf create mode 100644 src/SystemMethodAttribute.bf diff --git a/src/ComponentManager.bf b/src/ComponentManager.bf new file mode 100644 index 0000000..8fe5778 --- /dev/null +++ b/src/ComponentManager.bf @@ -0,0 +1,109 @@ +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 _Data = null ~ delete _; + private List _Available = new .() ~ delete _; + private HashSet _Deleted = new .() ~ delete _; + + ///Make the component manager useable for a specific type of component + public void Initialize(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 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() where T : struct + { + return .((.)(void*)_Data.Ptr, Count); + } + + [Unchecked] + public T* Get(int_cosize idx) where T : struct + { + return (T*)(void*)(_Data.Ptr + idx * (_ComponentSize + _IndexSize) + _IndexSize); + } + + [Unchecked] + public void GetEntities(List 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); + } + +} \ No newline at end of file diff --git a/src/ECS.bf b/src/ECS.bf new file mode 100644 index 0000000..e251196 --- /dev/null +++ b/src/ECS.bf @@ -0,0 +1,102 @@ +namespace Theater_ECS; + +using System; +using System.Collections; + +public typealias Entity = int_cosize; + +public typealias Component = int_cosize; + +class ECS +{ + public int_cosize MaxComponents; + + private int_cosize _ComponentsNext = 0; + private List _ComponentsPacked = new .() ~ DeleteContainerAndItems!(_); + private Dictionary _ComponentMap = new .() ~ delete _; + + private Entity _EntityNext = 0; + private List> _EntityList = new .() ~ DeleteContainerAndItems!(_); + private Queue _EntityAvailable = new .() ~ delete _; + private HashSet _DeletedEntities = new .() ~ delete _; + + public Component RegisterComponent(int_cosize count) where T : struct + { + _ComponentsPacked.Add(new ComponentManager()..Initialize(count)); + _EntityList.Add(new .(MaxComponents)); + _ComponentMap.Add(typeof(T), _ComponentsNext); + return _ComponentsNext++; + } + + public Component GetComponent() + { + if(_ComponentMap.ContainsKey(typeof(T))) + return _ComponentMap[typeof(T)]; + return -1; + } + + 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; + + _DeletedEntities.Add(e); + _EntityAvailable.Add(e); + + for(int i < _EntityList.Count) + { + if(_EntityList[i][e] != -1) + { + _ComponentsPacked[i].Remove(_EntityList[i][e]); + _EntityList[i][e] = -1; + } + } + } + + public Result AddComponentToEntity(Entity e, Component c) + { + if(e >= _EntityNext || _DeletedEntities.Contains(e)) + return .Err; + + var res = _ComponentsPacked[c].Create(e); + _EntityList[c][e] = res.0; + return res.1; + } + + public Result RemoveComponentFromEntity(Entity e, Component c) + { + if(e >= _EntityNext || _DeletedEntities.Contains(e)) + return .Err; + + return _ComponentsPacked[c].Remove(e); + } + + public void GetEntityFromComponent(Component c, List e) + { + _ComponentsPacked[c].[Unchecked]GetEntities(e); + } + + public void ExecuteSystem(delegate void(ECS, Entity, ref A) func) where A : struct + { + var idx = GetComponent(); + var data = _ComponentsPacked[idx].GetAll(); + for(var i in ref data) + func.Invoke(this, i.0, ref i.1); + } +} \ No newline at end of file diff --git a/src/Example/MoverSystem.bf b/src/Example/MoverSystem.bf new file mode 100644 index 0000000..e02cec7 --- /dev/null +++ b/src/Example/MoverSystem.bf @@ -0,0 +1,13 @@ +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; + } +} \ No newline at end of file diff --git a/src/Example/Position.bf b/src/Example/Position.bf new file mode 100644 index 0000000..1157b34 --- /dev/null +++ b/src/Example/Position.bf @@ -0,0 +1,9 @@ +namespace Theater_ECS.Example; + +using System; + +struct Position +{ + public float x = (.)gRand.NextI32() % 25; + public float y = (.)gRand.NextI32() % 25; +} \ No newline at end of file diff --git a/src/Example/Program.bf b/src/Example/Program.bf new file mode 100644 index 0000000..1c78700 --- /dev/null +++ b/src/Example/Program.bf @@ -0,0 +1,62 @@ +namespace Theater_ECS; +using Theater_ECS.Example; + + +using System; +using System.Collections; + +class Program +{ + public static uint64 counter = 0; + + public static void Main() + { + ECS ecs = scope .(); + var pos = ecs.RegisterComponent(100); + //var waste = ecs.RegisterComponent(100); + var vel = ecs.RegisterComponent(100); + + + List entities = scope .(); + for(int i < 500000) + entities.Add(ecs.CreateEntity()); + + for(var i in entities) + *(Position*)ecs.AddComponentToEntity(i, pos).Value = .(); + + for(var i in entities) + *(Velocity*)ecs.AddComponentToEntity(i, vel).Value = .(); + + /* + for(var i in entities) + if(gRand.NextI32() % 2 == 1) + *(waste*)ecs.AddComponentToEntity(i, waste).Value = .(); + */ + + MoverSystem s = scope .(); + s.IntializeSystem(ecs); + + System.Diagnostics.Stopwatch watch = scope .(); + for(int a < 10) + { + watch.Start(); + for(int i < 10) + { + + s.RunSystem(ecs); + + } + Console.WriteLine(scope $"{watch.ElapsedMilliseconds}ms"); + watch.Stop(); + watch.Reset(); + } + //Console.Read(); + } + + +} + +public struct waste +{ + private float[10] a; +} \ No newline at end of file diff --git a/src/Example/Velocity.bf b/src/Example/Velocity.bf new file mode 100644 index 0000000..8067be8 --- /dev/null +++ b/src/Example/Velocity.bf @@ -0,0 +1,9 @@ +namespace Theater_ECS.Example; + +using System; + +struct Velocity +{ + public float x = (.)gRand.NextI32() % 25; + public float y = (.)gRand.NextI32() % 25; +} \ No newline at end of file diff --git a/src/System.bf b/src/System.bf new file mode 100644 index 0000000..a84ca56 --- /dev/null +++ b/src/System.bf @@ -0,0 +1,15 @@ +namespace Theater_ECS; + +using System; +using System.Collections; + +abstract class System +{ + protected Dictionary _ComponentMap = new .() ~ delete _; + protected List _Target = new .(100) ~ delete _; + + public abstract void RunSystem(ECS ecs); + + public abstract bool IntializeSystem(ECS ecs); + +} \ No newline at end of file diff --git a/src/SystemMethodAttribute.bf b/src/SystemMethodAttribute.bf new file mode 100644 index 0000000..1541cea --- /dev/null +++ b/src/SystemMethodAttribute.bf @@ -0,0 +1,117 @@ +namespace Theater_ECS; + +using System; +using System.Reflection; +using System.Collections; + +[AttributeUsage(.Method)] +struct SystemMethodAttribute : Attribute, IOnTypeInit +{ + private StringView _Name; + private Span _Types; + + public this(StringView name, params Span 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"); + } + } +} \ No newline at end of file -- 2.39.5