diff --git a/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs b/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs index 92c9e6b..d3bec08 100644 --- a/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs +++ b/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs @@ -16,14 +16,6 @@ namespace GrasshopperAsyncComponent public override GH_Exposure Exposure => GH_Exposure.hidden; - public IAsyncComponentWorker Worker; - - IAsyncComponentWorker CurrentWorker; - - Task CurrentRun; - - ConcurrentBag TokenSources = new ConcurrentBag(); - Action ReportProgress; Action ReportError; @@ -32,9 +24,18 @@ namespace GrasshopperAsyncComponent Action Done; + Timer DisplayProgressTimer; + int State = 0; - Timer DisplayProgressTimer; + int Iterations = 0; + + public WorkerInstance BaseWorker { get; set; } + + List Workers; + + List CancelationSources; + protected GH_AsyncComponent(string name, string nickname, string description, string category, string subCategory) : base(name, nickname, description, category, subCategory) { @@ -50,78 +51,97 @@ namespace GrasshopperAsyncComponent ReportProgress = (progress) => { - Rhino.RhinoApp.InvokeOnUiThread((Action)delegate - { - Message = progress; - if (!DisplayProgressTimer.Enabled) DisplayProgressTimer.Start(); - }); + Message = progress; + if (!DisplayProgressTimer.Enabled) DisplayProgressTimer.Start(); }; ReportError = (error, type) => Errors?.Add((error, type)); Done = () => { - Rhino.RhinoApp.InvokeOnUiThread((Action)delegate + State++; + + if (State == Iterations) { - State = 1; - ExpireSolution(true); - }); + Rhino.RhinoApp.InvokeOnUiThread((Action)delegate + { + //State = 1; + ExpireSolution(true); + }); + } }; Errors = new List<(string, GH_RuntimeMessageLevel)>(); + + Workers = new List(); + CancelationSources = new List(); + } + + protected override void BeforeSolveInstance() + { + if (State != 0) return; + + foreach (var source in CancelationSources) source.Cancel(); + + CancelationSources.Clear(); + Workers.Clear(); + Errors.Clear(); + + State = 0; + Iterations = 0; + base.BeforeSolveInstance(); } protected override void SolveInstance(IGH_DataAccess DA) { if (State == 0) { - if (Worker == null) + Iterations++; + if (BaseWorker == null) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Worker class not provided."); return; } - CurrentWorker = Worker.GetNewInstance(); + var CurrentWorker = BaseWorker.Duplicate(); if (CurrentWorker == null) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Could not get a worker instance."); return; } - Errors = new List<(string, GH_RuntimeMessageLevel)>(); - - // Request the cancellation of any old tasks. - CancellationTokenSource oldTokenSource; - while (TokenSources.TryTake(out oldTokenSource)) - { - oldTokenSource?.Cancel(); - } - // Let the worker collect data. - CurrentWorker.CollectData(DA, Params); + CurrentWorker.GetData(DA, Params); // Create the task var tokenSource = new CancellationTokenSource(); - CurrentRun = new Task(() => CurrentWorker.DoWork(tokenSource.Token, ReportProgress, ReportError, Done), tokenSource.Token); + CurrentWorker.CancellationToken = tokenSource.Token; + var CurrentRun = new Task(() => CurrentWorker.DoWork(ReportProgress, ReportError, Done), tokenSource.Token, TaskCreationOptions.LongRunning); // Add cancelation source to our bag - TokenSources.Add(tokenSource); + CancelationSources.Add(tokenSource); + // Add the worker to our list + Workers.Add(CurrentWorker); + CurrentRun.Start(); return; } - foreach (var (message, type) in Errors) + var test = DA.Iteration; + + Workers[DA.Iteration].SetData(DA); + + if (--State == 0) { - AddRuntimeMessage(type, message); + foreach (var (message, type) in Errors) + { + AddRuntimeMessage(type, message); + } + + Message = "Done"; + OnDisplayExpired(true); + Errors.Clear(); } - - OnDisplayExpired(true); - CurrentWorker.SetData(DA); - - Message = "Done"; - - Errors.Clear(); - State = 0; } } } diff --git a/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs b/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs index 8c5ac07..ed7f5a4 100644 --- a/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs +++ b/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs @@ -39,6 +39,21 @@ namespace GrasshopperAsyncComponent /// Call this to report a warning or an error. /// When you are done computing, call this function to have the parent component invoke the SetData function. void DoWork(CancellationToken token, Action ReportProgress, Action ReportError, Action Done); - } + + public abstract class WorkerInstance + { + public CancellationToken CancellationToken { get; set; } + + public string Id { get; set; } + + public abstract WorkerInstance Duplicate(); + + public abstract void DoWork(Action ReportProgress, Action ReportError, Action Done); + + public abstract void SetData(IGH_DataAccess DA); + + public abstract void GetData(IGH_DataAccess DA, GH_ComponentParamServer Params); + } + } diff --git a/GrasshopperAsyncComponent/SampleImplementations/SampleAsyncComponent.cs b/GrasshopperAsyncComponent/SampleImplementations/SampleAsyncComponent.cs index ec4ab0a..0e24ccb 100644 --- a/GrasshopperAsyncComponent/SampleImplementations/SampleAsyncComponent.cs +++ b/GrasshopperAsyncComponent/SampleImplementations/SampleAsyncComponent.cs @@ -16,9 +16,9 @@ namespace GrasshopperAsyncComponent.SampleImplementations public override GH_Exposure Exposure => GH_Exposure.primary; - public SampleAsyncComponent() : base("Sample Async Component", "ASYNC", "Meaningless labour.", "Samples", "Async") + public SampleAsyncComponent() : base("Sample Async Component", "CYCLOMAXOTRON", "Meaningless labour.", "Samples", "Async") { - Worker = new SampleAsyncComponentWorker(); + BaseWorker = new PrimeCalculator(); } protected override void RegisterInputParams(GH_InputParamManager pManager) @@ -32,46 +32,112 @@ namespace GrasshopperAsyncComponent.SampleImplementations } } - public class SampleAsyncComponentWorker : IAsyncComponentWorker + public class SampleAsyncWorker : WorkerInstance { int MaxIterations { get; set; } = 100; - - public void CollectData(IGH_DataAccess DA, GH_ComponentParamServer Params) + + public override void DoWork(Action ReportProgress, Action ReportError, Action Done) { - int _maxIterations = 100; - DA.GetData(0, ref _maxIterations); - if (_maxIterations > 1000) MaxIterations = 1000; - if (_maxIterations < 10) MaxIterations = 10; - - MaxIterations = _maxIterations; - } - - public void DoWork(CancellationToken token, Action ReportProgress, Action ReportError, Action Done) - { - if (token.IsCancellationRequested) return; + if (CancellationToken.IsCancellationRequested) return; for (int i = 0; i <= MaxIterations; i++) { var sw = new SpinWait(); for (int j = 0; j <= 100; j++) sw.SpinOnce(); - + ReportProgress(((double)(i + 1) / (double)MaxIterations).ToString("0.00%")); - if (token.IsCancellationRequested) return; + if (CancellationToken.IsCancellationRequested) return; } Done(); } - public IAsyncComponentWorker GetNewInstance() + public override WorkerInstance Duplicate() => new SampleAsyncWorker(); + + public override void GetData(IGH_DataAccess DA, GH_ComponentParamServer Params) { - return new SampleAsyncComponentWorker(); + if (CancellationToken.IsCancellationRequested) return; + + int _maxIterations = 100; + DA.GetData(0, ref _maxIterations); + if (_maxIterations > 1000) _maxIterations = 1000; + if (_maxIterations < 10) _maxIterations = 10; + + MaxIterations = _maxIterations; } - public void SetData(IGH_DataAccess DA) + public override void SetData(IGH_DataAccess DA) { - DA.SetData(0, "Hello world. I'm done spinning."); + if (CancellationToken.IsCancellationRequested) return; + DA.SetData(0, $"Hello world. Worker {Id} has spun for {MaxIterations} iterations."); } } + + public class PrimeCalculator : WorkerInstance + { + int TehNthPrime { get; set; } = 100; + long ThePrime { get; set; } = -1; + + public override void DoWork(Action ReportProgress, Action ReportError, Action Done) + { + if (CancellationToken.IsCancellationRequested) return; + + int count = 0; + long a = 2; + + while (count < TehNthPrime) + { + if (CancellationToken.IsCancellationRequested) return; + + long b = 2; + int prime = 1;// to check if found a prime + while (b * b <= a) + { + + if (CancellationToken.IsCancellationRequested) return; + + if (a % b == 0) + { + prime = 0; + break; + } + b++; + } + + ReportProgress(((double)(count) / (double)TehNthPrime).ToString("0.00%")); + + if (prime > 0) + { + count++; + } + a++; + } + + ThePrime = --a; + Done(); + } + + public override WorkerInstance Duplicate() => new PrimeCalculator(); + + public override void GetData(IGH_DataAccess DA, GH_ComponentParamServer Params) + { + if (CancellationToken.IsCancellationRequested) return; + + int _maxIterations = 100; + DA.GetData(0, ref _maxIterations); + if (_maxIterations > 1000000) _maxIterations = 1000000; + if (_maxIterations < 10) _maxIterations = 10; + + TehNthPrime = _maxIterations; + } + + public override void SetData(IGH_DataAccess DA) + { + if (CancellationToken.IsCancellationRequested) return; + DA.SetData(0, $"Hello world. Worker {Id} has found for that the {TehNthPrime}th prime is: {ThePrime}"); + } + } + }