SimpleLooper.cs
using System;
using System.Collections.Generic;
using System.Threading;
namespace BEPUphysics.Threading
{
///
/// Manages the engine's threads.
///
///
/// Uses a simple round-robin threadpool.
/// It is recommended that other thread managers are used instead of this one;
/// it is kept for compatibility and a fallback in case of problems.
///
public clast SimpleLooper : IParallelLooper
{
private readonly ManualResetEvent allThreadsIdleNotifier = new ManualResetEvent(false);
private readonly object disposedLocker = new object();
private readonly Action doLoopSectionDelegate;
private readonly List taskInfos = new List();
private readonly List workers = new List();
///
/// Index into the thread loop lists, incremented after each task allocation.
///
private int currentTaskAllocationIndex;
private bool disposed;
private int loopTasksPerThread;
private int tasksRemaining = 1;
///
/// Constructs the thread manager.
///
public SimpleLooper()
{
LoopTasksPerThread = 1;
doLoopSectionDelegate = new Action(DoLoopSection);
}
///
/// Releases resources used by the object.
///
~SimpleLooper()
{
Dispose();
}
///
/// Gets or sets the number of tasks to create per thread when doing forLoops.
///
public int LoopTasksPerThread
{
get { return loopTasksPerThread; }
set
{
loopTasksPerThread = value;
RemakeLoopSections();
}
}
///
/// Gets the number of threads currently handled by the manager.
///
public int ThreadCount
{
get { return workers.Count; }
}
///
/// Blocks the current thread until all tasks have been completed.
///
public void WaitForTaskCompletion()
{
if (Interlocked.Decrement(ref tasksRemaining) == 0)
{
allThreadsIdleNotifier.Set();
}
allThreadsIdleNotifier.WaitOne();
//When it gets here, it means things are successfully idle'd.
tasksRemaining = 1;
allThreadsIdleNotifier.Reset();
}
///
/// Adds a thread to the manager.
///
public void AddThread()
{
AddThread(null, null);
}
///
/// Adds a thread to the manager.
///
/// A function to run to perform any initialization on the new thread.
/// Data to give the ParameterizedThreadStart for initialization.
public void AddThread(Action initialization, object initializationInformation)
{
lock (workers)
{
var worker = new WorkerThread(this, initialization, initializationInformation);
workers.Add(worker);
RemakeLoopSections();
}
}
///
/// Removes a thread and blocks until success.
///
public void RemoveThread()
{
EnqueueTask(null, null);
WaitForTaskCompletion();
RemakeLoopSections();
}
///
/// Gives the thread manager a new task to run.
///
/// Task to run.
/// Information to be used by the task.
public void EnqueueTask(Action task, object taskInformation)
{
lock (workers)
{
workers[currentTaskAllocationIndex].EnqueueTask(task, taskInformation);
currentTaskAllocationIndex = (currentTaskAllocationIndex + 1) % workers.Count;
}
}
///
/// Loops from the starting index (inclusive) to the ending index (exclusive), calling the loopBody at each iteration.
/// The forLoop function will not return until all iterations are complete.
/// This is meant to be used in a 'fork-join' model; only a single thread should be running a forLoop
/// at any time.
///
/// Inclusive starting index.
/// Exclusive ending index.
/// Function that handles an individual iteration of the loop.
public void ForLoop(int startIndex, int endIndex, Action loopBody)
{
int subdivisions = workers.Count * loopTasksPerThread;
int iterationCount = endIndex - startIndex;
for (int b = 0; b < subdivisions; b++)
{
taskInfos[b].loopBody = loopBody;
taskInfos[b].iterationCount = iterationCount;
EnqueueTaskSequentially(doLoopSectionDelegate, taskInfos[b]);
}
WaitForTaskCompletion();
}
///
/// Releases threads and resources used by the thread manager.
///
public void Dispose()
{
lock (disposedLocker)
{
if (!disposed)
{
disposed = true;
ShutDown();
allThreadsIdleNotifier.Close();
GC.SuppressFinalize(this);
}
}
}
///
/// Enqueues a task.
/// This method also does not perform any locking; it should only be called when all worker threads of the thread pool are idle and all calls to this method are from the same thread.
///
/// Task to enqueue.
/// Information for the task.
public void EnqueueTaskSequentially(Action task, object taskInformation)
{
//enqueueTask(task, taskInformation);
workers[currentTaskAllocationIndex].EnqueueTask(task, taskInformation);
currentTaskAllocationIndex = (currentTaskAllocationIndex + 1) % workers.Count;
//workers[currentTaskAllocationIndex].enqueueTaskSequentially(task, taskInformation);
//currentTaskAllocationIndex = (currentTaskAllocationIndex + 1) % workers.Count;
}
///
/// Tells every thread in the thread manager to shut down and waits until completion.
///
public void ShutDown()
{
var toJoin = new Queue();
for (int i = workers.Count - 1; i >= 0; i--)
{
lock (workers)
{
toJoin.Enqueue(workers[i].Thread);
workers[i].EnqueueTask(null, null);
}
}
while (toJoin.Count > 0)
{
toJoin.Dequeue().Join();
}
}
private static void DoLoopSection(object o)
{
var data = o as LoopSection;
int finalIndex = (data.iterationCount * (data.Index + 1)) / data.Subdivisions;
for (int i = (data.iterationCount * data.Index) / data.Subdivisions; i < finalIndex; i++)
{
//do stuff
data.loopBody(i);
}
}
private void RemakeLoopSections()
{
taskInfos.Clear();
int workerCount = workers.Count;
int subdivisions = workerCount * loopTasksPerThread;
for (int i = 0; i < workerCount; i++)
{
for (int j = 0; j < loopTasksPerThread; j++)
{
taskInfos.Add(new LoopSection(i * loopTasksPerThread + j, subdivisions));
}
}
}
private clast LoopSection
{
internal readonly int Index;
internal readonly int Subdivisions;
internal int iterationCount;
internal Action loopBody;
internal LoopSection(int index, int subdivisions)
{
Index = index;
Subdivisions = subdivisions;
}
}
private clast WorkerThread : IDisposable
{
private readonly object disposedLocker = new object();
private readonly object initializationInformation;
private readonly SimpleLooper manager;
private readonly AutoResetEvent resetEvent = new AutoResetEvent(false);
private readonly Queue taskInformationQueue;
private readonly Queue taskQueue;
internal readonly Thread Thread;
private readonly Action threadStart;
private bool disposed;
internal WorkerThread(SimpleLooper manager, Action threadStart, object initializationInformation)
{
this.manager = manager;
Thread = new Thread(ThreadExecutionLoop);
Thread.IsBackground = true;
taskQueue = new Queue();
taskInformationQueue = new Queue();
this.threadStart = threadStart;
this.initializationInformation = initializationInformation;
Thread.Start();
}
///
/// Shuts down any still living threads.
///
~WorkerThread()
{
Dispose();
}
#region IDisposable Members
public void Dispose()
{
lock (disposedLocker)
{
if (!disposed)
{
disposed = true;
ShutDownThread();
resetEvent.Close();
GC.SuppressFinalize(this);
}
}
}
#endregion
internal void EnqueueTask(Action task, object taskInformation)
{
lock (taskQueue)
{
Interlocked.Increment(ref manager.tasksRemaining);
taskQueue.Enqueue(task);
taskInformationQueue.Enqueue(taskInformation);
resetEvent.Set();
}
}
private void ShutDownThread()
{
//Let the manager know that it is done with its 'task'!
if (Interlocked.Decrement(ref manager.tasksRemaining) == 0)
{
if (!manager.disposed) //Don't mess with the handle if it's already disposed.
manager.allThreadsIdleNotifier.Set();
}
//Dump out any remaining tasks in the queue.
for (int i = 0; i < taskQueue.Count; i++) //This is still safe since shutDownThread is called from within a lock(taskQueue) block.
{
taskQueue.Dequeue();
if (Interlocked.Decrement(ref manager.tasksRemaining) == 0)
{
if (!manager.disposed) //Don't mess with the handle if it's already disposed.
manager.allThreadsIdleNotifier.Set();
}
}
lock (manager.workers)
manager.workers.Remove(this);
}
/// Thrown when the thread encounters an invalid state; generally propagated float.NaN's.
private void ThreadExecutionLoop()
{
//Perform any initialization requested.
if (threadStart != null)
threadStart(initializationInformation);
object information = null;
while (true)
{
Action task = null;
lock (taskQueue)
{
if (taskQueue.Count > 0)
{
task = taskQueue.Dequeue();
if (task == null)
{
Dispose();
return;
}
information = taskInformationQueue.Dequeue();
}
}
if (task != null)
{
//Perform the task!
try
{
task(information);
}
catch (ArithmeticException arithmeticException)
{
throw new ArithmeticException(
"Some internal mulsathreaded arithmetic has encountered an invalid state. Check for invalid ensaty momentums, velocities, and positions; propagating NaN's will generally trigger this exception in the getExtremePoint function.",
arithmeticException);
}
if (Interlocked.Decrement(ref manager.tasksRemaining) == 0)
{
manager.allThreadsIdleNotifier.Set();
resetEvent.WaitOne();
}
}
else
resetEvent.WaitOne();
}
}
}
}
}