From 8f6ad0f67c8225bc8cb75545f16bd01389541eee Mon Sep 17 00:00:00 2001 From: hadashiA Date: Wed, 29 May 2024 20:10:37 +0900 Subject: [PATCH 1/4] Refine PlayerLoopRunner loop --- .../VContainer/Runtime/Internal/FreeList.cs | 177 ++++++++++++++++++ .../Runtime/Internal/FreeList.cs.meta | 11 ++ .../Runtime/Unity/PlayerLoopRunner.cs | 70 +------ .../VContainer/Runtime/VContainer.asmdef | 4 +- 4 files changed, 200 insertions(+), 62 deletions(-) create mode 100644 VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs create mode 100644 VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs.meta diff --git a/VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs b/VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs new file mode 100644 index 00000000..a15c1a87 --- /dev/null +++ b/VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs @@ -0,0 +1,177 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace VContainer.Internal +{ + static class UnsafeHelper + { + public static ref TTo As(ref TFrom from) + { +#if UNITY_2021_3_OR_NEWER + return ref global::Unity.Collections.LowLevel.Unsafe.UnsafeUtility.As(ref from); +#else + return ref System.Runtime.CompilerServices.Unsafe.As(ref from); +#endif + } + } + + class FreeList where T : class + { + public bool IsDisposed => lastIndex == -2; + + readonly object gate = new(); + T?[] values; + int lastIndex = -1; + + public FreeList(int initialCapacity) + { + values = new T[initialCapacity]; + } + + public ReadOnlySpan AsSpan() + { + if (lastIndex < 0) + { + return ReadOnlySpan.Empty; + } + return values.AsSpan(0, lastIndex + 1); + } + + public T? this[int index] => values[index]; + + public void Add(T item) + { + lock (gate) + { + CheckDispose(); + + // try find blank + var index = FindNullIndex(values); + if (index == -1) + { + // full, 1, 4, 6,...resize(x1.5) + var len = values.Length; + var newValues = new T[len + len / 2]; + Array.Copy(values, newValues, len); + values = newValues; + index = len; + } + + values[index] = item; + if (lastIndex < index) + { + lastIndex = index; + } + } + } + + public void RemoveAt(int index) + { + lock (gate) + { + if (index < values.Length) + { + ref var v = ref values[index]; + if (v == null) throw new KeyNotFoundException($"key index {index} is not found."); + + v = null; + if (index == lastIndex) + { + lastIndex = FindLastNonNullIndex(values, index); + } + } + } + } + + public bool Remove(T value) + { + lock (gate) + { + if (lastIndex < 0) return false; + + var index = -1; + var span = values.AsSpan(0, lastIndex + 1); + for (var i = 0; i < span.Length; i++) + { + if (span[i] == value) + { + index = i; + break; + } + } + + if (index != -1) + { + RemoveAt(index); + return true; + } + } + return false; + } + + public void Clear() + { + lock (gate) + { + if (lastIndex > 0) + { + values.AsSpan(0, lastIndex + 1).Clear(); + lastIndex = -1; + } + } + } + + public void Dispose() + { + lock (gate) + { + lastIndex = -2; // -2 is disposed. + } + } + + void CheckDispose() + { + if (IsDisposed) + { + throw new ObjectDisposedException(GetType().FullName); + } + } + + static unsafe int FindNullIndex(T?[] target) + { + ref var head = ref UnsafeHelper.As(ref MemoryMarshal.GetReference(target.AsSpan())); + fixed (void* p = &head) + { + var span = new ReadOnlySpan(p, target.Length); + +#if NETSTANDARD2_1 + return span.IndexOf(IntPtr.Zero); +#else + for (int i = 0; i < span.Length; i++) + { + if (span[i] == IntPtr.Zero) return i; + } + return -1; +#endif + } + } + + static unsafe int FindLastNonNullIndex(T?[] target, int lastIndex) + { + ref var head = ref UnsafeHelper.As(ref MemoryMarshal.GetReference(target.AsSpan())); + fixed (void* p = &head) + { + var span = new ReadOnlySpan(p, lastIndex); // without lastIndexed value. + + for (var i = span.Length - 1; i >= 0; i--) + { + if (span[i] != IntPtr.Zero) return i; + } + + return -1; + } + } + } +} \ No newline at end of file diff --git a/VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs.meta b/VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs.meta new file mode 100644 index 00000000..ea16e759 --- /dev/null +++ b/VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ab65b7c977d342df8c7b3d914e896b30 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VContainer/Assets/VContainer/Runtime/Unity/PlayerLoopRunner.cs b/VContainer/Assets/VContainer/Runtime/Unity/PlayerLoopRunner.cs index 3bb78af9..2f6a29a1 100644 --- a/VContainer/Assets/VContainer/Runtime/Unity/PlayerLoopRunner.cs +++ b/VContainer/Assets/VContainer/Runtime/Unity/PlayerLoopRunner.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Threading; +using VContainer.Internal; namespace VContainer.Unity { @@ -11,78 +9,30 @@ interface IPlayerLoopItem sealed class PlayerLoopRunner { - readonly Queue runningQueue = new Queue(); - readonly Queue waitingQueue = new Queue(); - - readonly object runningGate = new object(); - readonly object waitingGate = new object(); + readonly FreeList runners = new FreeList(16); int running; public void Dispatch(IPlayerLoopItem item) { - if (Interlocked.CompareExchange(ref running, 1, 1) == 1) - { - lock (waitingGate) - { - waitingQueue.Enqueue(item); - return; - } - } - - lock (runningGate) - { - runningQueue.Enqueue(item); - } + runners.Add(item); } public void Run() { - Interlocked.Exchange(ref running, 1); - - lock (runningGate) - lock (waitingGate) - { - while (waitingQueue.Count > 0) - { - var waitingItem = waitingQueue.Dequeue(); - runningQueue.Enqueue(waitingItem); - } - } - - IPlayerLoopItem item; - lock (runningGate) - { - item = runningQueue.Count > 0 ? runningQueue.Dequeue() : null; - } - - while (item != null) + var span = runners.AsSpan(); + for (var i = 0; i < span.Length; i++) { - var continuous = false; - try + var item = span[i]; + if (item != null) { - continuous = item.MoveNext(); - } - catch (Exception ex) - { - UnityEngine.Debug.LogException(ex); - } - - if (continuous) - { - lock (waitingGate) + var continued = item.MoveNext(); + if (!continued) { - waitingQueue.Enqueue(item); + runners.RemoveAt(i); } } - - lock (runningGate) - { - item = runningQueue.Count > 0 ? runningQueue.Dequeue() : null; - } } - - Interlocked.Exchange(ref running, 0); } } } diff --git a/VContainer/Assets/VContainer/Runtime/VContainer.asmdef b/VContainer/Assets/VContainer/Runtime/VContainer.asmdef index 0c4c82a0..5473e0e5 100644 --- a/VContainer/Assets/VContainer/Runtime/VContainer.asmdef +++ b/VContainer/Assets/VContainer/Runtime/VContainer.asmdef @@ -8,7 +8,7 @@ ], "includePlatforms": [], "excludePlatforms": [], - "allowUnsafeCode": false, + "allowUnsafeCode": true, "overrideReferences": false, "precompiledReferences": [], "autoReferenced": true, @@ -26,4 +26,4 @@ } ], "noEngineReferences": false -} +} \ No newline at end of file From 090c7bac8c1763a729ea66343b01df1401944e21 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Sun, 2 Jun 2024 20:34:05 +0900 Subject: [PATCH 2/4] Fix ci --- VContainer.Standalone/VContainer.Standalone.csproj | 1 + VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/VContainer.Standalone/VContainer.Standalone.csproj b/VContainer.Standalone/VContainer.Standalone.csproj index dec2593d..742ce4bf 100644 --- a/VContainer.Standalone/VContainer.Standalone.csproj +++ b/VContainer.Standalone/VContainer.Standalone.csproj @@ -1,6 +1,7 @@ net8.0 + true where T : class { public bool IsDisposed => lastIndex == -2; - readonly object gate = new(); + readonly object gate = new object(); T?[] values; int lastIndex = -1; From cd38a36cf7f7ad9548325d20fc8ceab7179e2b29 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Sun, 16 Jun 2024 20:38:54 +0900 Subject: [PATCH 3/4] Fix for netstandard2.0 --- VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs | 2 ++ .../Assets/VContainer/Runtime/Unity/PlayerLoopRunner.cs | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs b/VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs index eead7f2c..cc07ab9b 100644 --- a/VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs +++ b/VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs @@ -30,6 +30,7 @@ public FreeList(int initialCapacity) values = new T[initialCapacity]; } +#if NETSTANDARD2_1 public ReadOnlySpan AsSpan() { if (lastIndex < 0) @@ -38,6 +39,7 @@ public FreeList(int initialCapacity) } return values.AsSpan(0, lastIndex + 1); } +#endif public T? this[int index] => values[index]; diff --git a/VContainer/Assets/VContainer/Runtime/Unity/PlayerLoopRunner.cs b/VContainer/Assets/VContainer/Runtime/Unity/PlayerLoopRunner.cs index 2f6a29a1..da4837dd 100644 --- a/VContainer/Assets/VContainer/Runtime/Unity/PlayerLoopRunner.cs +++ b/VContainer/Assets/VContainer/Runtime/Unity/PlayerLoopRunner.cs @@ -20,7 +20,12 @@ public void Dispatch(IPlayerLoopItem item) public void Run() { - var span = runners.AsSpan(); + var span = +#if NETSTANDARD2_1 + runners.AsSpan(); +#else + runners; +#endif for (var i = 0; i < span.Length; i++) { var item = span[i]; From 40490db752d8eff00dbf93915a1fdb0cdec8b7d5 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Sat, 22 Jun 2024 00:11:14 +0900 Subject: [PATCH 4/4] Support unity 201x --- .../VContainer/Runtime/Internal/FreeList.cs | 64 +++++++++++-------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs b/VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs index cc07ab9b..2ef8a296 100644 --- a/VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs +++ b/VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs @@ -1,28 +1,18 @@ -#nullable enable using System; using System.Collections.Generic; -using System.Runtime.InteropServices; - -namespace VContainer.Internal -{ - static class UnsafeHelper - { - public static ref TTo As(ref TFrom from) - { #if UNITY_2021_3_OR_NEWER - return ref global::Unity.Collections.LowLevel.Unsafe.UnsafeUtility.As(ref from); -#else - return ref System.Runtime.CompilerServices.Unsafe.As(ref from); +using Unity.Collections.LowLevel.Unsafe; #endif - } - } +namespace VContainer.Internal +{ class FreeList where T : class { public bool IsDisposed => lastIndex == -2; + public int Length => lastIndex + 1; readonly object gate = new object(); - T?[] values; + T[] values; int lastIndex = -1; public FreeList(int initialCapacity) @@ -31,17 +21,17 @@ public FreeList(int initialCapacity) } #if NETSTANDARD2_1 - public ReadOnlySpan AsSpan() + public ReadOnlySpan AsSpan() { if (lastIndex < 0) { - return ReadOnlySpan.Empty; + return ReadOnlySpan.Empty; } return values.AsSpan(0, lastIndex + 1); } #endif - public T? this[int index] => values[index]; + public T this[int index] => values[index]; public void Add(T item) { @@ -94,10 +84,9 @@ public bool Remove(T value) if (lastIndex < 0) return false; var index = -1; - var span = values.AsSpan(0, lastIndex + 1); - for (var i = 0; i < span.Length; i++) + for (var i = 0; i < values.Length; i++) { - if (span[i] == value) + if (values[i] == value) { index = i; break; @@ -110,6 +99,7 @@ public bool Remove(T value) return true; } } + return false; } @@ -119,7 +109,7 @@ public void Clear() { if (lastIndex > 0) { - values.AsSpan(0, lastIndex + 1).Clear(); + Array.Clear(values, 0, lastIndex + 1); lastIndex = -1; } } @@ -141,9 +131,10 @@ void CheckDispose() } } - static unsafe int FindNullIndex(T?[] target) +#if UNITY_2021_3_OR_NEWER + static unsafe int FindNullIndex(T[] target) { - ref var head = ref UnsafeHelper.As(ref MemoryMarshal.GetReference(target.AsSpan())); + ref var head = ref UnsafeUtility.As(ref MemoryMarshal.GetReference(target.AsSpan())); fixed (void* p = &head) { var span = new ReadOnlySpan(p, target.Length); @@ -160,9 +151,9 @@ static unsafe int FindNullIndex(T?[] target) } } - static unsafe int FindLastNonNullIndex(T?[] target, int lastIndex) + static unsafe int FindLastNonNullIndex(T[] target, int lastIndex) { - ref var head = ref UnsafeHelper.As(ref MemoryMarshal.GetReference(target.AsSpan())); + ref var head = ref UnsafeUtility.As(ref MemoryMarshal.GetReference(target.AsSpan())); fixed (void* p = &head) { var span = new ReadOnlySpan(p, lastIndex); // without lastIndexed value. @@ -175,5 +166,24 @@ static unsafe int FindLastNonNullIndex(T?[] target, int lastIndex) return -1; } } +#else + static int FindNullIndex(T[] target) + { + for (var i = 0; i < target.Length; i++) + { + if (target[i] == null) return i; + } + return -1; + } + + static int FindLastNonNullIndex(T[] target, int lastIndex) + { + for (var i = lastIndex; i >= 0; i--) + { + if (target[i] != null) return i; + } + return -1; + } +#endif } -} \ No newline at end of file +}