diff --git a/Nuclex.Support (PC).csproj b/Nuclex.Support (PC).csproj index bb3c9fa..9823ba5 100644 --- a/Nuclex.Support (PC).csproj +++ b/Nuclex.Support (PC).csproj @@ -166,6 +166,18 @@ false AbortedException + + false + QueueOperation + + + false + ThreadOperation + + + false + WeightedRequirableOperation + false BinarySerializer.Test @@ -203,9 +215,13 @@ false SpatialIndex2 - + false - ObservedProgression + IdleStateEventArgs + + + false + ObservedWeightedProgression false @@ -227,9 +243,9 @@ false ProgressUpdateEventArgs - + false - ThreadedMethodOperation + ThreadCallbackOperation false diff --git a/Source/Licensing/LicenseKey.cs b/Source/Licensing/LicenseKey.cs index 4c49ea5..dcbe5f6 100644 --- a/Source/Licensing/LicenseKey.cs +++ b/Source/Licensing/LicenseKey.cs @@ -212,7 +212,7 @@ namespace Nuclex.Support.Licensing { private static readonly string codeTable = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - /// Helper array with the powers of two + /// Helper array containing the precalculated powers of two private static readonly uint[,] powersOfTwo = new uint[32, 2] { { 0, 1 }, { 0, 2 }, { 0, 4 }, { 0, 8 }, { 0, 16 }, { 0, 32 }, { 0, 64 }, { 0, 128 }, diff --git a/Source/Scheduling/Operation.cs b/Source/Scheduling/Operation.cs index ce66dac..4c0e66b 100644 --- a/Source/Scheduling/Operation.cs +++ b/Source/Scheduling/Operation.cs @@ -27,12 +27,6 @@ namespace Nuclex.Support.Scheduling { /// Base class for observable operations running in the background public abstract class Operation : Progression { - /// Executes the operation synchronously - public virtual void Execute() { - Begin(); - End(); - } - /// Launches the background operation public abstract void Begin(); @@ -55,13 +49,15 @@ namespace Nuclex.Support.Scheduling { } else { error = true; } - } + } // lock } else { error = true; } - + if(error) + throw new InvalidOperationException("End() has already been called"); + // If the progression itself hasn't ended yet, block the caller until it has. - if(!Ended) + if(!Ended) WaitHandle.WaitOne(); // If an exception occured during the background execution @@ -75,7 +71,25 @@ namespace Nuclex.Support.Scheduling { /// If this field is null, it is assumed that no exception has occured /// in the background process. When it is set, the End() method will /// - protected Exception occuredException; + public Exception OccuredException { + get { return this.occuredException; } + } + + /// Sets the exception to raise to the caller of the End() method + /// Exception to raise to the caller of the End() method + protected void SetException(Exception exception) { + + // We allow the caller to set the exception multiple times. While I certainly + // can't think of a scenario where this would happen, throwing an exception + // in that case seems worse. The caller might just be executing an exception + // handling block and locking the operation instance could cause all even + // more problems. + this.occuredException = exception; + + } + + /// Exception that occured while the operation was executing + private volatile Exception occuredException; /// Whether the End() method has been called already private volatile bool endCalled; diff --git a/Source/Scheduling/QueueOperation.cs b/Source/Scheduling/QueueOperation.cs new file mode 100644 index 0000000..0922f14 --- /dev/null +++ b/Source/Scheduling/QueueOperation.cs @@ -0,0 +1,68 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2007 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 Nuclex.Support.Tracking; + +namespace Nuclex.Support.Scheduling { + + /// Operation that sequentially executes a series of operations + /// + /// Type of the child operations the QueueOperation will contain + /// + public class QueueOperation : Operation + where OperationType : Operation { + + /// Initializes a new queue operation + /// Child operations to execute in this operation + /// + /// All child operations will have a default weight of 1.0 + /// + public QueueOperation(IEnumerable childs) { + this.setProgression = new SetProgression(childs); + } + + /// Initializes a new queue operation with custom weights + /// Child operations to execute in this operation + public QueueOperation(IEnumerable> childs) { + this.setProgression = new SetProgression(childs); + } + + /// Launches the background operation + public override void Begin() { + this.setProgression.AsyncProgressUpdated += + new EventHandler(setProgressionProgressUpdated); + + + + //this.setProgression.Childs[0].Progression.Begin(); + } + + private void setProgressionProgressUpdated(object sender, ProgressUpdateEventArgs e) { + throw new Exception("The method or operation is not implemented."); + } + + /// SetProgression used internally to handle progress reports + private volatile SetProgression setProgression; + + } + +} // namespace Nuclex.Support.Scheduling diff --git a/Source/Scheduling/ThreadedMethodOperation.cs b/Source/Scheduling/ThreadCallbackOperation.cs similarity index 64% rename from Source/Scheduling/ThreadedMethodOperation.cs rename to Source/Scheduling/ThreadCallbackOperation.cs index 8f1466d..32c259b 100644 --- a/Source/Scheduling/ThreadedMethodOperation.cs +++ b/Source/Scheduling/ThreadCallbackOperation.cs @@ -24,7 +24,7 @@ using System.Threading; namespace Nuclex.Support.Scheduling { /// Operation that executes a method in a background thread - public class ThreadedMethodOperation : Operation { + public class ThreadCallbackOperation : ThreadOperation { /// /// Initializes a new threaded method operation for a parameterless method @@ -33,7 +33,7 @@ namespace Nuclex.Support.Scheduling { /// /// Uses a ThreadPool thread to execute the method in /// - public ThreadedMethodOperation(ThreadStart method) + public ThreadCallbackOperation(ThreadStart method) : this(method, true) { } /// @@ -46,35 +46,20 @@ namespace Nuclex.Support.Scheduling { /// that the method will be executed immediately but has an impact on /// performance since the creation of new threads is not a cheap operation. /// - public ThreadedMethodOperation(ThreadStart method, bool useThreadPool) { - if(useThreadPool) { - ThreadPool.QueueUserWorkItem(callMethod, method); - } else { - Thread thread = new Thread(callMethod); - thread.Name = "Nuclex.Support.Scheduling.ThreadedMethodOperation thread"; - thread.IsBackground = true; - thread.Start(method); - } + public ThreadCallbackOperation(ThreadStart method, bool useThreadPool) + : base(useThreadPool) { + + this.method = method; } - /// Invokes the delegate passed as an argument - /// ThreadStart-comaptible Delegate to invoke - private void callMethod(object method) { -#if PROGRESSION_STARTABLE - AsyncStarted(); -#endif - - try { - ((ThreadStart)method)(); - } - catch(Exception exception) { - this.occuredException = exception; - } - finally { - AsyncEnded(); - } + /// Executes the thread callback in the background thread + protected override void Execute() { + this.method(); } + /// Method to be invoked in a background thread + private ThreadStart method; + } } // namespace Nuclex.Support.Scheduling diff --git a/Source/Scheduling/ThreadOperation.cs b/Source/Scheduling/ThreadOperation.cs new file mode 100644 index 0000000..7588307 --- /dev/null +++ b/Source/Scheduling/ThreadOperation.cs @@ -0,0 +1,88 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2007 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.Threading; + +namespace Nuclex.Support.Scheduling { + + /// Operation that executes a method in a background thread + public abstract class ThreadOperation : Operation { + + /// + /// Initializes a new threaded operation + /// + /// + /// Uses a ThreadPool thread to execute the method in + /// + public ThreadOperation() : this(true) { } + + /// + /// Initializes a new threaded operation + /// + /// Whether tos use a ThreadPool thread + /// + /// If useThreadPool is false, a new thread will be created. This guarantees + /// that the method will be executed immediately but has an impact on + /// performance since the creation of new threads is not a cheap operation. + /// + public ThreadOperation(bool useThreadPool) { + this.useThreadPool = useThreadPool; + } + + /// Launches the background operation + public override void Begin() { + if(useThreadPool) { + ThreadPool.QueueUserWorkItem(callMethod); + } else { + Thread thread = new Thread(callMethod); + thread.Name = "Nuclex.Support.Scheduling.ThreadOperation"; + thread.IsBackground = true; + thread.Start(); + } + } + + /// Contains the payload to be executed in the background thread + protected abstract void Execute(); + + /// Invokes the delegate passed as an argument + /// Not used + private void callMethod(object state) { +#if PROGRESSION_STARTABLE + OnAsyncStarted(); +#endif + + try { + Execute(); + } + catch(Exception exception) { + SetException(exception); + } + finally { + OnAsyncEnded(); + } + } + + /// Whether to use the ThreadPool for obtaining a background thread + private bool useThreadPool; + + } + +} // namespace Nuclex.Support.Scheduling diff --git a/Source/Tracking/IdleStateEventArgs.cs b/Source/Tracking/IdleStateEventArgs.cs new file mode 100644 index 0000000..dc80fdd --- /dev/null +++ b/Source/Tracking/IdleStateEventArgs.cs @@ -0,0 +1,45 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2007 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.Text; + +namespace Nuclex.Support.Tracking { + + /// Event arguments for an idle state change notification + public class IdleStateEventArgs : EventArgs { + + /// Initializes the idle state change notification + /// The new idle state + public IdleStateEventArgs(bool idle) { + this.idle = idle; + } + + /// Current idle state + public bool Idle { + get { return this.idle; } + } + + /// Current idle state + private bool idle; + + } + +} // namespace Nuclex.Support.Tracking diff --git a/Source/Tracking/Internal/ObservedProgression.cs b/Source/Tracking/Internal/ObservedWeightedProgression.cs similarity index 95% rename from Source/Tracking/Internal/ObservedProgression.cs rename to Source/Tracking/Internal/ObservedWeightedProgression.cs index c83b9cc..750df02 100644 --- a/Source/Tracking/Internal/ObservedProgression.cs +++ b/Source/Tracking/Internal/ObservedWeightedProgression.cs @@ -27,7 +27,7 @@ namespace Nuclex.Support.Tracking { /// /// Type of the progression that is being observed /// - internal class ObservedProgression : IDisposable + internal class ObservedWeightedProgression : IDisposable where ProgressionType : Progression { /// Delegate for reporting progress updates @@ -41,7 +41,7 @@ namespace Nuclex.Support.Tracking { /// /// Callback to invoke when the progression has ended /// - internal ObservedProgression( + internal ObservedWeightedProgression( WeightedProgression weightedProgression, ReportDelegate progressUpdateCallback, ReportDelegate endedCallback diff --git a/Source/Tracking/Internal/WeightedProgressionWrapperCollection.cs b/Source/Tracking/Internal/WeightedProgressionWrapperCollection.cs index b77c762..dc556d5 100644 --- a/Source/Tracking/Internal/WeightedProgressionWrapperCollection.cs +++ b/Source/Tracking/Internal/WeightedProgressionWrapperCollection.cs @@ -48,14 +48,14 @@ namespace Nuclex.Support.Tracking { /// internal class WeightedProgressionWrapperCollection : TransformingReadOnlyCollection< - ObservedProgression, WeightedProgression + ObservedWeightedProgression, WeightedProgression > where ProgressionType : Progression { /// Initializes a new weighted progression collection wrapper /// Items to be exposed as weighted progressions internal WeightedProgressionWrapperCollection( - IList> items + IList> items ) : base(items) { } @@ -69,7 +69,7 @@ namespace Nuclex.Support.Tracking { /// not cache otherwise store the transformed items. /// protected override WeightedProgression Transform( - ObservedProgression item + ObservedWeightedProgression item ) { return item.WeightedProgression; } diff --git a/Source/Tracking/ProgressionTracker.cs b/Source/Tracking/ProgressionTracker.cs index 8afe2e4..24eb3ed 100644 --- a/Source/Tracking/ProgressionTracker.cs +++ b/Source/Tracking/ProgressionTracker.cs @@ -22,15 +22,97 @@ using System.Collections.Generic; using System.Text; namespace Nuclex.Support.Tracking { -/* - public abstract class ProgressionTracker { - public void Track() {} + /// Helps tracking the progress of one or more progressions + /// + /// + /// This is useful if you want to display a progress bar for multiple + /// progressions but can not guarantee that no additional progressions + /// will appear inmidst of execution. + /// + /// + /// This class does not implement the IProgression interface itself in + /// order to not violate the design principles of progressions which + /// guarantee that a progression will only finish once (whereas the + /// progression tracking might finish any number of times). + /// + /// + public class ProgressionTracker : IDisposable { - protected virtual void OnStartTracking(); - protected virtual void OnEndTracking(); - protected virtual void OnProgressUpdated(); + /// Triggered when the idle state of the tracker changes + /// + /// The tracker is idle when no progressions are being tracked in it. If you're + /// using this class to feed a progress bar, this would be the event to use for + /// showing or hiding the progress bar. The tracker starts off as idle because, + /// upon construction, its list of progressions will be empty. + /// + public event EventHandler AsyncIdleStateChanged; + + /// Triggered when the total progress has changed + public event EventHandler AsyncProgressUpdated; + + /// Immediately releases all resources owned by the instance + public void Dispose() { + // TODO: Untrack all + } + + /// Begins tracking the specified progression + /// Progression to be tracked + public void Track(Progression progression) { + Track(progression, 1.0f); + } + + /// Begins tracking the specified progression + /// Progression to be tracked + /// Weight to assign to this progression + public void Track(Progression progression, float weight) { + this.trackedProgressions.Add( + new WeightedProgression(progression, weight) + ); + this.totalWeight += weight; + + recalculateProgress(); + } + + /// Stops tracking the specified progression + /// Progression to stop tracking of + public void Untrack(Progression progression) { } + + /// Fires the AsyncIdleStateChanged event + /// New idle state to report + protected virtual void OnAsyncIdleStateChanged(bool idle) { + EventHandler copy = AsyncIdleStateChanged; + if(copy != null) + copy(this, new IdleStateEventArgs(idle)); + } + + /// Fires the AsyncProgressUpdated event + /// New progress to report + protected virtual void OnAsyncProgressUpdated(float progress) { + EventHandler copy = AsyncProgressUpdated; + if(copy != null) + copy(this, new ProgressUpdateEventArgs(progress)); + } + + /// Recalculates the total progress of the tracker + private void recalculateProgress() { + float totalProgress; + + for(int index = 0; index < trackedProgressions.Count; ++index) { + float weight = this.trackedProgressions[index].WeightedProgression; + totalProgress = this.trackedProgressions[index].Progress * weight; + } + + totalProgress /= this.totalWeight; + + //OnAsyncProgressUpdated( + } + + /// Total weight of all progressions being tracked + private float totalWeight; + /// Progressions being tracked by this tracker + private List> trackedProgressions; } -*/ + } // namespace Nuclex.Support.Tracking diff --git a/Source/Tracking/SetProgression.cs b/Source/Tracking/SetProgression.cs index a5e3c85..09745c2 100644 --- a/Source/Tracking/SetProgression.cs +++ b/Source/Tracking/SetProgression.cs @@ -32,7 +32,7 @@ namespace Nuclex.Support.Tracking { /// Performs common initialization for the public constructors private SetProgression() { - this.childs = new List>(); + this.childs = new List>(); } /// Initializes a new set progression @@ -47,10 +47,10 @@ namespace Nuclex.Support.Tracking { // progression and wrap it in an ObservedProgression foreach(ProgressionType progression in childs) { this.childs.Add( - new ObservedProgression( + new ObservedWeightedProgression( new WeightedProgression(progression), - new ObservedProgression.ReportDelegate(asyncProgressUpdated), - new ObservedProgression.ReportDelegate(asyncEnded) + new ObservedWeightedProgression.ReportDelegate(asyncProgressUpdated), + new ObservedWeightedProgression.ReportDelegate(asyncEnded) ) ); } @@ -71,10 +71,10 @@ namespace Nuclex.Support.Tracking { // Construct an ObservedProgression around each of the WeightedProgressions foreach(WeightedProgression progression in childs) { this.childs.Add( - new ObservedProgression( + new ObservedWeightedProgression( progression, - new ObservedProgression.ReportDelegate(asyncProgressUpdated), - new ObservedProgression.ReportDelegate(asyncEnded) + new ObservedWeightedProgression.ReportDelegate(asyncProgressUpdated), + new ObservedWeightedProgression.ReportDelegate(asyncEnded) ) ); @@ -110,8 +110,8 @@ namespace Nuclex.Support.Tracking { // 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 doesn't need a lock because it's a stateless wrapper. + // If it is constructed twice, then so be it, no problem at all. this.wrapper = new WeightedProgressionWrapperCollection( this.childs ); @@ -127,10 +127,10 @@ namespace Nuclex.Support.Tracking { /// Called when the progress of one of the observed progressions changes /// private void asyncProgressUpdated() { + float totalProgress = 0.0f; // 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; @@ -142,7 +142,6 @@ namespace Nuclex.Support.Tracking { // Send out the progress update OnAsyncProgressUpdated(totalProgress); - } /// @@ -162,7 +161,7 @@ namespace Nuclex.Support.Tracking { } /// Progressions being managed in the set - private List> childs; + private List> childs; /// /// Wrapper collection for exposing the child progressions under the /// WeightedProgression interface