using System; using System.Collections.Generic; using System.Collections.ObjectModel; using Nuclex.Support.Collections; namespace Nuclex.Support.Tracking { /// Forms a single progression from a set of progressions /// Type of progressions to manage as a set public class SetProgression : Progression, IDisposable where ProgressionType : Progression { /// Performs common initialization for the public constructors private SetProgression() { this.childs = new List>(); } /// Initializes a new set progression /// Progressions to track with this set /// /// Uses a default weighting factor of 1.0 for all progressions. /// public SetProgression(IEnumerable childs) : this() { // Construct a WeightedProgression with the default weight for each // progression and wrap it in an ObservedProgression foreach(ProgressionType progression in childs) { this.childs.Add( new ObservedProgression( new WeightedProgression(progression), new ObservedProgression.ReportDelegate(asyncProgressUpdated), new ObservedProgression.ReportDelegate(asyncEnded) ) ); } // Since all progressions have a weight of 1.0, the total weight is // equal to the number of progressions in our list this.totalWeight = (float)this.childs.Count; } /// Initializes a new set progression /// Progressions to track with this set public SetProgression( IEnumerable> childs ) : this() { // Construct an ObservedProgression around each of the WeightedProgressions float totalWeight = 0.0f; foreach(WeightedProgression progression in childs) { this.childs.Add( new ObservedProgression( progression, new ObservedProgression.ReportDelegate(asyncProgressUpdated), new ObservedProgression.ReportDelegate(asyncEnded) ) ); // Sum up the total weight totalWeight += progression.Weight; } // Take over the summed weight of all progressions we were given this.totalWeight = totalWeight; } /// Immediately releases all resources owned by the object public void Dispose() { if(this.childs != null) { // Dispose all the observed progressions, disconnecting the events from the // actual progressions so the GC can more easily collect this class for(int index = 0; index < this.childs.Count; ++index) this.childs[index].Dispose(); this.childs = null; this.wrapper = null; } } /// Childs contained in the progression set public IList> Childs { get { // The wrapper is constructed only when needed. Most of the time, users will // just create a SetProgression and monitor its progress without ever using // the Childs collection. if(this.wrapper == null) { // This doesn't need a lock because it's only a stateless wrapper. If it // is constructed twice, then so be it. this.wrapper = new WeightedProgressionWrapperCollection( this.childs ); } return this.wrapper; } } /// /// Called when the progress of one of the observed progressions changes /// private void asyncProgressUpdated() { // Calculate the sum of the progress reported by our child progressions, // scaled to the weight each progression has assigned to it. float totalProgress = 0.0f; for(int index = 0; index < this.childs.Count; ++index) { totalProgress += this.childs[index].Progress * this.childs[index].WeightedProgression.Weight; } // Calculate the actual combined progress if(this.totalWeight > 0.0f) totalProgress /= this.totalWeight; // Send out the progress update OnAsyncProgressUpdated(totalProgress); } /// /// Called when an observed progressions ends /// private void asyncEnded() { // If there's still at least one progression going, don't report that // the SetProgression has finished yet. for(int index = 0; index < this.childs.Count; ++index) if(!this.childs[index].WeightedProgression.Progression.Ended) return; // All child progressions have ended, so the set has now ended as well OnAsyncEnded(); } /// Progressions being managed in the set private List> childs; /// /// Wrapper collection for exposing the child progressions under the /// WeightedProgression interface /// private volatile WeightedProgressionWrapperCollection wrapper; /// Summed weight of all progression in the set private float totalWeight; } } // namespace Nuclex.Support.Tracking