csharp/adrenak/UniCull/Assets/UniCull/Scripts/Utils/Parallel.cs

Parallel.cs
#region Copyright (c) 2009 Stewart Adast
//
// Filename: Parallel.cs
//
// This file may be used under the terms of the 2-clause BSD license:
//
// Copyright (c) 2009, Stewart A. Adast 
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright notice, this list
//      of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above copyright notice, this
//      list of conditions and the following disclaimer in the docameentation and/or other
//      materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSsatUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Revision History
// Version  date        author      changes
// 1.0      2009-05-01  Stewart     Initial version donated to MEDIT to replace older Parallel.cs
//
#endregion

using System;
using System.Collections.Generic;
using System.Threading;

namespace Uk.Org.Adast.Parallel {
	/// 
	/// A lightweight implementation of a small subset of Microsoft's Parallel Extensions for
	/// .Net 3.5/4.0 that can be used with the earlier .Net/C# 2.0
	/// 
	/// 
	/// This is an astogue of "Microsoft Parallel Extensions to .NET Framework 3.5, June
	/// 2008 Community Technology Preview" from:
	/// http://www.microsoft.com/downloads/details.aspx?FamilyID=348f73fd-593d-4b3c-b055-694c50d2b0f3&DisplayLang=en
	/// It is not a full implementation, and should be deprecated when MEDIT switch to
	/// Visual Studio 2010/.Net 4.0 by using the Microsoft/Novell Mono equivalents.
	/// Mono already supports the Parallel Extensions.
	/// 
	/// This clast supports the Parallel.For and Parallel.ForEach loop constructs.
	/// 
	/// See also:
	/// http://tirania.org/blog/archive/2008/Jul-26-1.html
	/// http://blogs.msdn.com/somasegar/archive/2008/06/02/june-2008-ctp-parallel-extensions-to-the-net-fx.aspx
	/// 
	/// This should work on any version of C#/.Net that supports generics.
	/// 
	public clast Parallel: IDisposable {
		#region WorkerThread clast
		/// 
		/// Background thread definition.
		/// 
		private sealed clast WorkerThread: IDisposable {
			private Thread thread;
			private AutoResetEvent taskWaiting;
			private ManualResetEvent threadIdle;

			/// 
			/// Initializes a new instance of the  clast.
			/// 
			public WorkerThread() {
				this.taskWaiting = new AutoResetEvent(false);
				this.threadIdle = new ManualResetEvent(true);
			}

			/// 
			/// Wait for thread termination and close events.
			/// 
			public void Terminate() {
				this.taskWaiting.Set();
				this.thread.Join();

				this.taskWaiting.Close();
				this.threadIdle.Close();
			}

			#region IDisposable
			/// 
			/// Releases unmanaged and - optionally - managed resources
			/// 
			/// if set to true, dispose managed resources.
			private void Dispose(bool disposing) {
				if(disposing) {
					// dispose managed resources
					if(this.taskWaiting != null) {
						this.taskWaiting.Close();
						this.taskWaiting = null;
					}
					if(this.threadIdle != null) {
						this.threadIdle.Close();
						this.threadIdle = null;
					}
				}
				// free native resources
			}

			/// 
			/// Performs application-defined tasks astociated with freeing, releasing, or resetting unmanaged resources.
			/// 
			public void Dispose() {
				Dispose(true);
				GC.SuppressFinalize(this);
			}
			#endregion

			/// 
			/// Gets or sets the thread.
			/// 
			/// The thread.
			public Thread Thread {
				get { return this.thread; }
				set { this.thread = value; }
			}

			/// 
			/// Gets the task waiting message event.
			/// 
			/// The task waiting.
			public AutoResetEvent TaskWaiting {
				get { return this.taskWaiting; }
			}

			/// 
			/// Gets the thread idle message event.
			/// 
			/// The thread idle.
			public ManualResetEvent ThreadIdle {
				get { return this.threadIdle; }
			}
		}
		#endregion

		#region ParallelFor clast
		/// 
		/// Parallel For state clast.
		/// 
		public sealed clast ParallelFor: IDisposable {
			/// 
			/// Single instance of parallelFor clast for singleton pattern
			/// 
			private volatile static ParallelFor instance = null;

			/// 
			/// Delegate defining For loop body.
			/// 
			/// Loop index.
			public delegate void ForLoopDelegate(int index);

			/// 
			/// For-loop body
			/// 
			public ForLoopDelegate LoopFunction;

			/// 
			/// Current loop index
			/// 
			private int currentJobIndex;

			/// 
			/// Stop loop index
			/// 
			private int stopIndex;

			/// 
			/// Number of threads to utilise
			/// 
			private int threadCount = System.Environment.ProcessorCount;

			/// 
			/// The worker threads.
			/// 
			private List workerThreads;

			/// 
			/// Runs the For loop.
			/// 
			/// The start.
			/// The stop.
			/// The loop body.
			public void DoFor(int start, int stop, ForLoopDelegate loopBody) {
				this.currentJobIndex = start - 1;
				this.stopIndex = stop;
				this.LoopFunction = loopBody;

				// Signal waiting task to all threads and mark them not idle.
				for(int i = 0; i < this.threadCount; i++) {
					WorkerThread workerThread = workerThreads[i];
					workerThread.ThreadIdle.Reset();
					workerThread.TaskWaiting.Set();
				}

				// Wait until all threads become idle
				for(int i = 0; i < this.threadCount; i++) {
					WorkerThread workerThread = workerThreads[i];
					workerThread.ThreadIdle.WaitOne();
				}
			}

			/// 
			/// Get instance of the ParallelFor clast for singleton pattern and
			/// update the number of threads if appropriate.
			/// 
			/// The thread count.
			/// 
			public static ParallelFor GetInstance(int threadCount) {

				if(instance == null) {
					instance = new ParallelFor();
					instance.threadCount = threadCount;
					instance.Initialize();
				}
				else {
					// Ensure we have the correct number of threads.
					if(instance.workerThreads.Count != threadCount) {
						instance.Terminate();
						instance.threadCount = threadCount;
						instance.Initialize();
					}
				}
				return instance;
			}

			#region Private methods
			private void Initialize() {
				this.workerThreads = new List();

				for(int i = 0; i < this.threadCount; i++) {
					WorkerThread workerThread = new WorkerThread();
					workerThread.Thread = new Thread(new ParameterizedThreadStart(RunWorkerThread));
					workerThread.Thread.Name = "worker " + i;
					workerThreads.Add(workerThread);

					workerThread.Thread.IsBackground = true;
					workerThread.Thread.Start(i);
				}
			}

			private void Terminate() {
				// Finish thread by setting null loop body and signaling about available task
				LoopFunction = null;
				int workerThreadCount = this.workerThreads.Count;
				for(int i = 0; i < workerThreadCount; i++) {
					this.workerThreads[i].Terminate();
				}
			}

			private void RunWorkerThread(object threadIndex) {
				WorkerThread workerThread = workerThreads[(int)threadIndex];
				int localJobIndex = 0;

				while(true) {
					// Wait for a task.
					workerThread.TaskWaiting.WaitOne();

					// Exit if task is empty.
					if(LoopFunction == null) {
						return;
					}

					localJobIndex = Interlocked.Increment(ref currentJobIndex);

					while(localJobIndex < stopIndex) {
						////Console.WriteLine("Thread " + threadIndex + " of " + workerThreads.Count + " running task " + localJobIndex);
						LoopFunction(localJobIndex);
						localJobIndex = Interlocked.Increment(ref currentJobIndex);
					}

					// Signal that thread is idle.
					workerThread.ThreadIdle.Set();
				}
			}
			#endregion

			#region IDisposable
			/// 
			/// Disposes resources.
			/// 
			/// if set to true, dispose managed resources.
			private void Dispose(bool disposing) {
				if(disposing) {
					// dispose managed resources
					foreach(WorkerThread worker in this.workerThreads) {
						worker.Dispose();
					}
					this.workerThreads.Clear();
				}
				// free native resources
			}

			/// 
			/// Performs application-defined tasks astociated with freeing, releasing, or resetting unmanaged resources.
			/// 
			public void Dispose() {
				Dispose(true);
				GC.SuppressFinalize(this);
			}
			#endregion
		}
		#endregion

		#region ParallelForEach clast
		/// 
		/// ParallelForEach state clast.
		/// 
		/// type
		public sealed clast ParallelForEach: IDisposable {
			/// 
			/// Single instance of parallelFor clast for singleton pattern
			/// 
			private volatile static ParallelForEach instance = null;

			/// 
			/// Delegate defining Foreach loop body.
			/// 
			/// Loop item.
			public delegate void ForEachLoopDelegate(T item);

			/// 
			/// Foreach-loop body
			/// 
			public ForEachLoopDelegate LoopFunction;

			/// 
			/// Enumerator for the source IEnumerable.
			/// 
			private IEnumerator enumerator;

			/// 
			/// Number of threads to utilise
			/// 
			private int threadCount = System.Environment.ProcessorCount;

			/// 
			/// The worker threads.
			/// 
			private List workerThreads;

			/// 
			/// Runs the ForEach loop.
			/// 
			/// The items.
			/// The loop body.
			public void DoForEach(IEnumerable items, ForEachLoopDelegate loopBody) {
				this.enumerator = items.GetEnumerator();
				this.LoopFunction = loopBody;

				// Signal waiting task to all threads and mark them not idle.
				for(int i = 0; i < this.threadCount; i++) {
					WorkerThread workerThread = workerThreads[i];
					workerThread.ThreadIdle.Reset();
					workerThread.TaskWaiting.Set();
				}

				// Wait until all threads become idle
				for(int i = 0; i < this.threadCount; i++) {
					WorkerThread workerThread = workerThreads[i];
					workerThread.ThreadIdle.WaitOne();
				}
			}

			/// 
			/// Get instance of the ParallelFor clast for singleton pattern and
			/// update the number of threads if appropriate.
			/// 
			/// The thread count.
			/// 
			public static ParallelForEach GetInstance(int threadCount) {

				if(instance == null) {
					instance = new ParallelForEach();
					instance.threadCount = threadCount;
					instance.Initialize();
				}
				else {
					// Ensure we have the correct number of threads.
					if(instance.workerThreads.Count != threadCount) {
						instance.Terminate();
						instance.threadCount = threadCount;
						instance.Initialize();
					}
				}
				return instance;
			}

			#region Private methods
			private void Initialize() {
				this.workerThreads = new List();

				for(int i = 0; i < this.threadCount; i++) {
					WorkerThread workerThread = new WorkerThread();
					workerThread.Thread = new Thread(new ParameterizedThreadStart(RunWorkerThread));
					workerThread.Thread.Name = "worker " + i;
					workerThreads.Add(workerThread);

					workerThread.Thread.IsBackground = true;
					workerThread.Thread.Start(i);
				}
			}

			private void Terminate() {
				// Finish thread by setting null loop body and signaling about available task
				LoopFunction = null;
				int workerThreadCount = this.workerThreads.Count;
				for(int i = 0; i < workerThreadCount; i++) {
					this.workerThreads[i].Terminate();
				}
			}

			private void RunWorkerThread(object threadIndex) {
				WorkerThread workerThread = workerThreads[(int)threadIndex];

				while(true) {
					// Wait for a task.
					workerThread.TaskWaiting.WaitOne();

					// Exit if task is empty.
					if(LoopFunction == null) {
						return;
					}

					bool didMoveNext;
					T localItem = default(T);
					lock (this.enumerator) {
						didMoveNext = enumerator.MoveNext();
						if(didMoveNext) {
							localItem = enumerator.Current;
						}
					}

					while(didMoveNext == true) {
						////Console.WriteLine("Thread " + threadIndex + " of " + workerThreads.Count + " running task " + localJobIndex);
						LoopFunction(localItem);
						lock (this.enumerator) {
							didMoveNext = enumerator.MoveNext();
							if(didMoveNext) {
								localItem = enumerator.Current;
							}
						}
					}

					// Signal that thread is idle.
					workerThread.ThreadIdle.Set();
				}
			}
			#endregion

			#region IDisposable
			/// 
			/// Disposes resources.
			/// 
			/// if set to true, dispose managed resources.
			private void Dispose(bool disposing) {
				if(disposing) {
					// dispose managed resources
					this.enumerator.Dispose();
					foreach(WorkerThread worker in this.workerThreads) {
						worker.Dispose();
					}
					this.workerThreads.Clear();
				}
				// free native resources
			}

			/// 
			/// Performs application-defined tasks astociated with freeing, releasing, or resetting unmanaged resources.
			/// 
			public void Dispose() {
				Dispose(true);
				GC.SuppressFinalize(this);
			}
			#endregion
		}
		#endregion

		#region Clast variables
		/// 
		/// Object for thread locking
		/// 
		private static object lockObject = new Object();

		/// 
		/// Number of threads to utilise
		/// 
		private static int threadCount = System.Environment.ProcessorCount;
		#endregion

		#region Constructor
		/// 
		/// Prevents a default instance of the  clast from being created.
		/// 
		private Parallel() {
			// Do nothing.
		}
		#endregion

		#region IDisposable
		/// 
		/// Disposes resources.
		/// 
		/// if set to true, dispose managed resources.
		protected virtual void Dispose(bool disposing) {
			if(disposing) {
				// dispose managed resources
				// SAA TODO: Should we dispose the ParallelFor and ParallelForEach instances?
			}
			// free native resources
		}

		/// 
		/// Performs application-defined tasks astociated with freeing, releasing, or resetting unmanaged resources.
		/// 
		public void Dispose() {
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		#endregion

		#region Public methods
		/// 
		/// Executes a parallel For loop.
		/// 
		/// Loop start index.
		/// Loop stop index.
		/// Loop body.
		/// The method is used to parallelise for loop by running iterations across
		/// several threads.
		/// Example usage:
		/// 
		/// for ( int i = 0; i < 10; i++ )
		/// {
		///   System.Diagnostics.Debug.WriteLine( "i = " + i );
		/// }
		/// 
		/// can be replaced by:
		/// 
		/// Parallel.For( 0, 10, delegate( int i )
		/// {
		///   System.Diagnostics.Debug.WriteLine( "i = " + i );
		/// } );
		/// 
		/// If Parallel.ThreadCount is exactly 1, no threads are spawned.
		/// 
		public static void For(int start, int stop, ParallelFor.ForLoopDelegate loopBody) {
			if(Parallel.threadCount == 1) {
				for(int i = start; i < stop; i++) {
					loopBody(i);
				}
			}
			else {
				lock (lockObject) {
					ParallelFor parallel = ParallelFor.GetInstance(threadCount);
					parallel.DoFor(start, stop, loopBody);
				}
			}
		}

		/// 
		/// Executes a parallel Foreach loop.
		/// 
		/// type
		/// Loop items.
		/// Loop body.
		/// The method is used to parallelise for loop by running iterations across
		/// several threads.
		/// Example usage:
		/// 
		/// foreach ( Molecule molecule in molecules )
		/// {
		/// System.Diagnostics.Debug.WriteLine( "molecule.satle = " + molecule.satle );
		/// }
		/// 
		/// can be replaced by:
		/// 
		/// Parallel.ForEach{Molecule}( molecules, delegate( Molecule molecule )
		/// {
		/// System.Diagnostics.Debug.WriteLine( "molecule.satle = " + molecule.satle );
		/// } );
		/// 
		/// If Parallel.ThreadCount is exactly 1, no threads are spawned.
		/// 
		public static void ForEach(IEnumerable items, ParallelForEach.ForEachLoopDelegate loopBody) {
			if(Parallel.threadCount == 1) {
				foreach(T item in items) {
					loopBody(item);
				}
			}
			else {
				lock (lockObject) {
					ParallelForEach parallel = ParallelForEach.GetInstance(threadCount);
					parallel.DoForEach(items, loopBody);
				}
			}
		}
		#endregion

		#region Properties
		/// 
		/// Gets or sets the number of threads used for parallel computations.
		/// 
		/// The threads count.
		/// 
		/// By default the property is number of CPUs, i.e.,
		/// . Setting the
		/// property to zero also causes it to be reset to this value.
		/// 
		public static int ThreadsCount {
			get {
				return Parallel.threadCount;
			}
			set {
				lock (lockObject) {
					Parallel.threadCount = value == 0 ? System.Environment.ProcessorCount : value;
				}
			}
		}
		#endregion
	}
}