Skip to content

Commit

Permalink
Refine PlayerLoopRunner loop
Browse files Browse the repository at this point in the history
  • Loading branch information
hadashiA committed Jun 16, 2024
1 parent 5ec983c commit 7825e9b
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 62 deletions.
177 changes: 177 additions & 0 deletions VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs
Original file line number Diff line number Diff line change
@@ -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<TFrom, TTo>(ref TFrom from)
{
#if UNITY_2021_3_OR_NEWER
return ref global::Unity.Collections.LowLevel.Unsafe.UnsafeUtility.As<TFrom, TTo>(ref from);
#else
return ref System.Runtime.CompilerServices.Unsafe.As<TFrom, TTo>(ref from);
#endif
}
}

class FreeList<T> 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<T?> AsSpan()
{
if (lastIndex < 0)
{
return ReadOnlySpan<T?>.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<T?, IntPtr>(ref MemoryMarshal.GetReference(target.AsSpan()));
fixed (void* p = &head)
{
var span = new ReadOnlySpan<IntPtr>(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<T?, IntPtr>(ref MemoryMarshal.GetReference(target.AsSpan()));
fixed (void* p = &head)
{
var span = new ReadOnlySpan<IntPtr>(p, lastIndex); // without lastIndexed value.

for (var i = span.Length - 1; i >= 0; i--)
{
if (span[i] != IntPtr.Zero) return i;
}

return -1;
}
}
}
}
11 changes: 11 additions & 0 deletions VContainer/Assets/VContainer/Runtime/Internal/FreeList.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 10 additions & 60 deletions VContainer/Assets/VContainer/Runtime/Unity/PlayerLoopRunner.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Threading;
using VContainer.Internal;

namespace VContainer.Unity
{
Expand All @@ -11,78 +9,30 @@ interface IPlayerLoopItem

sealed class PlayerLoopRunner
{
readonly Queue<IPlayerLoopItem> runningQueue = new Queue<IPlayerLoopItem>();
readonly Queue<IPlayerLoopItem> waitingQueue = new Queue<IPlayerLoopItem>();

readonly object runningGate = new object();
readonly object waitingGate = new object();
readonly FreeList<IPlayerLoopItem> runners = new FreeList<IPlayerLoopItem>(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);
}
}
}
4 changes: 2 additions & 2 deletions VContainer/Assets/VContainer/Runtime/VContainer.asmdef
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
Expand All @@ -26,4 +26,4 @@
}
],
"noEngineReferences": false
}
}

0 comments on commit 7825e9b

Please sign in to comment.