From 7825e9be57af2b103e3ff4d4e50229f1c2a01561 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Wed, 29 May 2024 20:10:37 +0900 Subject: [PATCH] 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