From c6c2ab4c0ecb7a0835f64079159dbd0e62383b90 Mon Sep 17 00:00:00 2001 From: andreakarasho Date: Mon, 1 Apr 2024 23:41:43 +0200 Subject: [PATCH] + more threading support + removed "Commands" class + Deferred(Action fn) function --- samples/MyBattleground/Program.cs | 25 ++-- src/Commands.cs | 223 ----------------------------- src/Query.cs | 4 +- src/System.cs | 67 ++++----- src/World.Deferred.cs | 31 ++-- src/World.Shortcuts.cs | 7 + src/World.cs | 69 +++------ tools/TinyEcs.Generator/Program.cs | 8 +- 8 files changed, 100 insertions(+), 334 deletions(-) delete mode 100644 src/Commands.cs diff --git a/samples/MyBattleground/Program.cs b/samples/MyBattleground/Program.cs index 79cc5a2..e6a8dc3 100644 --- a/samples/MyBattleground/Program.cs +++ b/samples/MyBattleground/Program.cs @@ -27,10 +27,10 @@ .RunIf(() => true) .RunIf((Res state, Res state1, Res state2, Query velQuery) => state.Value == GameStates.InGame); -scheduler.AddSystem((Local i32, Res str, Commands commands) => { +scheduler.AddSystem((Local i32, Res str, SchedulerState schedState) => { Console.WriteLine(i32.Value++); - commands.AddResource(23ul); + schedState.AddResource(23ul); }, Stages.Startup); scheduler.AddSystem((Res ul) => { Console.WriteLine(ul.Value++); @@ -83,13 +83,20 @@ Res myText Console.WriteLine("What: {0}", myText.Value); } ); -scheduler.AddSystem((Commands commands) => { - var ff = commands.Entity() +scheduler.AddSystem((World world) => { + + world.BeginDeferred(); + var ff = world.Entity() .Set(default) .Set(default); -}); -scheduler.AddSystem((Commands commands) => { - commands.Merge(); + world.EndDeferred(); + + world.Deferred(w => { + var ff = w.Entity() + .Set(default) + .Set(default) + .Set(); + }); }); scheduler.AddSystem((World world) => { Console.WriteLine("entities in world {0}", world.EntityCount); @@ -128,7 +135,7 @@ Res myText // ecs.Entity(); ecs.Query() - .Each((EntityView ent) => { + .EachJob((EntityView ent, ref Position pos) => { ent.Set(new MyEvent() { Value = 2 }); @@ -138,6 +145,8 @@ Res myText ref var v2 = ref ent.Get(); v.Value += 1; + ent.Unset(); + // var ee = ent.World.Entity() // .Set(new MyEvent() { Value = 222 }); diff --git a/src/Commands.cs b/src/Commands.cs deleted file mode 100644 index a00d385..0000000 --- a/src/Commands.cs +++ /dev/null @@ -1,223 +0,0 @@ -using System.Collections.Concurrent; -using Microsoft.Collections.Extensions; - -namespace TinyEcs; - -public sealed partial class Commands -{ - private readonly EntitySparseSet _despawn; - private readonly EntitySparseSet _set; - private readonly EntitySparseSet _unset; - - internal Commands(World main) - { - World = main; - _despawn = new(); - _set = new(); - _unset = new(); - } - - internal World World { get; set; } - - - public CommandEntityView Entity(EcsID id = default) - { - if (id == 0 || !World.Exists(id)) - id = World.NewEmpty(id); - - return new CommandEntityView(this, id); - } - - public void Delete(EcsID id) - { - EcsAssert.Assert(World.Exists(id)); - - ref var entity = ref _despawn.Get(id); - if (Unsafe.IsNullRef(ref entity)) - { - _despawn.Add(id, id); - } - } - - public void Set(EcsID id) where T : struct - { - ref readonly var cmp = ref World.Component(); - EcsAssert.Assert(cmp.Size <= 0, "this is not a tag"); - Set(id, in cmp); - } - - public ref T Set(EcsID id, T component) where T : struct - { - EcsAssert.Assert(World.Exists(id)); - - ref readonly var cmp = ref World.Component(); - EcsAssert.Assert(cmp.Size > 0); - - if (World.Has(id, in cmp)) - { - ref var value = ref World.Get(id); - value = component; - - return ref value; - } - - ref var objRef = ref Set(id, in cmp); - if (!Unsafe.IsNullRef(ref objRef)) - { - objRef = component; - } - - return ref Unsafe.Unbox(objRef); - } - - private ref object Set(EcsID id, ref readonly ComponentInfo cmp) - { - EcsAssert.Assert(World.Exists(id)); - - ref var set = ref _set.CreateNew(out _); - set.Entity = id; - set.Component = cmp; - set.Data = null!; - - if (cmp.Size > 0) - { - var array = Lookup.GetArray(cmp.ID, 1); - set.Data = array!.GetValue(0)!; - } - - return ref set.Data; - } - - public void Unset(EcsID id) where T : struct - { - EcsAssert.Assert(World.Exists(id)); - - ref var unset = ref _unset.CreateNew(out _); - unset.Entity = id; - unset.Component = World.Component(); - } - - public ref T Get(EcsID entity) where T : struct - { - EcsAssert.Assert(World.Exists(entity)); - - if (World.Has(entity)) - { - return ref World.Get(entity); - } - - Unsafe.SkipInit(out var cmp); - - return ref Set(entity, cmp); - } - - public bool Has(EcsID entity) where T : struct - { - EcsAssert.Assert(World.Exists(entity)); - - return World.Has(entity); - } - - public void Merge() - { - if (_despawn.Length == 0 && _set.Length == 0 && _unset.Length == 0) - { - return; - } - - foreach (ref var set in _set) - { - EcsAssert.Assert(World.Exists(set.Entity)); - - ref var record = ref World.GetRecord(set.Entity); - var array = World.Set(World.Entity(set.Entity), ref record, in set.Component); - array?.SetValue(set.Data, record.Row & Archetype.CHUNK_THRESHOLD); - - set.Data = null!; - } - - foreach (ref var unset in _unset) - { - EcsAssert.Assert(World.Exists(unset.Entity)); - - World.DetachComponent(unset.Entity, ref unset.Component); - } - - foreach (ref var despawn in _despawn) - { - EcsAssert.Assert(World.Exists(despawn)); - - World.Delete(despawn); - } - - Clear(); - } - - public void Clear() - { - _set.Clear(); - _unset.Clear(); - _despawn.Clear(); - } - - private struct SetComponent - { - public EcsID Entity; - public ComponentInfo Component; - public object Data; - } - - private struct UnsetComponent - { - public EcsID Entity; - public ComponentInfo Component; - } -} - -public readonly ref struct CommandEntityView -{ - private readonly EcsID _id; - private readonly Commands _cmds; - - internal CommandEntityView(Commands cmds, EcsID id) - { - _cmds = cmds; - _id = id; - } - - public readonly EcsID ID => _id; - - public readonly CommandEntityView Set(T cmp) where T : struct - { - _cmds.Set(_id, cmp); - return this; - } - - public readonly CommandEntityView Set() where T : struct - { - _cmds.Set(_id); - return this; - } - - public readonly CommandEntityView Unset() where T : struct - { - _cmds.Unset(_id); - return this; - } - - public readonly CommandEntityView Delete() - { - _cmds.Delete(_id); - return this; - } - - public readonly ref T Get() where T : struct - { - return ref _cmds.Get(_id); - } - - public readonly bool Has() where T : struct - { - return _cmds.Has(_id); - } -} diff --git a/src/Query.cs b/src/Query.cs index 5ad53d7..1c6cb2c 100644 --- a/src/Query.cs +++ b/src/Query.cs @@ -171,7 +171,7 @@ public QueryInternal GetEnumerator() public void Each(QueryFilterDelegateWithEntity fn) { - World.Lock(); + World.BeginDeferred(); foreach (var arch in this) { @@ -187,6 +187,6 @@ public void Each(QueryFilterDelegateWithEntity fn) } } - World.Unlock(); + World.EndDeferred(); } } diff --git a/src/System.cs b/src/System.cs index 9620c2e..8f55136 100644 --- a/src/System.cs +++ b/src/System.cs @@ -56,7 +56,7 @@ public Scheduler(World world) _systems[i] = new (); AddSystemParam(world); - AddSystemParam(new Commands(world, this)); + AddSystemParam(new SchedulerState(this)); } @@ -117,7 +117,10 @@ internal Scheduler AddSystemParam(T param) where T : ISystemParam return this; } - internal bool ResourceExists() => _resources.ContainsKey(typeof(T)); + internal bool ResourceExists() where T : ISystemParam + { + return _resources.ContainsKey(typeof(T)); + } } public interface IPlugin @@ -219,27 +222,6 @@ void ISystemParam.New(object arguments) } } -partial class Commands : ISystemParam -{ - private readonly Scheduler? _scheduler; - - public Commands() : this(null!) { } - - public Commands(World world, Scheduler scheduler) : this(world) - => _scheduler = scheduler; - - void ISystemParam.New(object arguments) - { - World = (World) arguments; - } - - public void AddResource(T resource) - => _scheduler?.AddResource(resource); - - public bool ResourceExists() - => _scheduler?.ResourceExists>() ?? false; -} - public sealed class Res : ISystemParam { private T? _t; @@ -278,25 +260,30 @@ void ISystemParam.New(object arguments) } } -// public sealed class SystemState : ISystemParam -// { -// private readonly Scheduler _scheduler; +public sealed class SchedulerState : ISystemParam +{ + private readonly Scheduler _scheduler; -// internal SystemState(Scheduler scheduler) -// { -// _scheduler = scheduler; -// } + internal SchedulerState(Scheduler scheduler) + { + _scheduler = scheduler; + } -// public SystemState() -// => throw new Exception("You are not allowed to initialixze this object by yourself!"); + public SchedulerState() + => throw new Exception("You are not allowed to initialixze this object by yourself!"); -// public void AddResource(T resource) -// { -// _scheduler.AddResource(resource); -// } + public void AddResource(T resource) + { + _scheduler.AddResource(resource); + } -// void ISystemParam.New(object arguments) -// { + public bool ResourceExists() + { + return _scheduler.ResourceExists>(); + } -// } -// } + void ISystemParam.New(object arguments) + { + + } +} diff --git a/src/World.Deferred.cs b/src/World.Deferred.cs index 8f8b1f4..95d7dce 100644 --- a/src/World.Deferred.cs +++ b/src/World.Deferred.cs @@ -7,26 +7,35 @@ public sealed partial class World { private readonly ConcurrentQueue _operations = new(); private readonly ConcurrentDictionary> _deferredSets = new(); - private WorldState _worldState; + private WorldState _worldState = new () { State = WorldStateTypes.Normal, Locks = 0 }; + private readonly object _worldStateLock = new object(); + public bool IsDeferred => _worldState.State == WorldStateTypes.Deferred; - internal void Lock() + + public void BeginDeferred() { - _worldState.State = WorldStateTypes.Deferred; - Interlocked.Increment(ref _worldState.Locks); + lock (_worldStateLock) + { + _worldState.State = WorldStateTypes.Deferred; + _worldState.Locks += 1; + } } - internal void Unlock() + public void EndDeferred() { - Interlocked.Decrement(ref _worldState.Locks); - - if (_worldState.Locks == 0) + lock (_worldStateLock) { - _worldState.NewEntities = 0; - _worldState.State = WorldStateTypes.Normal; - Merge(); + _worldState.Locks -= 1; + + if (_worldState.Locks == 0) + { + _worldState.NewEntities = 0; + _worldState.State = WorldStateTypes.Normal; + Merge(); + } } } diff --git a/src/World.Shortcuts.cs b/src/World.Shortcuts.cs index da0e04f..61943da 100644 --- a/src/World.Shortcuts.cs +++ b/src/World.Shortcuts.cs @@ -82,4 +82,11 @@ public ref T TryGet(EcsID entity) where T : struct ref var chunk = ref record.GetChunk(); return ref Unsafe.Add(ref chunk.GetReference(column), record.Row & Archetype.CHUNK_THRESHOLD); } + + public void Deferred(Action fn) + { + BeginDeferred(); + fn(this); + EndDeferred(); + } } diff --git a/src/World.cs b/src/World.cs index f9659fd..cb3c1e8 100644 --- a/src/World.cs +++ b/src/World.cs @@ -13,6 +13,8 @@ public sealed partial class World : IDisposable private readonly ComponentComparer _comparer; private readonly EcsID _maxCmpId; private readonly Dictionary _cachedQueries = new (); + private readonly object _newEntLock = new object(); + public World(ulong maxComponentId = 256) { @@ -117,20 +119,6 @@ public ref readonly ComponentInfo Component() where T : struct return ref lookup; } - // public EntityView Entity(string name) - // { - // _entityNames.TryGetValue(name, out var id); - - // var entity = Entity(id); - // if (id == 0) - // { - // _entityNames.Add(name, entity.ID); - // GetRecord(entity.ID).Name = name; - // } - - // return entity; - // } - public EntityView Entity() where T : struct { return Entity(Component().ID); @@ -143,34 +131,20 @@ public EntityView Entity(EcsID id = default) internal EntityView NewEmpty(ulong id = 0) { - // if (IsDeferred) - // { - // if (id == 0) - // { - // id = _worldState.NewEntities++ + _entities.MaxID; - // } - - // // _operations.Enqueue(new DeferredOp() { - // // Op = DeferredOpTypes.CreateEntity, - // // Entity = id, - // // }); - - // _commands.Entity(id); - - // return new EntityView(this, id); - // } - - ref var record = ref ( - id > 0 ? ref _entities.Add(id, default!) : ref _entities.CreateNew(out id) - ); - record.Archetype = _archRoot; - record.Row = _archRoot.Add(id); + lock (_newEntLock) + { + ref var record = ref ( + id > 0 ? ref _entities.Add(id, default!) : ref _entities.CreateNew(out id) + ); + record.Archetype = _archRoot; + record.Row = _archRoot.Add(id); - var e = new EntityView(this, id); + var e = new EntityView(this, id); - OnEntityCreated?.Invoke(e); + OnEntityCreated?.Invoke(e); - return e; + return e; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -190,14 +164,17 @@ public void Delete(EcsID entity) return; } - OnEntityDeleted?.Invoke(new (this, entity)); + lock (_newEntLock) + { + OnEntityDeleted?.Invoke(new (this, entity)); - ref var record = ref GetRecord(entity); + ref var record = ref GetRecord(entity); - var removedId = record.Archetype.Remove(ref record); - EcsAssert.Assert(removedId == entity); + var removedId = record.Archetype.Remove(ref record); + EcsAssert.Assert(removedId == entity); - _entities.Remove(removedId); + _entities.Remove(removedId); + } } public bool Exists(EcsID entity) @@ -371,7 +348,7 @@ public Query Query() where TQuery : struct whe public void Each(QueryFilterDelegateWithEntity fn) { - Lock(); + BeginDeferred(); foreach (var arch in GetQuery(0, ImmutableArray.Empty, static (world, terms) => new Query(world, terms))) { @@ -387,7 +364,7 @@ public void Each(QueryFilterDelegateWithEntity fn) } } - Unlock(); + EndDeferred(); } internal Query GetQuery(ulong hash, ImmutableArray terms, Func, Query> factory) diff --git a/tools/TinyEcs.Generator/Program.cs b/tools/TinyEcs.Generator/Program.cs index bc9afc1..5e04c99 100644 --- a/tools/TinyEcs.Generator/Program.cs +++ b/tools/TinyEcs.Generator/Program.cs @@ -191,7 +191,7 @@ public partial {className} sb.AppendLine($@" public void Each<{typeParams}>({delegateName}<{typeParams}> fn) {whereParams} {{ - {worldLock}Lock(); + {worldLock}BeginDeferred(); foreach (var arch in {(withFilter ? getQuery : "this")}) {{ @@ -226,7 +226,7 @@ public partial {className} }} }} - {worldLock}Unlock(); + {worldLock}EndDeferred(); }} "); } @@ -250,7 +250,7 @@ public partial {className} sb.AppendLine($@" public void EachJob<{typeParams}>({delegateName}<{typeParams}> fn) {whereParams} {{ - {worldLock}Lock(); + {worldLock}BeginDeferred(); var query = {(withFilter ? getQuery : "this")}; var cde = query.ThreadCounter; cde.Reset(); @@ -298,7 +298,7 @@ public partial {className} cde.Signal(); cde.Wait(); - {worldLock}Unlock(); + {worldLock}EndDeferred(); }} "); }