From b88c61fe8881998fabcb45d0aa73f75f4e22f9a3 Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Wed, 7 Oct 2020 13:35:04 +0100 Subject: [PATCH 1/5] feat(multiple runs): adds iteration handling to async component base nifty data matching is now in place fixes #1 --- .../Base/GH_AsyncComponent.cs | 104 ++++++++++------- .../Base/IAsyncComponentWorker.cs | 17 ++- .../SampleAsyncComponent.cs | 110 ++++++++++++++---- 3 files changed, 166 insertions(+), 65 deletions(-) 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}"); + } + } + } From e5ba777fd25a725cb2e104a2e4f85d0840f9975e Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Wed, 7 Oct 2020 14:48:04 +0100 Subject: [PATCH 2/5] feat(cleanup): replaced interface with abstract class, cleanup, comments --- .../Base/GH_AsyncComponent.cs | 3 +- .../Base/IAsyncComponentWorker.cs | 65 +++++++++---------- .../SampleAsyncComponent.cs | 1 + 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs b/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs index d3bec08..569a7a6 100644 --- a/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs +++ b/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs @@ -65,7 +65,6 @@ namespace GrasshopperAsyncComponent { Rhino.RhinoApp.InvokeOnUiThread((Action)delegate { - //State = 1; ExpireSolution(true); }); } @@ -116,6 +115,7 @@ namespace GrasshopperAsyncComponent // Create the task var tokenSource = new CancellationTokenSource(); CurrentWorker.CancellationToken = tokenSource.Token; + CurrentWorker.Id = DA.Iteration.ToString(); var CurrentRun = new Task(() => CurrentWorker.DoWork(ReportProgress, ReportError, Done), tokenSource.Token, TaskCreationOptions.LongRunning); // Add cancelation source to our bag @@ -131,6 +131,7 @@ namespace GrasshopperAsyncComponent Workers[DA.Iteration].SetData(DA); + // Note we're decrementing the state here. if (--State == 0) { foreach (var (message, type) in Errors) diff --git a/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs b/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs index ed7f5a4..a470595 100644 --- a/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs +++ b/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs @@ -8,51 +8,48 @@ using System.Threading.Tasks; namespace GrasshopperAsyncComponent { - // TODO: Would an an abstract class be better here? - public interface IAsyncComponentWorker - { - - /// - /// This function should return a duplicate instance of your class. Make sure any state is duplicated (or not) properly. - /// - /// - IAsyncComponentWorker GetNewInstance(); - - /// - /// Here you can safely set the data of your component, just like you would normally. Important: do not call this method directly! When you are ready, call the provided "Done" action from the DoWork function. - /// - /// - void SetData(IGH_DataAccess DA); - - /// - /// Here you can safely collect the data from your component, just like you would normally. Important: do not call this method directly. It will be invoked by the parent component. - /// - /// The magic Data Access class. - /// The parameters list, in case you need it. - void CollectData(IGH_DataAccess DA, GH_ComponentParamServer Params); - - /// - /// This where the computation happens. Make sure to check and return if the token is cancelled! - /// - /// The cancellation token. - /// Call this action to report progress. It will be displayed in the component's message bar. - /// 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); - } - + + /// + /// A class that holds the actual compute logic and encapsulates the state it needs. Every needs to have one. + /// public abstract class WorkerInstance { + /// + /// This token is set by the parent . + /// public CancellationToken CancellationToken { get; set; } + /// + /// This is set by the parent . You can set it yourself, but it's not really worth it. + /// public string Id { get; set; } + /// + /// This is a "factory" method. It should return a fresh instance of this class, but with all the necessary state that you might have passed on directly from your component. + /// + /// public abstract WorkerInstance Duplicate(); - + + /// + /// This method is where the actual calculation/computation/heavy lifting should be done. + /// Make sure you always check as frequently as you can if is cancelled. For an example, see the . + /// + /// Call this to report progress up to the parent component. + /// Call this to report errors up to the parent component. + /// Call this when everything is done. It will tell the parent component that you're ready to . public abstract void DoWork(Action ReportProgress, Action ReportError, Action Done); + /// + /// Write your data setting logic here. Do not call this function directly from this class. It will be invoked by the parent after you've called `Done` in the function. + /// + /// public abstract void SetData(IGH_DataAccess DA); + /// + /// Write your data collection logic here. Do not call this method directly. It will be invoked by the parent . + /// + /// + /// public abstract void GetData(IGH_DataAccess DA, GH_ComponentParamServer Params); } diff --git a/GrasshopperAsyncComponent/SampleImplementations/SampleAsyncComponent.cs b/GrasshopperAsyncComponent/SampleImplementations/SampleAsyncComponent.cs index 0e24ccb..05e7703 100644 --- a/GrasshopperAsyncComponent/SampleImplementations/SampleAsyncComponent.cs +++ b/GrasshopperAsyncComponent/SampleImplementations/SampleAsyncComponent.cs @@ -87,6 +87,7 @@ namespace GrasshopperAsyncComponent.SampleImplementations int count = 0; long a = 2; + // Thanks Steak Overflow (TM) https://stackoverflow.com/a/13001749/ while (count < TehNthPrime) { if (CancellationToken.IsCancellationRequested) return; From fd229e1a7f22feaca7d039e7c1036e91def327b0 Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Wed, 7 Oct 2020 17:02:36 +0100 Subject: [PATCH 3/5] fix(data matching): corrects cancellation on fast input changes & other things: corrects getdata for child tasks, syncs current state, extra signalling for SetData phase --- .../Base/GH_AsyncComponent.cs | 83 ++++++++++++++----- .../Base/IAsyncComponentWorker.cs | 2 +- .../GrasshopperAsyncComponent.csproj | 2 +- ...> Sample_PrimeCalculatorAsyncComponent.cs} | 28 +++---- 4 files changed, 77 insertions(+), 38 deletions(-) rename GrasshopperAsyncComponent/SampleImplementations/{SampleAsyncComponent.cs => Sample_PrimeCalculatorAsyncComponent.cs} (77%) diff --git a/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs b/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs index 569a7a6..f955ce9 100644 --- a/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs +++ b/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs @@ -8,6 +8,9 @@ using Timer = System.Timers.Timer; namespace GrasshopperAsyncComponent { + /// + /// Inherit your component from this class to make all the async goodness available. + /// public abstract class GH_AsyncComponent : GH_Component { public override Guid ComponentGuid { get => new Guid("5DBBD498-0326-4E25-83A5-424D8DC493D4"); } @@ -30,12 +33,23 @@ namespace GrasshopperAsyncComponent int Iterations = 0; - public WorkerInstance BaseWorker { get; set; } + bool SetData = false; List Workers; + List Tasks; + List CancelationSources; + /// + /// Set this property inside the constructor of your derived component. + /// + public WorkerInstance BaseWorker { get; set; } + + /// + /// Optional: if you have opinions on how the default system task scheduler should treat your workers, set it here. + /// + public TaskCreationOptions? TaskCreationOptions { get; set; } = null; protected GH_AsyncComponent(string name, string nickname, string description, string category, string subCategory) : base(name, nickname, description, category, subCategory) { @@ -61,8 +75,10 @@ namespace GrasshopperAsyncComponent { State++; - if (State == Iterations) + if (State == Workers.Count) { + SetData = true; + Workers.Reverse(); Rhino.RhinoApp.InvokeOnUiThread((Action)delegate { ExpireSolution(true); @@ -74,28 +90,35 @@ namespace GrasshopperAsyncComponent Workers = new List(); CancelationSources = new List(); + Tasks = new List(); } protected override void BeforeSolveInstance() { - if (State != 0) return; + if (State != 0 && SetData) return; foreach (var source in CancelationSources) source.Cancel(); CancelationSources.Clear(); Workers.Clear(); Errors.Clear(); + Tasks.Clear(); State = 0; - Iterations = 0; - base.BeforeSolveInstance(); + } + + protected override void AfterSolveInstance() + { + if (State == 0 && Tasks.Count > 0) + { + foreach (var task in Tasks) task.Start(); + } } protected override void SolveInstance(IGH_DataAccess DA) { if (State == 0) { - Iterations++; if (BaseWorker == null) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Worker class not provided."); @@ -116,32 +139,50 @@ namespace GrasshopperAsyncComponent var tokenSource = new CancellationTokenSource(); CurrentWorker.CancellationToken = tokenSource.Token; CurrentWorker.Id = DA.Iteration.ToString(); - var CurrentRun = new Task(() => CurrentWorker.DoWork(ReportProgress, ReportError, Done), tokenSource.Token, TaskCreationOptions.LongRunning); + Task CurrentRun; + if (TaskCreationOptions != null) + { + CurrentRun = new Task(() => CurrentWorker.DoWork(ReportProgress, ReportError, Done), tokenSource.Token, (TaskCreationOptions)TaskCreationOptions); + } + else + { + CurrentRun = new Task(() => CurrentWorker.DoWork(ReportProgress, ReportError, Done), tokenSource.Token); + } // Add cancelation source to our bag CancelationSources.Add(tokenSource); + // Add the worker to our list Workers.Add(CurrentWorker); - CurrentRun.Start(); + Tasks.Add(CurrentRun); + return; } - var test = DA.Iteration; - - Workers[DA.Iteration].SetData(DA); - - // Note we're decrementing the state here. - if (--State == 0) + if (SetData) { - foreach (var (message, type) in Errors) - { - AddRuntimeMessage(type, message); - } + if (Workers.Count > 0) + Workers[--State].SetData(DA); - Message = "Done"; - OnDisplayExpired(true); - Errors.Clear(); + if (State == 0) + { + foreach (var (message, type) in Errors) + { + AddRuntimeMessage(type, message); + } + + CancelationSources.Clear(); + Workers.Clear(); + Errors.Clear(); + Tasks.Clear(); + + SetData = false; + + Message = "Done"; + //OnDisplayExpired(true); + OnDisplayExpired(false); + } } } } diff --git a/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs b/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs index a470595..b0943c0 100644 --- a/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs +++ b/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs @@ -32,7 +32,7 @@ namespace GrasshopperAsyncComponent /// /// This method is where the actual calculation/computation/heavy lifting should be done. - /// Make sure you always check as frequently as you can if is cancelled. For an example, see the . + /// Make sure you always check as frequently as you can if is cancelled. For an example, see the . /// /// Call this to report progress up to the parent component. /// Call this to report errors up to the parent component. diff --git a/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj b/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj index a981acc..8a12863 100644 --- a/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj +++ b/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj @@ -63,7 +63,7 @@ - + diff --git a/GrasshopperAsyncComponent/SampleImplementations/SampleAsyncComponent.cs b/GrasshopperAsyncComponent/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs similarity index 77% rename from GrasshopperAsyncComponent/SampleImplementations/SampleAsyncComponent.cs rename to GrasshopperAsyncComponent/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs index 05e7703..35b1479 100644 --- a/GrasshopperAsyncComponent/SampleImplementations/SampleAsyncComponent.cs +++ b/GrasshopperAsyncComponent/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; namespace GrasshopperAsyncComponent.SampleImplementations { - public class SampleAsyncComponent : GH_AsyncComponent + public class Sample_PrimeCalculatorAsyncComponent : GH_AsyncComponent { public override Guid ComponentGuid { get => new Guid("DF2B93E2-052D-4BE4-BC62-90DC1F169BF6"); } @@ -16,19 +16,19 @@ namespace GrasshopperAsyncComponent.SampleImplementations public override GH_Exposure Exposure => GH_Exposure.primary; - public SampleAsyncComponent() : base("Sample Async Component", "CYCLOMAXOTRON", "Meaningless labour.", "Samples", "Async") + public Sample_PrimeCalculatorAsyncComponent() : base("Sample Async Component", "PRIME", "Calculates the nth prime number.", "Samples", "Async") { - BaseWorker = new PrimeCalculator(); + BaseWorker = new PrimeCalculatorWorker(); } protected override void RegisterInputParams(GH_InputParamManager pManager) { - pManager.AddIntegerParameter("Max iterations", "M", "How many useless cycles should we spin. Minimum 10, maximum 1000.", GH_ParamAccess.item); + pManager.AddIntegerParameter("N", "N", "Which n-th prime number. Minimum 1, maximum one million. Take care, it can burn your CPU.", GH_ParamAccess.item); } protected override void RegisterOutputParams(GH_OutputParamManager pManager) { - pManager.AddTextParameter("Output", "O", "Will just say hello world after spinning.", GH_ParamAccess.item); + pManager.AddTextParameter("Output", "O", "The n-th prime number.", GH_ParamAccess.item); } } @@ -75,7 +75,7 @@ namespace GrasshopperAsyncComponent.SampleImplementations } } - public class PrimeCalculator : WorkerInstance + public class PrimeCalculatorWorker : WorkerInstance { int TehNthPrime { get; set; } = 100; long ThePrime { get; set; } = -1; @@ -120,24 +120,22 @@ namespace GrasshopperAsyncComponent.SampleImplementations Done(); } - public override WorkerInstance Duplicate() => new PrimeCalculator(); + public override WorkerInstance Duplicate() => new PrimeCalculatorWorker(); public override void GetData(IGH_DataAccess DA, GH_ComponentParamServer Params) { - if (CancellationToken.IsCancellationRequested) return; + int _nthPrime = 100; + DA.GetData(0, ref _nthPrime); + if (_nthPrime > 1000000) _nthPrime = 1000000; + if (_nthPrime < 1) _nthPrime = 1; - int _maxIterations = 100; - DA.GetData(0, ref _maxIterations); - if (_maxIterations > 1000000) _maxIterations = 1000000; - if (_maxIterations < 10) _maxIterations = 10; - - TehNthPrime = _maxIterations; + TehNthPrime = _nthPrime; } 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}"); + DA.SetData(0, $"W_ID {Id}: {TehNthPrime}th prime is: {ThePrime}"); } } From 1446fe2e8aafae4bb360fa6b62cb8d1b59e4fcca Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Wed, 7 Oct 2020 19:35:43 +0100 Subject: [PATCH 4/5] refactor(cleanup): cleans up a bit, adds some comments, renames components --- .../Base/GH_AsyncComponent.cs | 6 +- .../GrasshopperAsyncComponent.csproj | 1 + .../Sample_PrimeCalculatorAsyncComponent.cs | 61 +++----------- .../Sample_UslessCyclesComponent.cs | 80 +++++++++++++++++++ 4 files changed, 96 insertions(+), 52 deletions(-) create mode 100644 GrasshopperAsyncComponent/SampleImplementations/Sample_UslessCyclesComponent.cs diff --git a/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs b/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs index f955ce9..723a1a5 100644 --- a/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs +++ b/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs @@ -78,7 +78,9 @@ namespace GrasshopperAsyncComponent if (State == Workers.Count) { SetData = true; + // We need to reverse the workers list to set the outputs in the same order as the inputs. Workers.Reverse(); + Rhino.RhinoApp.InvokeOnUiThread((Action)delegate { ExpireSolution(true); @@ -109,6 +111,7 @@ namespace GrasshopperAsyncComponent protected override void AfterSolveInstance() { + // We need to start all the tasks as close as possible to each other. if (State == 0 && Tasks.Count > 0) { foreach (var task in Tasks) task.Start(); @@ -180,8 +183,7 @@ namespace GrasshopperAsyncComponent SetData = false; Message = "Done"; - //OnDisplayExpired(true); - OnDisplayExpired(false); + OnDisplayExpired(true); } } } diff --git a/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj b/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj index 8a12863..1e44e20 100644 --- a/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj +++ b/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj @@ -64,6 +64,7 @@ + diff --git a/GrasshopperAsyncComponent/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs b/GrasshopperAsyncComponent/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs index 35b1479..acadebc 100644 --- a/GrasshopperAsyncComponent/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs +++ b/GrasshopperAsyncComponent/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs @@ -10,7 +10,7 @@ namespace GrasshopperAsyncComponent.SampleImplementations { public class Sample_PrimeCalculatorAsyncComponent : GH_AsyncComponent { - public override Guid ComponentGuid { get => new Guid("DF2B93E2-052D-4BE4-BC62-90DC1F169BF6"); } + public override Guid ComponentGuid { get => new Guid("22C612B0-2C57-47CE-B9FE-E10621F18933"); } protected override System.Drawing.Bitmap Icon { get => null; } @@ -32,71 +32,30 @@ namespace GrasshopperAsyncComponent.SampleImplementations } } - public class SampleAsyncWorker : WorkerInstance - { - int MaxIterations { get; set; } = 100; - - public override void DoWork(Action ReportProgress, Action ReportError, Action Done) - { - 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 (CancellationToken.IsCancellationRequested) return; - } - - Done(); - } - - public override WorkerInstance Duplicate() => new SampleAsyncWorker(); - - public override void GetData(IGH_DataAccess DA, GH_ComponentParamServer Params) - { - 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 override void SetData(IGH_DataAccess DA) - { - if (CancellationToken.IsCancellationRequested) return; - DA.SetData(0, $"Hello world. Worker {Id} has spun for {MaxIterations} iterations."); - } - } - public class PrimeCalculatorWorker : WorkerInstance { - int TehNthPrime { get; set; } = 100; + int TheNthPrime { get; set; } = 100; long ThePrime { get; set; } = -1; public override void DoWork(Action ReportProgress, Action ReportError, Action Done) { + // πŸ‘‰ Checking for cancellation! if (CancellationToken.IsCancellationRequested) return; int count = 0; long a = 2; // Thanks Steak Overflow (TM) https://stackoverflow.com/a/13001749/ - while (count < TehNthPrime) + while (count < TheNthPrime) { + // πŸ‘‰ Checking for cancellation! if (CancellationToken.IsCancellationRequested) return; long b = 2; int prime = 1;// to check if found a prime while (b * b <= a) { - + // πŸ‘‰ Checking for cancellation! if (CancellationToken.IsCancellationRequested) return; if (a % b == 0) @@ -107,7 +66,7 @@ namespace GrasshopperAsyncComponent.SampleImplementations b++; } - ReportProgress(((double)(count) / (double)TehNthPrime).ToString("0.00%")); + ReportProgress(((double)(count) / TheNthPrime).ToString("0.00%")); if (prime > 0) { @@ -129,13 +88,15 @@ namespace GrasshopperAsyncComponent.SampleImplementations if (_nthPrime > 1000000) _nthPrime = 1000000; if (_nthPrime < 1) _nthPrime = 1; - TehNthPrime = _nthPrime; + TheNthPrime = _nthPrime; } public override void SetData(IGH_DataAccess DA) { + // πŸ‘‰ Checking for cancellation! if (CancellationToken.IsCancellationRequested) return; - DA.SetData(0, $"W_ID {Id}: {TehNthPrime}th prime is: {ThePrime}"); + + DA.SetData(0, $"Worker {Id}: {TheNthPrime}th prime is: {ThePrime}"); } } diff --git a/GrasshopperAsyncComponent/SampleImplementations/Sample_UslessCyclesComponent.cs b/GrasshopperAsyncComponent/SampleImplementations/Sample_UslessCyclesComponent.cs new file mode 100644 index 0000000..dcb6041 --- /dev/null +++ b/GrasshopperAsyncComponent/SampleImplementations/Sample_UslessCyclesComponent.cs @@ -0,0 +1,80 @@ +ο»Ώusing Grasshopper.Kernel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace GrasshopperAsyncComponent.SampleImplementations +{ + public class Sample_UselessCyclesAsyncComponent : GH_AsyncComponent + { + public override Guid ComponentGuid { get => new Guid("DF2B93E2-052D-4BE4-BC62-90DC1F169BF6"); } + + protected override System.Drawing.Bitmap Icon { get => null; } + + public override GH_Exposure Exposure => GH_Exposure.primary; + + public Sample_UselessCyclesAsyncComponent() : base("Sample Async Component", "CYCLOMATRON-X", "Spins uselessly.", "Samples", "Async") + { + BaseWorker = new UselessCyclesWorker(); + } + + protected override void RegisterInputParams(GH_InputParamManager pManager) + { + pManager.AddIntegerParameter("N", "N", "Number of spins.", GH_ParamAccess.item); + } + + protected override void RegisterOutputParams(GH_OutputParamManager pManager) + { + pManager.AddTextParameter("Output", "O", "Nothing really interesting.", GH_ParamAccess.item); + } + } + + public class UselessCyclesWorker : WorkerInstance + { + int MaxIterations { get; set; } = 100; + + public override void DoWork(Action ReportProgress, Action ReportError, Action Done) + { + // Checking for cancellation + 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%")); + + // Checking for cancellation + if (CancellationToken.IsCancellationRequested) return; + } + + Done(); + } + + public override WorkerInstance Duplicate() => new UselessCyclesWorker(); + + public override void GetData(IGH_DataAccess DA, GH_ComponentParamServer Params) + { + 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 override void SetData(IGH_DataAccess DA) + { + if (CancellationToken.IsCancellationRequested) return; + DA.SetData(0, $"Hello world. Worker {Id} has spun for {MaxIterations} iterations."); + } + } + +} From 4d9d25cb218834526ac6a48722d7de4d0ce1402c Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Wed, 7 Oct 2020 19:36:24 +0100 Subject: [PATCH 5/5] fix(cleanup): removes useless variable --- GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs b/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs index 723a1a5..4bf3d06 100644 --- a/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs +++ b/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs @@ -31,8 +31,6 @@ namespace GrasshopperAsyncComponent int State = 0; - int Iterations = 0; - bool SetData = false; List Workers;