From 0afd40a1f0b5a1375a15c8c13500d3f653093490 Mon Sep 17 00:00:00 2001 From: Roycon <4994730+RoyconSkylands@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:22:29 +0000 Subject: [PATCH 01/14] Fixing sizes (#189) * Increased array size for when adding new components * Maybe better --- src/Arch.SourceGen/Fundamentals/StructuralChanges.cs | 7 ++++++- src/Arch.SourceGen/Queries/AddWithQueryDescription.cs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Arch.SourceGen/Fundamentals/StructuralChanges.cs b/src/Arch.SourceGen/Fundamentals/StructuralChanges.cs index a5a5553a..a0873911 100644 --- a/src/Arch.SourceGen/Fundamentals/StructuralChanges.cs +++ b/src/Arch.SourceGen/Fundamentals/StructuralChanges.cs @@ -19,11 +19,13 @@ public static StringBuilder AppendWorldAdd(this StringBuilder sb, int amount) var inParameters = new StringBuilder().InsertGenericInParams(amount); var types = new StringBuilder().GenericTypeParams(amount); + var getIds = new StringBuilder(); var setIds = new StringBuilder(); var addEvents = new StringBuilder(); for (var index = 0; index <= amount; index++) { - setIds.AppendLine($"spanBitSet.SetBit(Component.ComponentType.Id);"); + getIds.AppendLine($"var id{index} = Component.ComponentType.Id;"); + setIds.AppendLine($"spanBitSet.SetBit(id{index});"); addEvents.AppendLine($"OnComponentAdded(entity);"); } @@ -36,6 +38,9 @@ public static StringBuilder AppendWorldAdd(this StringBuilder sb, int amount) { var oldArchetype = EntityInfo.GetArchetype(entity.Id); + // Get all the ids here just in case we are adding a new component as this will grow the ComponentRegistry.Size + {{getIds}} + // BitSet to stack/span bitset, size big enough to contain ALL registered components. Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)]; oldArchetype.BitSet.AsSpan(stack); diff --git a/src/Arch.SourceGen/Queries/AddWithQueryDescription.cs b/src/Arch.SourceGen/Queries/AddWithQueryDescription.cs index 996e3a44..f3cbbdc6 100644 --- a/src/Arch.SourceGen/Queries/AddWithQueryDescription.cs +++ b/src/Arch.SourceGen/Queries/AddWithQueryDescription.cs @@ -50,7 +50,7 @@ public static void AppendAddWithQueryDescription(this StringBuilder sb, int amou public void Add<{{generics}}>(in QueryDescription queryDescription, {{parameters}}) { // BitSet to stack/span bitset, size big enough to contain ALL registered components. - Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)]; + Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size + {{amount + 1}})]; var query = Query(in queryDescription); foreach (var archetype in query.GetArchetypeIterator()) From 967b8b80c435d0933e11ea774a076fca305482f7 Mon Sep 17 00:00:00 2001 From: richdog Date: Sun, 17 Dec 2023 18:54:45 -0500 Subject: [PATCH 02/14] fixed archetype duplication after loading a save (#191) # Conflicts: # src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs --- .../Core/Extensions/Dangerous/DangerousWorldExtensions.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs b/src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs index 64799234..be21ce99 100644 --- a/src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs +++ b/src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs @@ -1,3 +1,4 @@ +using Arch.Core.Utils; using Arch.LowLevel.Jagged; namespace Arch.Core.Extensions.Dangerous; @@ -17,8 +18,12 @@ public static class DangerousWorldExtensions public static void SetArchetypes(this World world, List archetypes) { world.Archetypes.AddRange(archetypes); + foreach (var archetype in archetypes) { + var hash = Component.GetHashCode(archetype.Types); + world.GroupToArchetype[hash] = archetype; + world.Size += archetype.EntityCount; world.Capacity += archetype.EntitiesPerChunk * archetype.ChunkCount; } From 1d6b832cfc7281cc30049b8727504de60de0582a Mon Sep 17 00:00:00 2001 From: richdog Date: Mon, 18 Dec 2023 15:24:38 -0500 Subject: [PATCH 03/14] allow getting and setting of recycled entity ids for persistence (#193) --- .../Dangerous/DangerousWorldExtensions.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs b/src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs index be21ce99..1123f310 100644 --- a/src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs +++ b/src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs @@ -39,6 +39,36 @@ public static void EnsureCapacity(this World world, int capacity) world.EntityInfo.EnsureCapacity(capacity); } + /// + /// Gets the recycled entities for the world. + /// + /// The instance. + /// /// a tuple (id, version) list of the recycled entities. + public static List<(int, int)> GetRecycledEntityIds(this World world) + { + List<(int, int)> recycledIdsList = new(); + foreach (RecycledEntity id in world.RecycledIds) + { + recycledIdsList.Add((id.Id, id.Version)); + } + + return recycledIdsList; + } + + /// + /// Sets the recycled entities for the world. + /// + /// The instance. + /// A tuple (id, version) list of recycled entites. + public static void SetRecycledEntityIds(this World world, List<(int, int)> recycledEntities) + { + world.RecycledIds.Clear(); + foreach ((int, int) recycledEntity in recycledEntities) + { + world.RecycledIds.Enqueue(new RecycledEntity(recycledEntity.Item1, recycledEntity.Item2)); + } + } + /// /// Sets the for an . /// From dc49bc14b08d76079b55995bc2e1ef9e253f6e20 Mon Sep 17 00:00:00 2001 From: James B Date: Mon, 8 Jan 2024 18:22:29 +0000 Subject: [PATCH 04/14] Fix missing component Add event, and raise Remove event when destroying entities. (#196) * Fixes missing component Add/Remove events. * Add unit tests for newly added events. --- src/Arch.Tests/EventTest.cs | 82 ++++++++++++++++++++++++++++++++++++- src/Arch/Core/World.cs | 28 +++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/src/Arch.Tests/EventTest.cs b/src/Arch.Tests/EventTest.cs index 62264ce3..d1b5ba01 100644 --- a/src/Arch.Tests/EventTest.cs +++ b/src/Arch.Tests/EventTest.cs @@ -9,7 +9,7 @@ namespace Arch.Tests; /// /// The class -/// adds several methods for checking if events are fired correctly upon entity modifcation. +/// adds several methods for checking if events are fired correctly upon entity modification. /// [TestFixture] public sealed class EventTest @@ -67,6 +67,30 @@ public void AddSingle() _asserter.Clear(); } + [Test] + public void AddSingleFromArchetype() + { + using var world = World.Create(); + world.SubscribeComponentAdded((in Entity entity, ref EventTestComponentOne _) => _asserter.CompOneAdded.Add(entity)); + world.SubscribeComponentAdded((in Entity entity, ref EventTestComponentTwo _) => _asserter.CompTwoAdded.Add(entity)); + + Span archetype = stackalloc ComponentType[] { typeof(EventTestComponentOne) }; + + // Create entity to check if created and add event were fired + var entity = world.Create(archetype); + + _asserter.AssertEvents(compOneAdded: 1); + That(_asserter.CompOneAdded, Does.Contain(entity)); + _asserter.Clear(); + + // Add another component to see if add was fired + world.Add(entity); + + _asserter.AssertEvents(compTwoAdded: 1); + That(_asserter.CompTwoAdded, Does.Contain(entity)); + _asserter.Clear(); + } + [Test] public void AddSingleObject() { @@ -226,6 +250,62 @@ public void RemoveMultiple() _asserter.Clear(); } + [Test] + public void DestroyDoesRaiseComponentRemove() + { + using var world = World.Create(); + world.SubscribeComponentRemoved((in Entity entity,ref EventTestComponentOne _) => _asserter.CompOneRemoved.Add(entity)); + world.SubscribeComponentRemoved((in Entity entity, ref EventTestComponentTwo _) => _asserter.CompTwoRemoved.Add(entity)); + + var entity = world.Create(); + world.Destroy(entity); + + _asserter.AssertEvents(compOneRemoved: 1, compTwoRemoved:1); + That(_asserter.CompOneRemoved, Does.Contain(entity)); + That(_asserter.CompTwoRemoved, Does.Contain(entity)); + _asserter.Clear(); + } + + [Test] + public void DestroyUsingQueryDoesRaiseComponentRemove() + { + using var world = World.Create(); + world.SubscribeComponentRemoved((in Entity entity,ref EventTestComponentOne _) => _asserter.CompOneRemoved.Add(entity)); + world.SubscribeComponentRemoved((in Entity entity, ref EventTestComponentTwo _) => _asserter.CompTwoRemoved.Add(entity)); + + var entity = world.Create(); + + world.Destroy(new QueryDescription().WithExclusive()); + + _asserter.AssertEvents(compOneRemoved: 1, compTwoRemoved:1); + That(_asserter.CompOneRemoved, Does.Contain(entity)); + That(_asserter.CompTwoRemoved, Does.Contain(entity)); + _asserter.Clear(); + } + + [Test] + public void RemoveCompThenDestroyDoesNotRaiseCompRemoveTwice() + { + using var world = World.Create(); + world.SubscribeComponentRemoved((in Entity entity,ref EventTestComponentOne _) => _asserter.CompOneRemoved.Add(entity)); + world.SubscribeComponentRemoved((in Entity entity, ref EventTestComponentTwo _) => _asserter.CompTwoRemoved.Add(entity)); + + var entity = world.Create(); + world.Remove(entity); + + _asserter.AssertEvents(compOneRemoved: 1, compTwoRemoved: 1); + That(_asserter.CompOneRemoved, Does.Contain(entity)); + That(_asserter.CompTwoRemoved, Does.Contain(entity)); + _asserter.Clear(); + + world.Destroy(entity); + + _asserter.AssertEvents(compOneRemoved: 0, compTwoRemoved: 0); + That(_asserter.CompOneRemoved, Does.Not.Contain(entity)); + That(_asserter.CompTwoRemoved, Does.Not.Contain(entity)); + _asserter.Clear(); + } + private class EventAsserter { public readonly List Created = new(); diff --git a/src/Arch/Core/World.cs b/src/Arch/Core/World.cs index db81f044..5c665780 100644 --- a/src/Arch/Core/World.cs +++ b/src/Arch/Core/World.cs @@ -1,5 +1,6 @@ using System.Diagnostics.Contracts; using System.Threading; +using Arch.Core.Extensions; using Arch.Core.Extensions.Internal; using Arch.Core.Utils; using Collections.Pooled; @@ -297,6 +298,14 @@ public Entity Create(Span types) EntityInfo.Add(entity.Id, recycled.Version, archetype, slot); Size++; OnEntityCreated(entity); + +#if EVENTS + foreach (ref var type in types) + { + OnComponentAdded(entity, type); + } +#endif + return entity; } @@ -343,6 +352,15 @@ internal void Move(Entity entity, Archetype source, Archetype destination, out S [StructuralChange] public void Destroy(Entity entity) { + #if EVENTS + // Raise the OnComponentRemoved event for each component on the entity. + var arch = GetArchetype(entity); + foreach (var compType in arch.Types) + { + OnComponentRemoved(entity, compType); + } + #endif + OnEntityDestroyed(entity); // Remove from archetype @@ -774,6 +792,16 @@ public void Destroy(in QueryDescription queryDescription) foreach (var index in chunk) { var entity = Unsafe.Add(ref entityFirstElement, index); + + #if EVENTS + // Raise the OnComponentRemoved event for each component on the entity. + var arch = GetArchetype(entity); + foreach (var compType in arch.Types) + { + OnComponentRemoved(entity, compType); + } + #endif + OnEntityDestroyed(entity); var version = EntityInfo.GetVersion(entity.Id); From c6fb4b3e7381e9ed7f18abb8d4d4f74f289e03a1 Mon Sep 17 00:00:00 2001 From: richdog Date: Tue, 16 Jan 2024 20:46:31 -0500 Subject: [PATCH 05/14] fixed issue with Unsafe.As not working in .Net 2.1 (#198) --- .../Dangerous/DangerousWorldExtensions.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs b/src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs index 1123f310..be2d4d2c 100644 --- a/src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs +++ b/src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs @@ -105,20 +105,28 @@ public static void SetVersions(this World world, JaggedArray versions) /// /// The instance. /// Its array. - public static JaggedArray<(Archetype, (int,int))> GetSlots(this World world) + public static JaggedArray<(Archetype, (int, int))> GetSlots(this World world) { var array = world.EntityInfo.EntitySlots; - return Unsafe.As>(array); + return Unsafe.As>(array); } /// - /// Sets the of a . + /// Sets the of a . /// /// The instance. /// The new slots array. - public static void SetSlots(this World world, JaggedArray<(Archetype, (int,int))> slots) + public static void SetSlots(this World world, JaggedArray<(Archetype, (int, int))> slots) { - world.EntityInfo.EntitySlots = Unsafe.As>(slots); + var convertedSlots = new JaggedArray(slots.Buckets, slots.Capacity); + + for (int i = 0; i < slots.Capacity; i++) + { + var slot = slots[i]; + convertedSlots[i] = new EntitySlot(slot.Item1, new Slot(slot.Item2.Item1, slot.Item2.Item2)); + } + + world.EntityInfo.EntitySlots = convertedSlots; } /// From e859b9d9f15a4041fb271d14266447cca27cefbc Mon Sep 17 00:00:00 2001 From: Lars Date: Mon, 29 Jan 2024 16:29:45 +0100 Subject: [PATCH 06/14] 1.2.8 Release --- src/Arch.Samples/Arch.Samples.csproj | 2 +- src/Arch.Samples/Game.cs | 3 ++- src/Arch/Arch.csproj | 10 +++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Arch.Samples/Arch.Samples.csproj b/src/Arch.Samples/Arch.Samples.csproj index bd68a6fe..e4fb4640 100644 --- a/src/Arch.Samples/Arch.Samples.csproj +++ b/src/Arch.Samples/Arch.Samples.csproj @@ -1,13 +1,13 @@  - net7.0 true enable Exe Debug;Release;Release-Pure;Release-PureECS;Release-Events;Debug-PureECS;Debug-Events + net7.0;net8.0 diff --git a/src/Arch.Samples/Game.cs b/src/Arch.Samples/Game.cs index 8fd79aff..6b5262d1 100644 --- a/src/Arch.Samples/Game.cs +++ b/src/Arch.Samples/Game.cs @@ -1,5 +1,6 @@ using Arch.Core; using Arch.Core.Extensions; +using Arch.Core.Utils; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; @@ -68,7 +69,7 @@ protected override void BeginRun() _drawSystem = new DrawSystem(_world, _spriteBatch); // Spawn in entities with position, velocity and sprite - for (var index = 0; index < 1002; index++) + for (var index = 0; index < 150_000; index++) { _world.Create( new Position { Vec2 = _random.NextVector2(GraphicsDevice.Viewport.Bounds) }, diff --git a/src/Arch/Arch.csproj b/src/Arch/Arch.csproj index 4bd48c3f..34358036 100644 --- a/src/Arch/Arch.csproj +++ b/src/Arch/Arch.csproj @@ -3,7 +3,7 @@ true latest - net7.0; net6.0; netstandard2.1 + net8.0; net7.0; net6.0; netstandard2.1 true true @@ -14,11 +14,15 @@ Arch Arch - 1.2.7.1-alpha + 1.2.8 genaray Apache-2.0 A high performance c# net.6 and net.7 archetype based ECS ( Entity component system ). - Updated LowLevel which fixes bugs. + Updated LowLevel which fixes bugs. +Fixed issue where Unsafe.As did not work on .Net 2.1 for the Dangerous-Utils. +Dangerous API now allows setting/getting of recycled ids. +Fixed archetype duplication after loading a save. +Fixed .Add when a newly non registered component was added. c#;.net;.net6;.net7;ecs;game;entity;gamedev; game-development; game-engine; entity-component-system;stride;unity;godot; https://github.com/genaray/Arch From 878381fae92a27b62b150b5ba590a08586cac502 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 31 Jan 2024 18:16:40 +0100 Subject: [PATCH 07/14] Upgraded to JobScheduler 1.1.1 --- src/Arch.Benchmarks/Arch.Benchmarks.csproj | 2 +- src/Arch.Samples/Game.cs | 18 +++++++++-- .../Queries/InlineParallelQuery.cs | 32 ++++++++++--------- src/Arch.SourceGen/Queries/ParallelQuery.cs | 30 ++++++++--------- src/Arch.SourceGen/QueryGenerator.cs | 2 +- src/Arch.Tests/CommandBufferTest.cs | 11 +++++-- src/Arch.Tests/QueryTest.cs | 10 ++++-- src/Arch/Arch.csproj | 3 +- src/Arch/Core/Jobs/Jobs.cs | 2 +- src/Arch/Core/Jobs/World.Jobs.cs | 22 ++++++------- src/Arch/Core/World.cs | 7 +++- 11 files changed, 86 insertions(+), 53 deletions(-) diff --git a/src/Arch.Benchmarks/Arch.Benchmarks.csproj b/src/Arch.Benchmarks/Arch.Benchmarks.csproj index 5d162a14..e9a1eb61 100644 --- a/src/Arch.Benchmarks/Arch.Benchmarks.csproj +++ b/src/Arch.Benchmarks/Arch.Benchmarks.csproj @@ -64,7 +64,7 @@ - + diff --git a/src/Arch.Samples/Game.cs b/src/Arch.Samples/Game.cs index 6b5262d1..7a5b2343 100644 --- a/src/Arch.Samples/Game.cs +++ b/src/Arch.Samples/Game.cs @@ -4,6 +4,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; +using Schedulers; namespace Arch.Samples; @@ -14,7 +15,7 @@ public sealed class Game : Microsoft.Xna.Framework.Game { // The world and a job scheduler for multithreading. private World _world; - private JobScheduler.JobScheduler _jobScheduler; + private JobScheduler _jobScheduler; // Our systems processing entities. private MovementSystem _movementSystem; @@ -61,9 +62,20 @@ protected override void BeginRun() { base.BeginRun(); - // Create world & systems + // Create world & Job Scheduler _world = World.Create(); - _jobScheduler = new("SampleWorkerThreads"); + _jobScheduler = new( + new JobScheduler.Config + { + ThreadPrefixName = "Arch.Samples", + ThreadCount = 0, + MaxExpectedConcurrentJobs = 64, + StrictAllocationMode = false, + } + ); + World.SharedJobScheduler = _jobScheduler; + + // Create systems _movementSystem = new MovementSystem(_world, GraphicsDevice.Viewport.Bounds); _colorSystem = new ColorSystem(_world); _drawSystem = new DrawSystem(_world, _spriteBatch); diff --git a/src/Arch.SourceGen/Queries/InlineParallelQuery.cs b/src/Arch.SourceGen/Queries/InlineParallelQuery.cs index 25b2695e..eee026a4 100644 --- a/src/Arch.SourceGen/Queries/InlineParallelQuery.cs +++ b/src/Arch.SourceGen/Queries/InlineParallelQuery.cs @@ -37,19 +37,20 @@ public static void AppendHpParallelQuery(this StringBuilder builder, int amount) job.Size = range.Length; job.Chunks = archetype.Chunks; job.Instance = innerJob; + + var jobHandle = SharedJobScheduler.Schedule(job); JobsCache.Add(job); + JobHandles.Add(jobHandle); } - IJob.Schedule(JobsCache, JobHandles); - JobScheduler.JobScheduler.Instance.Flush(); - JobHandle.Complete(JobHandles); - JobHandle.Return(JobHandles); - // Return jobs to pool - for (var jobIndex = 0; jobIndex < JobsCache.Count; jobIndex++) + SharedJobScheduler.Flush(); + JobHandle.CompleteAll(JobHandles.Span); + + for (var index = 0; index < JobsCache.Count; index++) { - var job = Unsafe.As>>(JobsCache[jobIndex]); - pool.Return(job); + var job = Unsafe.As>>(JobsCache[index]); + pool.Return(job); } JobHandles.Clear(); @@ -96,18 +97,19 @@ public static void AppendHpeParallelQuery(this StringBuilder builder, int amount job.Size = range.Length; job.Chunks = archetype.Chunks; job.Instance = innerJob; + + var jobHandle = SharedJobScheduler.Schedule(job); JobsCache.Add(job); + JobHandles.Add(jobHandle); } - IJob.Schedule(JobsCache, JobHandles); - JobScheduler.JobScheduler.Instance.Flush(); - JobHandle.Complete(JobHandles); - JobHandle.Return(JobHandles); - // Return jobs to pool - for (var jobIndex = 0; jobIndex < JobsCache.Count; jobIndex++) + SharedJobScheduler.Flush(); + JobHandle.CompleteAll(JobHandles.Span); + + for (var index = 0; index < JobsCache.Count; index++) { - var job = Unsafe.As>>(JobsCache[jobIndex]); + var job = Unsafe.As>>(JobsCache[index]); pool.Return(job); } diff --git a/src/Arch.SourceGen/Queries/ParallelQuery.cs b/src/Arch.SourceGen/Queries/ParallelQuery.cs index 06abb097..2d75a29b 100644 --- a/src/Arch.SourceGen/Queries/ParallelQuery.cs +++ b/src/Arch.SourceGen/Queries/ParallelQuery.cs @@ -37,18 +37,18 @@ public static StringBuilder AppendParallelQuery(this StringBuilder sb, int amoun job.Size = range.Length; job.Chunks = archetype.Chunks; job.Instance = innerJob; + + var jobHandle = SharedJobScheduler.Schedule(job); JobsCache.Add(job); + JobHandles.Add(jobHandle); } - IJob.Schedule(JobsCache, JobHandles); - JobScheduler.JobScheduler.Instance.Flush(); - JobHandle.Complete(JobHandles); - JobHandle.Return(JobHandles); + SharedJobScheduler.Flush(); + JobHandle.CompleteAll(JobHandles.Span); - // Return jobs to pool - for (var jobIndex = 0; jobIndex < JobsCache.Count; jobIndex++) + for (var index = 0; index < JobsCache.Count; index++) { - var job = Unsafe.As>>(JobsCache[jobIndex]); + var job = Unsafe.As>>(JobsCache[index]); pool.Return(job); } @@ -97,19 +97,19 @@ public static StringBuilder AppendParallelEntityQuery(this StringBuilder sb, int job.Size = range.Length; job.Chunks = archetype.Chunks; job.Instance = innerJob; + + var jobHandle = SharedJobScheduler.Schedule(job); JobsCache.Add(job); + JobHandles.Add(jobHandle); } - IJob.Schedule(JobsCache, JobHandles); - JobScheduler.JobScheduler.Instance.Flush(); - JobHandle.Complete(JobHandles); - JobHandle.Return(JobHandles); + SharedJobScheduler.Flush(); + JobHandle.CompleteAll(JobHandles.Span); - // Return jobs to pool - for (var jobIndex = 0; jobIndex < JobsCache.Count; jobIndex++) + for (var index = 0; index < JobsCache.Count; index++) { - var job = Unsafe.As>>(JobsCache[jobIndex]); - pool.Return(job); + var job = Unsafe.As>>(JobsCache[index]); + pool.Return(job); } JobHandles.Clear(); diff --git a/src/Arch.SourceGen/QueryGenerator.cs b/src/Arch.SourceGen/QueryGenerator.cs index 37c1f09e..3c4ea85d 100644 --- a/src/Arch.SourceGen/QueryGenerator.cs +++ b/src/Arch.SourceGen/QueryGenerator.cs @@ -56,7 +56,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var accessors = new StringBuilder(); accessors.AppendLine("using System;"); accessors.AppendLine("using System.Runtime.CompilerServices;"); - accessors.AppendLine("using JobScheduler;"); + accessors.AppendLine("using Schedulers;"); accessors.AppendLine("using Arch.Core.Utils;"); accessors.AppendLine("using System.Diagnostics.Contracts;"); accessors.AppendLine("using Arch.Core.Extensions;"); diff --git a/src/Arch.Tests/CommandBufferTest.cs b/src/Arch.Tests/CommandBufferTest.cs index 8511e7d3..e39d7670 100644 --- a/src/Arch.Tests/CommandBufferTest.cs +++ b/src/Arch.Tests/CommandBufferTest.cs @@ -1,6 +1,7 @@ using Arch.CommandBuffer; using Arch.Core; using Arch.Core.Utils; +using Schedulers; using static NUnit.Framework.Assert; namespace Arch.Tests; @@ -222,12 +223,18 @@ public void CommandBufferCombined() public partial class CommandBufferTest { - private JobScheduler.JobScheduler _jobScheduler; + private JobScheduler _jobScheduler; [OneTimeSetUp] public void Setup() { - _jobScheduler = new JobScheduler.JobScheduler("CommandBuffer"); + _jobScheduler = new JobScheduler( + new JobScheduler.Config{ + ThreadPrefixName = "CommandBuffer", + ThreadCount = 0, + MaxExpectedConcurrentJobs = 64, + StrictAllocationMode = false, + }); } [OneTimeTearDown] diff --git a/src/Arch.Tests/QueryTest.cs b/src/Arch.Tests/QueryTest.cs index 6ffc7165..f04cb13e 100644 --- a/src/Arch.Tests/QueryTest.cs +++ b/src/Arch.Tests/QueryTest.cs @@ -1,5 +1,6 @@ using Arch.Core; using Arch.Core.Utils; +using Schedulers; using static NUnit.Framework.Assert; namespace Arch.Tests; @@ -7,7 +8,7 @@ namespace Arch.Tests; [TestFixture] public sealed partial class QueryTest { - private JobScheduler.JobScheduler _jobScheduler; + private JobScheduler _jobScheduler; private World? _world; private static readonly ComponentType[] _entityGroup = { typeof(Transform), typeof(Rotation) }; @@ -19,7 +20,12 @@ public sealed partial class QueryTest [OneTimeSetUp] public void Setup() { - _jobScheduler = new JobScheduler.JobScheduler("Test"); + _jobScheduler = new JobScheduler(new JobScheduler.Config { + ThreadPrefixName = "Arch.Samples", + ThreadCount = 0, + MaxExpectedConcurrentJobs = 64, + StrictAllocationMode = false, + }); } [OneTimeTearDown] diff --git a/src/Arch/Arch.csproj b/src/Arch/Arch.csproj index 34358036..19782d77 100644 --- a/src/Arch/Arch.csproj +++ b/src/Arch/Arch.csproj @@ -96,8 +96,9 @@ Fixed .Add when a newly non registered component was added. + - + diff --git a/src/Arch/Core/Jobs/Jobs.cs b/src/Arch/Core/Jobs/Jobs.cs index 50b1e12b..8d2edcef 100644 --- a/src/Arch/Core/Jobs/Jobs.cs +++ b/src/Arch/Core/Jobs/Jobs.cs @@ -1,6 +1,6 @@ using CommunityToolkit.HighPerformance; -using JobScheduler; using Microsoft.Extensions.ObjectPool; +using Schedulers; namespace Arch.Core; diff --git a/src/Arch/Core/Jobs/World.Jobs.cs b/src/Arch/Core/Jobs/World.Jobs.cs index 39935ddd..6f3994cf 100644 --- a/src/Arch/Core/Jobs/World.Jobs.cs +++ b/src/Arch/Core/Jobs/World.Jobs.cs @@ -1,6 +1,6 @@ using Arch.Core.Utils; using Collections.Pooled; -using IJob = JobScheduler.IJob; +using Schedulers; // ReSharper disable once CheckNamespace namespace Arch.Core; @@ -11,9 +11,9 @@ public partial class World { /// - /// A list of which are pooled to avoid allocs. + /// A list of which are pooled to avoid allocs. /// - private PooledList JobHandles { get; } + private PooledList JobHandles { get; } /// /// A cache used for the parallel queries to prevent list allocations. @@ -86,7 +86,7 @@ public void InlineParallelQuery(in QueryDescription queryDescription, in IFor public void InlineParallelChunkQuery(in QueryDescription queryDescription, in T innerJob) where T : struct, IChunkJob { // Job scheduler needs to be initialized. - if (JobScheduler.JobScheduler.Instance is null) + if (SharedJobScheduler is null) { throw new Exception("JobScheduler was not initialized, create one instance of JobScheduler. This creates a singleton used for parallel iterations."); } @@ -105,19 +105,19 @@ public void InlineParallelChunkQuery(in QueryDescription queryDescription, in job.Size = range.Length; job.Chunks = archetype.Chunks; job.Instance = innerJob; + + var jobHandle = SharedJobScheduler.Schedule(job); JobsCache.Add(job); + JobHandles.Add(jobHandle); } // Schedule, flush, wait, return. - IJob.Schedule(JobsCache, JobHandles); - JobScheduler.JobScheduler.Instance.Flush(); - JobScheduler.JobHandle.Complete(JobHandles); - JobScheduler.JobHandle.Return(JobHandles); + SharedJobScheduler.Flush(); + JobHandle.CompleteAll(JobHandles.Span); - // Return jobs to pool. - for (var jobIndex = 0; jobIndex < JobsCache.Count; jobIndex++) + for (var index = 0; index < JobsCache.Count; index++) { - var job = Unsafe.As>(JobsCache[jobIndex]); + var job = Unsafe.As>(JobsCache[index]); pool.Return(job); } diff --git a/src/Arch/Core/World.cs b/src/Arch/Core/World.cs index 5c665780..22c748cf 100644 --- a/src/Arch/Core/World.cs +++ b/src/Arch/Core/World.cs @@ -4,7 +4,7 @@ using Arch.Core.Extensions.Internal; using Arch.Core.Utils; using Collections.Pooled; -using JobScheduler; +using Schedulers; using Component = Arch.Core.Utils.Component; namespace Arch.Core; @@ -83,6 +83,11 @@ public partial class World /// public static int WorldSize { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set; } + /// + /// The shared static used for Multithreading. + /// + public static JobScheduler? SharedJobScheduler { get; set; } + /// /// Creates a instance. /// From 11ffdf351df72112e27a46cb94401ba704209899 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 31 Jan 2024 18:33:19 +0100 Subject: [PATCH 08/14] Reduced code. --- .../Queries/InlineParallelQuery.cs | 66 +------------------ src/Arch.SourceGen/Queries/ParallelQuery.cs | 64 +----------------- src/Arch/Arch.csproj | 3 +- 3 files changed, 6 insertions(+), 127 deletions(-) diff --git a/src/Arch.SourceGen/Queries/InlineParallelQuery.cs b/src/Arch.SourceGen/Queries/InlineParallelQuery.cs index eee026a4..4aa480f5 100644 --- a/src/Arch.SourceGen/Queries/InlineParallelQuery.cs +++ b/src/Arch.SourceGen/Queries/InlineParallelQuery.cs @@ -24,38 +24,7 @@ public static void AppendHpParallelQuery(this StringBuilder builder, int amount) var innerJob = new IForEachJob(); innerJob.ForEach = iForEach; - var pool = JobMeta>>.Pool; - var query = Query(in description); - foreach (var archetype in query.GetArchetypeIterator()) - { - var archetypeSize = archetype.ChunkCount; - var part = new RangePartitioner(Environment.ProcessorCount, archetypeSize); - foreach (var range in part) - { - var job = pool.Get(); - job.Start = range.Start; - job.Size = range.Length; - job.Chunks = archetype.Chunks; - job.Instance = innerJob; - - var jobHandle = SharedJobScheduler.Schedule(job); - JobsCache.Add(job); - JobHandles.Add(jobHandle); - } - - // Return jobs to pool - SharedJobScheduler.Flush(); - JobHandle.CompleteAll(JobHandles.Span); - - for (var index = 0; index < JobsCache.Count; index++) - { - var job = Unsafe.As>>(JobsCache[index]); - pool.Return(job); - } - - JobHandles.Clear(); - JobsCache.Clear(); - } + InlineParallelChunkQuery(in description, innerJob); } """; @@ -84,38 +53,7 @@ public static void AppendHpeParallelQuery(this StringBuilder builder, int amount var innerJob = new IForEachWithEntityJob(); innerJob.ForEach = iForEach; - var pool = JobMeta>>.Pool; - var query = Query(in description); - foreach (var archetype in query.GetArchetypeIterator()) { - - var archetypeSize = archetype.ChunkCount; - var part = new RangePartitioner(Environment.ProcessorCount, archetypeSize); - foreach (var range in part) - { - var job = pool.Get(); - job.Start = range.Start; - job.Size = range.Length; - job.Chunks = archetype.Chunks; - job.Instance = innerJob; - - var jobHandle = SharedJobScheduler.Schedule(job); - JobsCache.Add(job); - JobHandles.Add(jobHandle); - } - - // Return jobs to pool - SharedJobScheduler.Flush(); - JobHandle.CompleteAll(JobHandles.Span); - - for (var index = 0; index < JobsCache.Count; index++) - { - var job = Unsafe.As>>(JobsCache[index]); - pool.Return(job); - } - - JobHandles.Clear(); - JobsCache.Clear(); - } + InlineParallelChunkQuery(in description, innerJob); } """; diff --git a/src/Arch.SourceGen/Queries/ParallelQuery.cs b/src/Arch.SourceGen/Queries/ParallelQuery.cs index 2d75a29b..361a2947 100644 --- a/src/Arch.SourceGen/Queries/ParallelQuery.cs +++ b/src/Arch.SourceGen/Queries/ParallelQuery.cs @@ -24,37 +24,7 @@ public static StringBuilder AppendParallelQuery(this StringBuilder sb, int amoun var innerJob = new ForEachJob<{{generics}}>(); innerJob.ForEach = forEach; - var pool = JobMeta>>.Pool; - var query = Query(in description); - foreach (var archetype in query.GetArchetypeIterator()) { - - var archetypeSize = archetype.ChunkCount; - var part = new RangePartitioner(Environment.ProcessorCount, archetypeSize); - foreach (var range in part) - { - var job = pool.Get(); - job.Start = range.Start; - job.Size = range.Length; - job.Chunks = archetype.Chunks; - job.Instance = innerJob; - - var jobHandle = SharedJobScheduler.Schedule(job); - JobsCache.Add(job); - JobHandles.Add(jobHandle); - } - - SharedJobScheduler.Flush(); - JobHandle.CompleteAll(JobHandles.Span); - - for (var index = 0; index < JobsCache.Count; index++) - { - var job = Unsafe.As>>(JobsCache[index]); - pool.Return(job); - } - - JobHandles.Clear(); - JobsCache.Clear(); - } + InlineParallelChunkQuery(in description, innerJob); } """; @@ -84,37 +54,7 @@ public static StringBuilder AppendParallelEntityQuery(this StringBuilder sb, int var innerJob = new ForEachWithEntityJob<{{generics}}>(); innerJob.ForEach = forEach; - var pool = JobMeta>>.Pool; - var query = Query(in description); - foreach (var archetype in query.GetArchetypeIterator()) - { - var archetypeSize = archetype.ChunkCount; - var part = new RangePartitioner(Environment.ProcessorCount, archetypeSize); - foreach (var range in part) - { - var job = pool.Get(); - job.Start = range.Start; - job.Size = range.Length; - job.Chunks = archetype.Chunks; - job.Instance = innerJob; - - var jobHandle = SharedJobScheduler.Schedule(job); - JobsCache.Add(job); - JobHandles.Add(jobHandle); - } - - SharedJobScheduler.Flush(); - JobHandle.CompleteAll(JobHandles.Span); - - for (var index = 0; index < JobsCache.Count; index++) - { - var job = Unsafe.As>>(JobsCache[index]); - pool.Return(job); - } - - JobHandles.Clear(); - JobsCache.Clear(); - } + InlineParallelChunkQuery(in description, innerJob); } """; diff --git a/src/Arch/Arch.csproj b/src/Arch/Arch.csproj index 19782d77..4fe9b380 100644 --- a/src/Arch/Arch.csproj +++ b/src/Arch/Arch.csproj @@ -22,7 +22,8 @@ Fixed issue where Unsafe.As did not work on .Net 2.1 for the Dangerous-Utils. Dangerous API now allows setting/getting of recycled ids. Fixed archetype duplication after loading a save. -Fixed .Add when a newly non registered component was added. +Fixed .Add when a newly non registered component was added. +Now makes use of the updated and improved JobScheduler 1.1.1. c#;.net;.net6;.net7;ecs;game;entity;gamedev; game-development; game-engine; entity-component-system;stride;unity;godot; https://github.com/genaray/Arch From 3d084ac5c49b4070c83f27bf1b53fcb80f60ac77 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 8 Feb 2024 19:28:11 +0100 Subject: [PATCH 09/14] Fixed test, added `ScheduleInlineParallelChunkQuery` which opposes an handle that can be interacted with. --- src/Arch.Tests/QueryTest.cs | 2 ++ src/Arch/Core/Jobs/World.Jobs.cs | 57 +++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/Arch.Tests/QueryTest.cs b/src/Arch.Tests/QueryTest.cs index f04cb13e..45c090e1 100644 --- a/src/Arch.Tests/QueryTest.cs +++ b/src/Arch.Tests/QueryTest.cs @@ -26,6 +26,8 @@ public void Setup() MaxExpectedConcurrentJobs = 64, StrictAllocationMode = false, }); + + World.SharedJobScheduler = _jobScheduler; } [OneTimeTearDown] diff --git a/src/Arch/Core/Jobs/World.Jobs.cs b/src/Arch/Core/Jobs/World.Jobs.cs index 6f3994cf..16c6435d 100644 --- a/src/Arch/Core/Jobs/World.Jobs.cs +++ b/src/Arch/Core/Jobs/World.Jobs.cs @@ -78,6 +78,12 @@ public void InlineParallelQuery(in QueryDescription queryDescription, in IFor /// /// NOT thread-safe! Do not call a parallel query from anything but the main thread! /// + /// + /// Processes s parallel, but blocks the thread until all s are processed. + /// + /// + /// Pools generated s internally to avoid garbage. + /// /// A struct implementation of the interface which is called on each found. /// The which specifies which 's are searched for. /// The struct instance of the generic type being invoked. @@ -112,8 +118,9 @@ public void InlineParallelChunkQuery(in QueryDescription queryDescription, in } // Schedule, flush, wait, return. + var handle = SharedJobScheduler.CombineDependencies(JobHandles.Span); SharedJobScheduler.Flush(); - JobHandle.CompleteAll(JobHandles.Span); + handle.Complete(); for (var index = 0; index < JobsCache.Count; index++) { @@ -125,4 +132,52 @@ public void InlineParallelChunkQuery(in QueryDescription queryDescription, in JobsCache.Clear(); } } + + /// + /// Finds all matching 's by a and calls an on them. + /// + /// + /// NOT thread-safe! Do not call a parallel query from anything but the main thread! + /// + /// A struct implementation of the interface which is called on each found. + /// The which specifies which 's are searched for. + /// The struct instance of the generic type being invoked. + /// An if the was not initialized before. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public JobHandle ScheduleInlineParallelChunkQuery(in QueryDescription queryDescription, in T innerJob) where T : struct, IChunkJob + { + // Job scheduler needs to be initialized. + if (SharedJobScheduler is null) + { + throw new Exception("JobScheduler was not initialized, create one instance of JobScheduler. This creates a singleton used for parallel iterations."); + } + + // Cast pool in an unsafe fast way and run the query. + var query = Query(in queryDescription); + foreach (var archetype in query.GetArchetypeIterator()) + { + var archetypeSize = archetype.ChunkCount; + var part = new RangePartitioner(Environment.ProcessorCount, archetypeSize); + foreach (var range in part) + { + var job = new ChunkIterationJob + { + Start = range.Start, + Size = range.Length, + Chunks = archetype.Chunks, + Instance = innerJob + }; + + var jobHandle = SharedJobScheduler.Schedule(job); + JobHandles.Add(jobHandle); + } + } + + // Schedule, flush, wait, return. + var handle = SharedJobScheduler.CombineDependencies(JobHandles.Span); + SharedJobScheduler.Flush(); + JobHandles.Clear(); + + return handle; + } } From da1aae47b2e20e18e7657e4e00a6408f2c40a5f5 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 8 Feb 2024 19:39:10 +0100 Subject: [PATCH 10/14] Added `World.IsAlive(EntityReference)` for a unified API usage. --- src/Arch/Arch.csproj | 4 +++- src/Arch/Core/Entity.cs | 10 +++------- src/Arch/Core/World.cs | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/Arch/Arch.csproj b/src/Arch/Arch.csproj index 4fe9b380..96c59971 100644 --- a/src/Arch/Arch.csproj +++ b/src/Arch/Arch.csproj @@ -23,7 +23,9 @@ Fixed issue where Unsafe.As did not work on .Net 2.1 for the Dangerous-Utils. Dangerous API now allows setting/getting of recycled ids. Fixed archetype duplication after loading a save. Fixed .Add when a newly non registered component was added. -Now makes use of the updated and improved JobScheduler 1.1.1. +Now makes use of the updated and improved JobScheduler 1.1.1. +ScheduleParallelInlineQuery added. +Added World.IsAlive(EntityReference); c#;.net;.net6;.net7;ecs;game;entity;gamedev; game-development; game-engine; entity-component-system;stride;unity;godot; https://github.com/genaray/Arch diff --git a/src/Arch/Core/Entity.cs b/src/Arch/Core/Entity.cs index dad7bbaa..dd38a98b 100644 --- a/src/Arch/Core/Entity.cs +++ b/src/Arch/Core/Entity.cs @@ -300,21 +300,17 @@ public EntityReference() /// /// The .. /// True if its alive, otherwhise false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsAlive(World world) { - if (this == Null) - { - return false; - } - - var reference = world.Reference(Entity); - return this == reference; + world.IsAlive(this); } #else /// /// Checks if the referenced is still valid and alife. /// /// True if its alive, otherwhise false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsAlive() { if (this == Null) diff --git a/src/Arch/Core/World.cs b/src/Arch/Core/World.cs index 22c748cf..6b3bbe4c 100644 --- a/src/Arch/Core/World.cs +++ b/src/Arch/Core/World.cs @@ -1466,6 +1466,24 @@ public bool IsAlive(Entity entity) return EntityInfo.Has(entity.Id); } + /// + /// Checks if the is alive and valid in this . + /// + /// The . + /// True if it exists and is alive, otherwise false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + public bool IsAlive(EntityReference entityReference) + { + if (entityReference == EntityReference.Null) + { + return false; + } + + var reference = Reference(entityReference.Entity); + return entityReference == reference; + } + /// /// Returns the version of an . /// Indicating how often it was recycled. From 210f5282342d7b2c31b2a9331aa01c5c00c88b85 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 8 Feb 2024 19:39:26 +0100 Subject: [PATCH 11/14] Fixed error. --- src/Arch/Core/Entity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Arch/Core/Entity.cs b/src/Arch/Core/Entity.cs index dd38a98b..d5bd3282 100644 --- a/src/Arch/Core/Entity.cs +++ b/src/Arch/Core/Entity.cs @@ -303,7 +303,7 @@ public EntityReference() [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsAlive(World world) { - world.IsAlive(this); + return world.IsAlive(this); } #else /// From 7ffa0ee89c7e7c5aef777c6450e6ee81edbdfbe2 Mon Sep 17 00:00:00 2001 From: Lars Date: Tue, 13 Feb 2024 00:56:30 +0100 Subject: [PATCH 12/14] EXPERIMENTAL - IJobParallelFor --- src/Arch.SourceGen/Queries/Job.cs | 14 +++--- src/Arch/Core/Jobs/Jobs.cs | 80 +++++++++++++++++++------------ src/Arch/Core/Jobs/World.Jobs.cs | 47 +++++++++--------- src/Arch/Core/World.cs | 2 +- 4 files changed, 80 insertions(+), 63 deletions(-) diff --git a/src/Arch.SourceGen/Queries/Job.cs b/src/Arch.SourceGen/Queries/Job.cs index 8d259711..f48d1c20 100644 --- a/src/Arch.SourceGen/Queries/Job.cs +++ b/src/Arch.SourceGen/Queries/Job.cs @@ -24,12 +24,12 @@ public struct ForEachJob<{{generics}}> : IChunkJob public ForEach<{{generics}}> ForEach; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Execute(int index, ref Chunk chunk) + public void Execute(ref Chunk chunk) { var chunkSize = chunk.Size; {{getFirstElement}} - for (var entityIndex = chunkSize - 1; entityIndex >= 0; --entityIndex) + foreach(var entityIndex in chunk) { {{getComponents}} ForEach({{insertParams}}); @@ -63,7 +63,7 @@ public struct ForEachWithEntityJob<{{generics}}> : IChunkJob public ForEachWithEntity<{{generics}}> ForEach; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Execute(int index, ref Chunk chunk) + public void Execute(ref Chunk chunk) { ref var entityFirstElement = ref chunk.Entity(0); {{getFirstElement}} @@ -104,12 +104,12 @@ public struct IForEachJob : IChunkJob where T : struct, IForEach public T ForEach; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Execute(int index, ref Chunk chunk) + public void Execute(ref Chunk chunk) { var chunkSize = chunk.Size; {{getFirstElement}} - for (var entityIndex = chunkSize - 1; entityIndex >= 0; --entityIndex) + foreach(var entityIndex in chunk) { {{getComponents}} ForEach.Update({{insertParams}}); @@ -143,13 +143,13 @@ public struct IForEachWithEntityJob : IChunkJob where T : struct public T ForEach; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Execute(int index, ref Chunk chunk) + public void Execute(ref Chunk chunk) { var chunkSize = chunk.Size; ref var entityFirstElement = ref chunk.Entity(0); {{getFirstElement}} - for (var entityIndex = chunkSize - 1; entityIndex >= 0; --entityIndex) + foreach(var entityIndex in chunk) { var entity = Unsafe.Add(ref entityFirstElement, entityIndex); {{getComponents}} diff --git a/src/Arch/Core/Jobs/Jobs.cs b/src/Arch/Core/Jobs/Jobs.cs index 8d2edcef..46c9b872 100644 --- a/src/Arch/Core/Jobs/Jobs.cs +++ b/src/Arch/Core/Jobs/Jobs.cs @@ -64,7 +64,7 @@ public Range(int start, int length) /// public interface IChunkJob { - public void Execute(int index, ref Chunk chunk); + public void Execute(ref Chunk chunk); } /// @@ -82,10 +82,9 @@ public struct ForEachJob : IChunkJob /// /// Called on each and iterates over all 's to call the callback for each. /// - /// The chunk index. /// A reference to the chunk which is currently processed. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly void Execute(int index, ref Chunk chunk) + public readonly void Execute(ref Chunk chunk) { ref var entityFirstElement = ref chunk.Entity(0); foreach(var entityIndex in chunk) @@ -116,7 +115,7 @@ public struct IForEachJob : IChunkJob where T : IForEach /// The chunk index. /// A reference to the chunk which is currently processed. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Execute(int index, ref Chunk chunk) + public void Execute(ref Chunk chunk) { ref var entityFirstElement = ref chunk.Entity(0); foreach(var entityIndex in chunk) @@ -132,35 +131,27 @@ public void Execute(int index, ref Chunk chunk) /// is an that can be scheduled using the and the to iterate multithreaded over chunks. /// /// The generic type that implements the interface. -public sealed class ChunkIterationJob : IJob where T : IChunkJob +public sealed class ChunkIterationJob : IJobParallelFor where T : IChunkJob { /// - /// Initializes a new instance of the class. + /// Represents a section of chunk iteration from one archetype. /// - public ChunkIterationJob() + private struct ChunkIterationPart { - Chunks = Array.Empty(); + public int Start; + public int Size; + public Chunk[]? Chunks; } /// /// Initializes a new instance of the class. /// - /// The start at which this job begins to process the . - /// The size or lengths, how man this job will process. - /// The array being processed. - public ChunkIterationJob(int start, int size, Chunk[] chunks) + public ChunkIterationJob() { - Start = start; - Size = size; - Chunks = chunks; + Parts = new List(); } - /// - /// A array, this will be processed. - /// - public Chunk[] Chunks { get; set; } - /// /// An instance of the generic type , being invoked upon each chunk. /// @@ -171,22 +162,51 @@ public ChunkIterationJob(int start, int size, Chunk[] chunks) /// public int Size { get; set; } - /// - /// The start index. - /// - public int Start; + + private List Parts { get; set; } + + public int ThreadCount { get; } = Environment.ProcessorCount; + public int BatchSize { get; } = 16; /// - /// Iterates over all between and and calls . + /// Add an array of chunks to be processed by this job. /// - public void Execute() + /// The chunks to add. + /// The first chunk to process in + /// The amount of chunks to process in + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddChunks(Chunk[] chunks, int start, int size) { - ref var chunk = ref Chunks.DangerousGetReferenceAt(Start); + Parts.Add(new ChunkIterationPart{ + Chunks = chunks, + Start = start, + Size = size + }); + } - for (var chunkIndex = 0; chunkIndex < Size; chunkIndex++) + public void Execute(int index) + { + var sizeSoFar = 0; + for (var i = 0; i < Parts.Count; i++) { - ref var currentChunk = ref Unsafe.Add(ref chunk, chunkIndex); - Instance?.Execute(Start + chunkIndex, ref currentChunk); + // If we're about to go over, we're ready to execute + var part = Parts[i]; + if (sizeSoFar + part.Size > index) + { + // this had better be not null! + ref var chunk = ref part.Chunks!.DangerousGetReferenceAt(index - sizeSoFar + part.Start); + Instance?.Execute(ref chunk); + return; + } + + sizeSoFar += part.Size; } + + throw new InvalidOperationException("Reached end of chunk, but could not find the correct index!"); + } + + public void Finish() + { + Parts.Clear(); } } diff --git a/src/Arch/Core/Jobs/World.Jobs.cs b/src/Arch/Core/Jobs/World.Jobs.cs index 16c6435d..0935d7db 100644 --- a/src/Arch/Core/Jobs/World.Jobs.cs +++ b/src/Arch/Core/Jobs/World.Jobs.cs @@ -18,7 +18,7 @@ public partial class World /// /// A cache used for the parallel queries to prevent list allocations. /// - internal List JobsCache { get; set; } + internal List JobsCache { get; set; } /// /// Searches all matching 's by a and calls the passed delegate. @@ -97,42 +97,39 @@ public void InlineParallelChunkQuery(in QueryDescription queryDescription, in throw new Exception("JobScheduler was not initialized, create one instance of JobScheduler. This creates a singleton used for parallel iterations."); } + if (!SharedJobScheduler.IsMainThread) + { + throw new Exception("JobScheduler must be called from MainThread."); + } + // Cast pool in an unsafe fast way and run the query. - var pool = JobMeta>.Pool; var query = Query(in queryDescription); + + var pool = JobMeta>.Pool; + var job = pool.Get(); + job.Instance = innerJob; + + var size = 0; foreach (var archetype in query.GetArchetypeIterator()) { var archetypeSize = archetype.ChunkCount; var part = new RangePartitioner(Environment.ProcessorCount, archetypeSize); foreach (var range in part) { - var job = pool.Get(); - job.Start = range.Start; - job.Size = range.Length; - job.Chunks = archetype.Chunks; - job.Instance = innerJob; - - var jobHandle = SharedJobScheduler.Schedule(job); - JobsCache.Add(job); - JobHandles.Add(jobHandle); + job.AddChunks(archetype.Chunks, range.Start, range.Length); + size += range.Length; } + } - // Schedule, flush, wait, return. - var handle = SharedJobScheduler.CombineDependencies(JobHandles.Span); - SharedJobScheduler.Flush(); - handle.Complete(); - - for (var index = 0; index < JobsCache.Count; index++) - { - var job = Unsafe.As>(JobsCache[index]); - pool.Return(job); - } + // Schedule, flush, wait, return. + var handle = SharedJobScheduler.Schedule(job, size); + SharedJobScheduler.Flush(); + //handle.Complete(); - JobHandles.Clear(); - JobsCache.Clear(); - } + pool.Return(job); } + /* /// /// Finds all matching 's by a and calls an on them. /// @@ -179,5 +176,5 @@ public JobHandle ScheduleInlineParallelChunkQuery(in QueryDescription queryDe JobHandles.Clear(); return handle; - } + }*/ } diff --git a/src/Arch/Core/World.cs b/src/Arch/Core/World.cs index 6b3bbe4c..57e94333 100644 --- a/src/Arch/Core/World.cs +++ b/src/Arch/Core/World.cs @@ -192,7 +192,7 @@ private World(int id) // Multithreading/Jobs. JobHandles = new PooledList(Environment.ProcessorCount); - JobsCache = new List(Environment.ProcessorCount); + JobsCache = new List(Environment.ProcessorCount); } /// From 92a6b866d41ad194ba7fe165c4b1b2aa848bf254 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 15 Feb 2024 15:20:22 +0100 Subject: [PATCH 13/14] Reversed experimental changes. --- src/Arch/Core/Jobs/Jobs.cs | 73 ++++++++++++-------------------- src/Arch/Core/Jobs/World.Jobs.cs | 47 ++++++++++---------- src/Arch/Core/World.cs | 2 +- 3 files changed, 52 insertions(+), 70 deletions(-) diff --git a/src/Arch/Core/Jobs/Jobs.cs b/src/Arch/Core/Jobs/Jobs.cs index 46c9b872..5d17984b 100644 --- a/src/Arch/Core/Jobs/Jobs.cs +++ b/src/Arch/Core/Jobs/Jobs.cs @@ -131,27 +131,35 @@ public void Execute(ref Chunk chunk) /// is an that can be scheduled using the and the to iterate multithreaded over chunks. /// /// The generic type that implements the interface. -public sealed class ChunkIterationJob : IJobParallelFor where T : IChunkJob +public sealed class ChunkIterationJob : IJob where T : IChunkJob { /// - /// Represents a section of chunk iteration from one archetype. + /// Initializes a new instance of the class. /// - private struct ChunkIterationPart + public ChunkIterationJob() { - public int Start; - public int Size; - public Chunk[]? Chunks; + Chunks = Array.Empty(); } /// /// Initializes a new instance of the class. /// - public ChunkIterationJob() + /// The start at which this job begins to process the . + /// The size or lengths, how man this job will process. + /// The array being processed. + public ChunkIterationJob(int start, int size, Chunk[] chunks) { - Parts = new List(); + Start = start; + Size = size; + Chunks = chunks; } + /// + /// A array, this will be processed. + /// + public Chunk[] Chunks { get; set; } + /// /// An instance of the generic type , being invoked upon each chunk. /// @@ -162,51 +170,22 @@ public ChunkIterationJob() /// public int Size { get; set; } - - private List Parts { get; set; } - - public int ThreadCount { get; } = Environment.ProcessorCount; - public int BatchSize { get; } = 16; + /// + /// The start index. + /// + public int Start; /// - /// Add an array of chunks to be processed by this job. + /// Iterates over all between and and calls . /// - /// The chunks to add. - /// The first chunk to process in - /// The amount of chunks to process in - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddChunks(Chunk[] chunks, int start, int size) + public void Execute() { - Parts.Add(new ChunkIterationPart{ - Chunks = chunks, - Start = start, - Size = size - }); - } + ref var chunk = ref Chunks.DangerousGetReferenceAt(Start); - public void Execute(int index) - { - var sizeSoFar = 0; - for (var i = 0; i < Parts.Count; i++) + for (var chunkIndex = 0; chunkIndex < Size; chunkIndex++) { - // If we're about to go over, we're ready to execute - var part = Parts[i]; - if (sizeSoFar + part.Size > index) - { - // this had better be not null! - ref var chunk = ref part.Chunks!.DangerousGetReferenceAt(index - sizeSoFar + part.Start); - Instance?.Execute(ref chunk); - return; - } - - sizeSoFar += part.Size; + ref var currentChunk = ref Unsafe.Add(ref chunk, chunkIndex); + Instance?.Execute(ref currentChunk); } - - throw new InvalidOperationException("Reached end of chunk, but could not find the correct index!"); - } - - public void Finish() - { - Parts.Clear(); } } diff --git a/src/Arch/Core/Jobs/World.Jobs.cs b/src/Arch/Core/Jobs/World.Jobs.cs index 0935d7db..16c6435d 100644 --- a/src/Arch/Core/Jobs/World.Jobs.cs +++ b/src/Arch/Core/Jobs/World.Jobs.cs @@ -18,7 +18,7 @@ public partial class World /// /// A cache used for the parallel queries to prevent list allocations. /// - internal List JobsCache { get; set; } + internal List JobsCache { get; set; } /// /// Searches all matching 's by a and calls the passed delegate. @@ -97,39 +97,42 @@ public void InlineParallelChunkQuery(in QueryDescription queryDescription, in throw new Exception("JobScheduler was not initialized, create one instance of JobScheduler. This creates a singleton used for parallel iterations."); } - if (!SharedJobScheduler.IsMainThread) - { - throw new Exception("JobScheduler must be called from MainThread."); - } - // Cast pool in an unsafe fast way and run the query. - var query = Query(in queryDescription); - var pool = JobMeta>.Pool; - var job = pool.Get(); - job.Instance = innerJob; - - var size = 0; + var query = Query(in queryDescription); foreach (var archetype in query.GetArchetypeIterator()) { var archetypeSize = archetype.ChunkCount; var part = new RangePartitioner(Environment.ProcessorCount, archetypeSize); foreach (var range in part) { - job.AddChunks(archetype.Chunks, range.Start, range.Length); - size += range.Length; + var job = pool.Get(); + job.Start = range.Start; + job.Size = range.Length; + job.Chunks = archetype.Chunks; + job.Instance = innerJob; + + var jobHandle = SharedJobScheduler.Schedule(job); + JobsCache.Add(job); + JobHandles.Add(jobHandle); } - } - // Schedule, flush, wait, return. - var handle = SharedJobScheduler.Schedule(job, size); - SharedJobScheduler.Flush(); - //handle.Complete(); + // Schedule, flush, wait, return. + var handle = SharedJobScheduler.CombineDependencies(JobHandles.Span); + SharedJobScheduler.Flush(); + handle.Complete(); - pool.Return(job); + for (var index = 0; index < JobsCache.Count; index++) + { + var job = Unsafe.As>(JobsCache[index]); + pool.Return(job); + } + + JobHandles.Clear(); + JobsCache.Clear(); + } } - /* /// /// Finds all matching 's by a and calls an on them. /// @@ -176,5 +179,5 @@ public JobHandle ScheduleInlineParallelChunkQuery(in QueryDescription queryDe JobHandles.Clear(); return handle; - }*/ + } } diff --git a/src/Arch/Core/World.cs b/src/Arch/Core/World.cs index 57e94333..6b3bbe4c 100644 --- a/src/Arch/Core/World.cs +++ b/src/Arch/Core/World.cs @@ -192,7 +192,7 @@ private World(int id) // Multithreading/Jobs. JobHandles = new PooledList(Environment.ProcessorCount); - JobsCache = new List(Environment.ProcessorCount); + JobsCache = new List(Environment.ProcessorCount); } /// From b4823e77f45ca5afa2ec3bfa58e4c2ccb83262c0 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 15 Feb 2024 15:58:34 +0100 Subject: [PATCH 14/14] Solves #181 --- src/Arch.Tests/WorldTest.cs | 4 ++++ src/Arch/Core/World.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/Arch.Tests/WorldTest.cs b/src/Arch.Tests/WorldTest.cs index ee2b933a..d0626369 100644 --- a/src/Arch.Tests/WorldTest.cs +++ b/src/Arch.Tests/WorldTest.cs @@ -278,6 +278,10 @@ public void TrimExcess() That(world.Capacity, Is.EqualTo(archetype.EntitiesPerChunk)); That(archetype.ChunkCount, Is.EqualTo(1)); That(archetype.ChunkCapacity, Is.EqualTo(1)); + + // Recycled ids must be trimmed too so that the newest created entity is not out of bounds! + world.RecycledIds.TryPeek(out var entityId); + That(entityId.Id, Is.EqualTo(world.Capacity - 1)); } /// diff --git a/src/Arch/Core/World.cs b/src/Arch/Core/World.cs index 6b3bbe4c..b55ab099 100644 --- a/src/Arch/Core/World.cs +++ b/src/Arch/Core/World.cs @@ -411,6 +411,10 @@ public void TrimExcess() archetype.TrimExcess(); Capacity += archetype.ChunkCount * archetype.EntitiesPerChunk; // Since always one chunk always exists. } + + // Traverse recycled ids and remove all that are higher than the current capacity. + // If we do not do this, a new entity might get a id higher than the entityinfo array which causes it to go out of bounds. + RecycledIds.RemoveWhere(entity => entity.Id >= Capacity); } ///