diff --git a/NoiseEngine.Tests/Collections/Concurrent/ConcurrentListTest.cs b/NoiseEngine.Tests/Collections/Concurrent/ConcurrentListTest.cs index 03dc28e5..48790f3e 100644 --- a/NoiseEngine.Tests/Collections/Concurrent/ConcurrentListTest.cs +++ b/NoiseEngine.Tests/Collections/Concurrent/ConcurrentListTest.cs @@ -104,4 +104,26 @@ public class ConcurrentListTest { Assert.Equal(testList.OrderBy(x => x), list.OrderBy(x => x)); } + [Fact] + public void IterateDuringGrowing() { + ConcurrentList list = new ConcurrentList(); + bool works = true; + + Task[] tasks = Enumerable.Range(0, Environment.ProcessorCount * 4).Select(_ => Task.Run(() => { + int i = 0; + while (works) { + list.Remove(i); + list.Add(i++); + } + })).ToArray(); + + for (int i = 0; i < Environment.ProcessorCount * 16; i++) { + foreach (int element in list) + Assert.True(element >= 0); + } + + works = false; + Task.WaitAll(tasks); + } + } diff --git a/NoiseEngine.Tests/Physics/PhysicsTestActivatorSystem.cs b/NoiseEngine.Tests/Physics/PhysicsTestActivatorSystem.cs index fb62caad..92d1601d 100644 --- a/NoiseEngine.Tests/Physics/PhysicsTestActivatorSystem.cs +++ b/NoiseEngine.Tests/Physics/PhysicsTestActivatorSystem.cs @@ -56,7 +56,7 @@ internal partial class PhysicsTestActivatorSystem : EntitySystem { scene.AddSystem(collisionResolveSystem, cycleTime); RigidBodyInitializerSystem initalizer = new RigidBodyInitializerSystem(); - scene.AddSystem(initalizer, cycleTime); + scene.AddSystem(initalizer, 500); } private void OnUpdateEntity() { diff --git a/NoiseEngine/Collections/Concurrent/ConcurrentList.cs b/NoiseEngine/Collections/Concurrent/ConcurrentList.cs index 3155fedc..8e5f7927 100644 --- a/NoiseEngine/Collections/Concurrent/ConcurrentList.cs +++ b/NoiseEngine/Collections/Concurrent/ConcurrentList.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Xml.Linq; namespace NoiseEngine.Collections.Concurrent; @@ -117,8 +118,11 @@ public class ConcurrentList : ICollection, IReadOnlyCollection, ICollec /// from . The must have zero-based indexing. /// The zero-based index in at which copying begins. public void CopyTo(T[] array, int arrayIndex) { - foreach (T element in this) + foreach (T element in this) { + if (arrayIndex >= array.Length) + break; array[arrayIndex++] = element; + } } /// diff --git a/NoiseEngine/Collections/Concurrent/ConcurrentListSegment.cs b/NoiseEngine/Collections/Concurrent/ConcurrentListSegment.cs index 505a1352..1c3d94f0 100644 --- a/NoiseEngine/Collections/Concurrent/ConcurrentListSegment.cs +++ b/NoiseEngine/Collections/Concurrent/ConcurrentListSegment.cs @@ -7,11 +7,11 @@ namespace NoiseEngine.Collections.Concurrent; internal class ConcurrentListSegment : IEnumerable { - private readonly ConcurrentListSegmentValue[] items; + internal readonly ConcurrentListSegmentValue[] items; private ConcurrentListSegment? previous; - private int nextIndex; + internal int nextIndex; private int count; public int Capacity => items.Length; diff --git a/NoiseEngine/Jobs/EntityScheduleWorker.cs b/NoiseEngine/Jobs/EntityScheduleWorker.cs index 374215c0..ef681b64 100644 --- a/NoiseEngine/Jobs/EntityScheduleWorker.cs +++ b/NoiseEngine/Jobs/EntityScheduleWorker.cs @@ -100,8 +100,7 @@ internal class EntityScheduleWorker : IDisposable { enqueueThreadLocker.WaitOne(); long executionTime = DateTime.UtcNow.Ticks; - EntitySystem[] sortedSystems = - systems.OrderByDescending(x => executionTime - x.lastExecutionTime).ToArray(); + EntitySystem[] sortedSystems = systems.OrderBy(x => x.lastExecutionTime + x.cycleTimeWithDelta).ToArray(); if (sortedSystems.Length == 0) continue; @@ -110,14 +109,12 @@ internal class EntityScheduleWorker : IDisposable { foreach (EntitySystem system in sortedSystems) { double executionTimeDifference = executionTime - system.lastExecutionTime; if (system.cycleTimeWithDelta >= executionTimeDifference) - break; - - if (!system.TryOrderWork()) continue; - packages.Enqueue(new SchedulePackage(true, system, null, 0, 0)); - SignalExecutorThreads(); + if (!system.TryOrderWork(true)) + continue; + EnqueueCycleBegin(system); needToWait = false; } @@ -156,8 +153,8 @@ internal class EntityScheduleWorker : IDisposable { EntityLocker locker = executionData.Chunk!.GetLocker(); if ( - executionData.System.ComponentWriteAccess ? !locker.TryEnterWriteLock(1) : - !locker.TryEnterReadLock(1) + executionData.System.ComponentWriteAccess ? !locker.TryEnterWriteLock(0) : + !locker.TryEnterReadLock(0) ) { packages.Enqueue(executionData); continue; diff --git a/NoiseEngine/Jobs/EntitySystemT0.cs b/NoiseEngine/Jobs/EntitySystemT0.cs index 721691a4..fc8c5fa0 100644 --- a/NoiseEngine/Jobs/EntitySystemT0.cs +++ b/NoiseEngine/Jobs/EntitySystemT0.cs @@ -188,6 +188,7 @@ protected struct NoiseEngineInternal_DoNotUse { private readonly object scheduleLocker = new object(); private readonly object enabledLocker = new object(); private readonly Dictionary dependencies = new Dictionary(); + private readonly ConcurrentQueue waitingForExecution = new ConcurrentQueue(); private EntityWorld? world; private uint ongoingWork; @@ -323,7 +324,7 @@ protected struct NoiseEngineInternal_DoNotUse { /// when cycle was enqueued; otherwise . public bool TryExecute() { EntitySchedule? schedule = Schedule; - if (schedule is null || !TryOrderWork()) + if (schedule is null || !TryOrderWork(false)) return false; schedule.Worker.EnqueueCycleBegin(this); return true; @@ -335,7 +336,7 @@ protected struct NoiseEngineInternal_DoNotUse { /// when cycle was executed; otherwise . public bool TryExecuteAndWait() { EntitySchedule? schedule = Schedule; - if (schedule is null || !TryOrderWork()) + if (schedule is null || !TryOrderWork(false)) return false; schedule.Worker.EnqueuePackages(this); Wait(); @@ -463,13 +464,18 @@ protected struct NoiseEngineInternal_DoNotUse { long executionTime = DateTime.UtcNow.Ticks; long difference = executionTime - lastExecutionTime; + Debug.Assert(difference >= 0); + DeltaTime = difference / (double)TimeSpan.TicksPerSecond; DeltaTimeF = (float)DeltaTime; double? cycleTime = CycleTime; if (cycleTime.HasValue) { long cycleTimeInTicks = (long)(cycleTime.Value * TimeSpan.TicksPerMillisecond); - cycleTimeWithDelta = cycleTimeInTicks - (difference - cycleTimeInTicks); + if (difference <= cycleTimeInTicks) + cycleTimeWithDelta = cycleTimeInTicks; + else + cycleTimeWithDelta = cycleTimeInTicks - (difference - cycleTimeInTicks); } lastExecutionTime = executionTime; @@ -520,6 +526,17 @@ protected struct NoiseEngineInternal_DoNotUse { workResetEvent.Set(); } + if (!waitingForExecution.IsEmpty) { + long executionTime = DateTime.UtcNow.Ticks; + while (waitingForExecution.TryDequeue(out EntitySystem? system)) { + double executionTimeDifference = executionTime - system.lastExecutionTime; + if (system.cycleTimeWithDelta >= executionTimeDifference) + continue; + + system.TryExecute(); + } + } + if (!enabled) { lock (enabledLocker) { OnStop(); @@ -528,12 +545,15 @@ protected struct NoiseEngineInternal_DoNotUse { } } - internal bool TryOrderWork() { + internal bool TryOrderWork(bool invokedFromSchedule) { if (!Enabled || IsDisposed || isWorking.Exchange(true)) return false; foreach (EntitySystem system in Dependencies) { if (system.cycleCount == dependencies[system]) { + if (invokedFromSchedule) + system.AddSystemToWaitingForExecution(this); + lock (workLocker) { isWorking = false; workResetEvent.Set(); @@ -546,6 +566,10 @@ protected struct NoiseEngineInternal_DoNotUse { return true; } + internal void AddSystemToWaitingForExecution(EntitySystem system) { + waitingForExecution.Enqueue(system); + } + /// /// This method is executed when this system is initializing. /// @@ -583,7 +607,7 @@ protected struct NoiseEngineInternal_DoNotUse { } private void WaitWhenCanExecuteAndOrderWork() { - while (!TryOrderWork()) { + while (!TryOrderWork(false)) { AssertCouldExecute(); Wait(); }