diff --git a/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs b/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs index 4bf3d06..3eee2bb 100644 --- a/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs +++ b/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Grasshopper.Kernel; @@ -19,12 +20,14 @@ namespace GrasshopperAsyncComponent public override GH_Exposure Exposure => GH_Exposure.hidden; - Action ReportProgress; - Action ReportError; List<(string, GH_RuntimeMessageLevel)> Errors; + Action ReportProgress; + + ConcurrentDictionary ProgressReports; + Action Done; Timer DisplayProgressTimer; @@ -55,15 +58,31 @@ namespace GrasshopperAsyncComponent DisplayProgressTimer = new Timer(333) { AutoReset = false }; DisplayProgressTimer.Elapsed += (s, e) => { + if (Workers.Count == 0) return; + if (Workers.Count == 1) + { + Message = ProgressReports.Values.Last().ToString("0.00%"); + } + else + { + double total = 0; + foreach (var kvp in ProgressReports) + { + total += kvp.Value; + } + + Message = (total / Workers.Count).ToString("0.00%"); + } + Rhino.RhinoApp.InvokeOnUiThread((Action)delegate { OnDisplayExpired(true); }); }; - ReportProgress = (progress) => + ReportProgress = (id, value) => { - Message = progress; + ProgressReports[id] = value; if (!DisplayProgressTimer.Enabled) DisplayProgressTimer.Start(); }; @@ -87,6 +106,7 @@ namespace GrasshopperAsyncComponent }; Errors = new List<(string, GH_RuntimeMessageLevel)>(); + ProgressReports = new ConcurrentDictionary(); Workers = new List(); CancelationSources = new List(); @@ -102,9 +122,12 @@ namespace GrasshopperAsyncComponent CancelationSources.Clear(); Workers.Clear(); Errors.Clear(); + ProgressReports.Clear(); Tasks.Clear(); State = 0; + + var test = Params.Output[0].VolatileData; } protected override void AfterSolveInstance() @@ -113,9 +136,17 @@ namespace GrasshopperAsyncComponent if (State == 0 && Tasks.Count > 0) { foreach (var task in Tasks) task.Start(); + var test = Params.Output[0].VolatileData; } } + protected override void ExpireDownStreamObjects() + { + // Prevents the flash of null data until the new solution is ready + if (SetData) + base.ExpireDownStreamObjects(); + } + protected override void SolveInstance(IGH_DataAccess DA) { if (State == 0) @@ -139,7 +170,7 @@ namespace GrasshopperAsyncComponent // Create the task var tokenSource = new CancellationTokenSource(); CurrentWorker.CancellationToken = tokenSource.Token; - CurrentWorker.Id = DA.Iteration.ToString(); + CurrentWorker.Id = $"Worker-{DA.Iteration}"; Task CurrentRun; if (TaskCreationOptions != null) @@ -176,6 +207,7 @@ namespace GrasshopperAsyncComponent CancelationSources.Clear(); Workers.Clear(); Errors.Clear(); + ProgressReports.Clear(); Tasks.Clear(); SetData = false; diff --git a/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs b/GrasshopperAsyncComponent/Base/WorkerInstance.cs similarity index 95% rename from GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs rename to GrasshopperAsyncComponent/Base/WorkerInstance.cs index b0943c0..41bf0bb 100644 --- a/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs +++ b/GrasshopperAsyncComponent/Base/WorkerInstance.cs @@ -37,7 +37,7 @@ namespace GrasshopperAsyncComponent /// 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); + 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. diff --git a/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj b/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj index 1e44e20..30e8f2c 100644 --- a/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj +++ b/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj @@ -61,7 +61,7 @@ - + diff --git a/GrasshopperAsyncComponent/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs b/GrasshopperAsyncComponent/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs index acadebc..1d91333 100644 --- a/GrasshopperAsyncComponent/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs +++ b/GrasshopperAsyncComponent/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs @@ -16,7 +16,7 @@ namespace GrasshopperAsyncComponent.SampleImplementations public override GH_Exposure Exposure => GH_Exposure.primary; - public Sample_PrimeCalculatorAsyncComponent() : base("Sample Async Component", "PRIME", "Calculates the nth prime number.", "Samples", "Async") + public Sample_PrimeCalculatorAsyncComponent() : base("The N-th Prime Calculator", "PRIME", "Calculates the nth prime number.", "Samples", "Async") { BaseWorker = new PrimeCalculatorWorker(); } @@ -28,7 +28,8 @@ namespace GrasshopperAsyncComponent.SampleImplementations protected override void RegisterOutputParams(GH_OutputParamManager pManager) { - pManager.AddTextParameter("Output", "O", "The n-th prime number.", GH_ParamAccess.item); + pManager.AddNumberParameter("Output", "O", "The n-th prime number.", GH_ParamAccess.item); + } } @@ -37,7 +38,7 @@ namespace GrasshopperAsyncComponent.SampleImplementations int TheNthPrime { get; set; } = 100; long ThePrime { get; set; } = -1; - public override void DoWork(Action ReportProgress, Action ReportError, Action Done) + public override void DoWork(Action ReportProgress, Action ReportError, Action Done) { // 👉 Checking for cancellation! if (CancellationToken.IsCancellationRequested) return; @@ -66,7 +67,7 @@ namespace GrasshopperAsyncComponent.SampleImplementations b++; } - ReportProgress(((double)(count) / TheNthPrime).ToString("0.00%")); + ReportProgress(Id, ((double)count) / TheNthPrime); if (prime > 0) { @@ -96,7 +97,7 @@ namespace GrasshopperAsyncComponent.SampleImplementations // 👉 Checking for cancellation! if (CancellationToken.IsCancellationRequested) return; - DA.SetData(0, $"Worker {Id}: {TheNthPrime}th prime is: {ThePrime}"); + DA.SetData(0, ThePrime); } } diff --git a/GrasshopperAsyncComponent/SampleImplementations/Sample_UslessCyclesComponent.cs b/GrasshopperAsyncComponent/SampleImplementations/Sample_UslessCyclesComponent.cs index dcb6041..f3204fa 100644 --- a/GrasshopperAsyncComponent/SampleImplementations/Sample_UslessCyclesComponent.cs +++ b/GrasshopperAsyncComponent/SampleImplementations/Sample_UslessCyclesComponent.cs @@ -36,7 +36,7 @@ namespace GrasshopperAsyncComponent.SampleImplementations { int MaxIterations { get; set; } = 100; - public override void DoWork(Action ReportProgress, Action ReportError, Action Done) + public override void DoWork(Action ReportProgress, Action ReportError, Action Done) { // Checking for cancellation if (CancellationToken.IsCancellationRequested) return; @@ -47,8 +47,8 @@ namespace GrasshopperAsyncComponent.SampleImplementations for (int j = 0; j <= 100; j++) sw.SpinOnce(); - ReportProgress(((double)(i + 1) / (double)MaxIterations).ToString("0.00%")); - + ReportProgress(Id, ((double)(i + 1) / (double)MaxIterations)); + // Checking for cancellation if (CancellationToken.IsCancellationRequested) return; }