#region CPL License /* Nuclex Framework Copyright (C) 2002-2009 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using Microsoft.Xna.Framework; using Nuclex.Support; namespace Nuclex.Graphics.SpecialEffects.Particles { partial class ParticleSystem { #region class AsyncPrune /// Prunes dead particles in the particle system asynchronously private class AsyncPrune : IAsyncResult, IDisposable { /// Initializes a new asynchronous prune process /// Particle system that will be pruned public AsyncPrune(ParticleSystem particleSystem) { this.particleSystem = particleSystem; this.runDelegate = new WaitCallback(run); } /// Immediately releases all resources owned by the instance public void Dispose() { if(this.doneEvent != null) { this.doneEvent.Close(); this.doneEvent = null; } } /// /// Resets the asynchronous prune process for another use /// /// Method deciding which particles to prune /// /// Callback that will be invoked when pruning has finished /// /// User-defined state from the BeginPrune() method public void Start( PrunePredicate pruneDelegate, AsyncCallback callback, object state ) { this.running = true; lock(this) { this.completed = false; if(this.doneEvent != null) { this.doneEvent.Reset(); } } this.exception = null; this.pruneDelegate = pruneDelegate; this.callback = callback; this.state = state; ThreadPool.QueueUserWorkItem(this.runDelegate); } /// User defined state from the BeginPrune() method public object AsyncState { get { return this.state; } } /// Exception that occured during asynchronous processing public Exception AsyncException { get { return this.exception; } } /// /// Wait handle that can be used to wait until pruning is finished /// public WaitHandle AsyncWaitHandle { get { if(this.doneEvent == null) { lock(this) { if(this.doneEvent == null) { this.doneEvent = new ManualResetEvent(this.completed); } } } return this.doneEvent; } } /// Whether the pruning process has finished synchronously public bool CompletedSynchronously { get { return false; } } /// True if pruning has finished public bool IsCompleted { get { return this.completed; } } /// Whether the pruning process is currently running public bool IsRunning { get { return this.running; } } /// Executes the asynchronous pruning /// Not used private void run(object state) { try { this.particleSystem.Prune(this.pruneDelegate); } catch(Exception exception) { this.exception = exception; } this.running = false; AsyncCallback callbackCopy; lock(this) { callbackCopy = this.callback; this.completed = true; if(this.doneEvent != null) { this.doneEvent.Set(); } } if(callbackCopy != null) { callbackCopy(this); } } /// Particle system being pruned private ParticleSystem particleSystem; /// Delegate for a method that decides which particles are pruned private PrunePredicate pruneDelegate; /// Callback that will be invoked when pruning has finished private AsyncCallback callback; /// Used-defined state from the BeginPrune() method private object state; /// Delegate to the run() method which performs the pruning private WaitCallback runDelegate; /// Wait handle that can be used to wait for pruning to finish private volatile ManualResetEvent doneEvent; /// Whether the pruning process is finished private volatile bool completed; /// Whether the pruning process is running private volatile bool running; /// Exception that occured during asynchronous processing private volatile Exception exception; } #endregion // class AsyncPrune #region class AsyncUpdate /// Updates the particle system asynchronously private class AsyncUpdate : IAsyncResult, IDisposable { #region class ThreadStartInfo /// Start informations for an update thread private class ThreadStartInfo { /// Index of the first particle that will be updated public int Start; /// Number of particles that will be updated public int Count; } #endregion // class ThreadStartInfo /// Initializes a new asynchronous update process /// Particle system that will be updated public AsyncUpdate(ParticleSystem particleSystem) { this.particleSystem = particleSystem; this.threadStartInfos = new List(); this.runDelegate = new WaitCallback(run); } /// Immediately releases all resources owned by the instance public void Dispose() { if(this.doneEvent != null) { this.doneEvent.Close(); this.doneEvent = null; } } /// /// Resets the asynchronous update process for another use /// /// Number of updates that will be performed /// Number of threads to use for the updates /// /// Callback that will be invoked when pruning has finished /// /// User-defined state from the BeginUpdate() method public void Start(int updates, int threads, AsyncCallback callback, object state) { this.running = true; Interlocked.Exchange(ref this.completedUpdates, 0); this.completed = false; if(this.doneEvent != null) { this.doneEvent.Reset(); } this.exception = null; this.updates = updates; this.threads = threads; this.callback = callback; this.state = state; if(threads == 0) { run(null); } else { // Make sure enough thread start infos are available while(this.threadStartInfos.Count < threads) { this.threadStartInfos.Add(new ThreadStartInfo()); } int lastThread = threads - 1; // Create all threads except for the last one (because the particle count // might not be evenly dividable by the number of threads) int particlesPerThread = this.particleSystem.particleCount / threads; for(int thread = 0; thread < lastThread; ++thread) { this.threadStartInfos[thread].Start = particlesPerThread * thread; this.threadStartInfos[thread].Count = particlesPerThread; runThreaded(this.runDelegate, this.threadStartInfos[thread]); } // Let the final thread process all remaining particles. For low numbers of // particles, the workload might be distributed unevenly (eg. 19 particles // on 10 threads = 10th thread does 9 particles), but with the huge numbers // of particles typically simulated, it's not relevant whether the 10th thread // does 5009 particles instead of 5000 like the others) int finalStart = particlesPerThread * lastThread; int finalCount = this.particleSystem.particleCount - finalStart; this.threadStartInfos[lastThread].Start = finalStart; this.threadStartInfos[lastThread].Count = finalCount; runThreaded(this.runDelegate, this.threadStartInfos[lastThread]); } } /// User defined state from the BeginUpdate() method public object AsyncState { get { return this.state; } } /// Exception that occured during asynchronous processing public Exception AsyncException { get { return this.exception; } } /// /// Wait handle that can be used to wait until updating is finished /// public System.Threading.WaitHandle AsyncWaitHandle { get { if(this.doneEvent == null) { lock(this) { if(this.doneEvent == null) { this.doneEvent = new ManualResetEvent(this.completed); } } } return this.doneEvent; } } /// Whether the update process has finished synchronously public bool CompletedSynchronously { get { return false; } } /// True if updating has finished public bool IsCompleted { get { return this.completed; } } /// Whether the update process is currently running public bool IsRunning { get { return this.running; } } /// Executes the asynchronous pruning /// Not used private void run(object state) { if(state != null) { ThreadStartInfo startInfo = state as ThreadStartInfo; try { this.particleSystem.update(this.updates, startInfo.Start, startInfo.Count); } catch(Exception exception) { this.exception = exception; } } // If we're the final thread coming around, set the async result to finished int completedUpdates = Interlocked.Increment(ref this.completedUpdates); if(completedUpdates >= this.threads) { this.running = false; AsyncCallback callbackCopy; lock(this) { callbackCopy = this.callback; this.completed = true; if(this.doneEvent != null) { this.doneEvent.Set(); } } if(callbackCopy != null) { callbackCopy(this); } } } /// Runs the provided wait callback in a separate thread /// Wait callback that will be run in a thread /// /// User defined state that will be passed on to the wait callback /// private static void runThreaded(WaitCallback waitCallback, object state) { AffineThreadPool.QueueUserWorkItem(waitCallback, state); } /// Particle system being pruned private ParticleSystem particleSystem; /// Number of updates that have been completed private int completedUpdates; /// Number of updates that will be performed private int updates; /// Number of threads being used to perform the update private int threads; /// Callback that will be invoked when pruning has finished private AsyncCallback callback; /// Used-defined state from the BeginPrune() method private object state; /// Delegate to the run() method which performs the pruning private WaitCallback runDelegate; /// Wait handle that can be used to wait for pruning to finish private volatile ManualResetEvent doneEvent; /// Whether the pruning process is finished private volatile bool completed; /// Whether the pruning process is running private volatile bool running; /// Start information holders for the threads private List threadStartInfos; /// Exception that occured during asynchronous processing private volatile Exception exception; } #endregion // class AsyncUpdate } } // namespace Nuclex.Graphics.SpecialEffects.Particles