108 lines
3.5 KiB
C#
108 lines
3.5 KiB
C#
#if UNITY_WEBGL && !UNITY_EDITOR
|
|
#define SINGLE_THREAD
|
|
#endif
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
|
|
namespace Pathfinding.Util {
|
|
/// <summary>
|
|
/// Helper for parallelizing tasks.
|
|
/// More specifically this class is useful if the tasks need some large and slow to initialize 'scratch pad'.
|
|
/// Using this class you can initialize a scratch pad per thread and then use the appropriate one in the task
|
|
/// callback (which includes a thread index).
|
|
///
|
|
/// Any exception that is thrown in the worker threads will be propagated out to the caller of the <see cref="Run"/> method.
|
|
/// </summary>
|
|
public class ParallelWorkQueue<T> {
|
|
/// <summary>
|
|
/// Callback to run for each item in the queue.
|
|
/// The callback takes the item as the first parameter and the thread index as the second parameter.
|
|
/// </summary>
|
|
public System.Action<T, int> action;
|
|
|
|
/// <summary>Number of threads to use</summary>
|
|
public readonly int threadCount;
|
|
|
|
/// <summary>Queue of items</summary>
|
|
readonly Queue<T> queue;
|
|
readonly int initialCount;
|
|
#if !SINGLE_THREAD
|
|
ManualResetEvent[] waitEvents;
|
|
System.Exception innerException;
|
|
#endif
|
|
|
|
public ParallelWorkQueue (Queue<T> queue) {
|
|
this.queue = queue;
|
|
initialCount = queue.Count;
|
|
#if SINGLE_THREAD
|
|
threadCount = 1;
|
|
#else
|
|
threadCount = System.Math.Min(initialCount, System.Math.Max(1, AstarPath.CalculateThreadCount(ThreadCount.AutomaticHighLoad)));
|
|
#endif
|
|
}
|
|
|
|
/// <summary>Execute the tasks.</summary>
|
|
/// <param name="progressTimeoutMillis">This iterator will yield approximately every progressTimeoutMillis milliseconds.
|
|
/// This can be used to e.g show a progress bar.</param>
|
|
public IEnumerable<int> Run (int progressTimeoutMillis) {
|
|
if (initialCount != queue.Count) throw new System.InvalidOperationException("Queue has been modified since the constructor");
|
|
|
|
// Return early if there are no items in the queue.
|
|
// This is important because WaitHandle.WaitAll with an array of length zero
|
|
// results in weird behaviour (Microsoft's .Net returns false, Mono returns true
|
|
// and the documentation says it should throw an exception).
|
|
if (initialCount == 0) yield break;
|
|
|
|
#if SINGLE_THREAD
|
|
// WebGL does not support multithreading so we will do everything on the main thread instead
|
|
for (int i = 0; i < initialCount; i++) {
|
|
action(queue.Dequeue(), 0);
|
|
yield return i + 1;
|
|
}
|
|
#else
|
|
// Fire up a bunch of threads to scan the graph in parallel
|
|
waitEvents = new ManualResetEvent[threadCount];
|
|
for (int i = 0; i < waitEvents.Length; i++) {
|
|
waitEvents[i] = new ManualResetEvent(false);
|
|
#if NETFX_CORE
|
|
// Need to make a copy here, otherwise it may refer to some other index when the task actually runs.
|
|
int threadIndex = i;
|
|
System.Threading.Tasks.Task.Run(() => RunTask(threadIndex));
|
|
#else
|
|
ThreadPool.QueueUserWorkItem(threadIndex => RunTask((int)threadIndex), i);
|
|
#endif
|
|
}
|
|
|
|
while (!WaitHandle.WaitAll(waitEvents, progressTimeoutMillis)) {
|
|
int count;
|
|
lock (queue) count = queue.Count;
|
|
yield return initialCount - count;
|
|
}
|
|
|
|
if (innerException != null) throw innerException;
|
|
#endif
|
|
}
|
|
|
|
#if !SINGLE_THREAD
|
|
void RunTask (int threadIndex) {
|
|
try {
|
|
while (true) {
|
|
T tile;
|
|
lock (queue) {
|
|
if (queue.Count == 0) return;
|
|
tile = queue.Dequeue();
|
|
}
|
|
action(tile, threadIndex);
|
|
}
|
|
} catch (System.Exception e) {
|
|
innerException = e;
|
|
// Stop the remaining threads
|
|
lock (queue) queue.Clear();
|
|
} finally {
|
|
waitEvents[threadIndex].Set();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|