Skip to content

Commit 48d957e

Browse files
Async Components
1 parent 1d6cdb7 commit 48d957e

8 files changed

Lines changed: 442 additions & 2 deletions

File tree

228 KB
Binary file not shown.
21.7 KB
Binary file not shown.
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
using Grasshopper.Kernel;
5+
using Rhino.Geometry;
6+
7+
using GrasshopperAsyncComponent;
8+
using System.Windows.Forms;
9+
10+
namespace PCampGHPlugin.AsyncComps
11+
{
12+
public class AsyncForLoopComponent : GH_AsyncComponent
13+
{
14+
/// <summary>
15+
/// Initializes a new instance of the AsyncForLoop class.
16+
/// </summary>
17+
public AsyncForLoopComponent()
18+
: base("AsyncForLoop", "AsyncForLoop",
19+
"AsyncForLoop",
20+
"PCamp", "Async")
21+
{
22+
BaseWorker = new ForLoopWorker();
23+
}
24+
25+
/// <summary>
26+
/// Registers all the input parameters for this component.
27+
/// </summary>
28+
protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
29+
{
30+
pManager.AddIntegerParameter("I", "Iterations", "", GH_ParamAccess.item);
31+
}
32+
33+
/// <summary>
34+
/// Registers all the output parameters for this component.
35+
/// </summary>
36+
protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
37+
{
38+
pManager.AddTextParameter("M", "Message", "", GH_ParamAccess.item);
39+
}
40+
41+
42+
43+
/// <summary>
44+
/// Provides an Icon for the component.
45+
/// </summary>
46+
protected override System.Drawing.Bitmap Icon
47+
{
48+
get
49+
{
50+
//You can add image files to your project resources and access them like this:
51+
// return Resources.IconForThisComponent;
52+
return Properties.Resources.pc_inv;
53+
}
54+
}
55+
56+
/// <summary>
57+
/// Gets the unique ID for this component. Do not change this ID after release.
58+
/// </summary>
59+
public override Guid ComponentGuid
60+
{
61+
get { return new Guid("7CC720C3-8645-4B6E-BD35-360450E7EF0A"); }
62+
}
63+
64+
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
65+
{
66+
base.AppendAdditionalMenuItems(menu);
67+
Menu_AppendItem(menu, "Cancel", (s, e) =>
68+
{
69+
RequestCancellation();
70+
});
71+
}
72+
73+
74+
private class ForLoopWorker : WorkerInstance
75+
{
76+
int numberOfIterations = -1;
77+
double finalResult = -1;
78+
79+
public ForLoopWorker() : base(null) { }
80+
81+
public override void GetData(IGH_DataAccess DA, GH_ComponentParamServer Params)
82+
{
83+
int _it = -1;
84+
DA.GetData(0, ref _it);
85+
86+
numberOfIterations = _it;
87+
}
88+
89+
public override void DoWork(Action<string, double> ReportProgress, Action Done)
90+
{
91+
// 👉 Checking for cancellation!
92+
if (CancellationToken.IsCancellationRequested) { return; }
93+
94+
double result = -1;
95+
96+
for (int i = 0; i <= numberOfIterations; i++)
97+
{
98+
if (CancellationToken.IsCancellationRequested) { return; }
99+
100+
result = i;
101+
102+
ReportProgress(Id, i / (double)numberOfIterations);
103+
}
104+
105+
finalResult = result;
106+
107+
Done();
108+
}
109+
110+
111+
public override void SetData(IGH_DataAccess DA)
112+
{
113+
DA.SetData(0, finalResult);
114+
}
115+
116+
public override WorkerInstance Duplicate() => new ForLoopWorker();
117+
118+
}
119+
}
120+
}
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
using Grasshopper.Kernel;
2+
using System;
3+
using System.Collections.Concurrent;
4+
using System.Collections.Generic;
5+
using System.Diagnostics;
6+
using System.Linq;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Timer = System.Timers.Timer;
10+
11+
namespace GrasshopperAsyncComponent
12+
{
13+
// https://github.com/specklesystems/GrasshopperAsyncComponent/blob/main/LICENSE
14+
15+
/// <summary>
16+
/// Inherit your component from this class to make all the async goodness available.
17+
/// </summary>
18+
public abstract class GH_AsyncComponent : GH_Component
19+
{
20+
public override Guid ComponentGuid => throw new Exception("ComponentGuid should be overriden in any descendant of GH_AsyncComponent!");
21+
22+
//List<(string, GH_RuntimeMessageLevel)> Errors;
23+
24+
Action<string, double> ReportProgress;
25+
26+
public ConcurrentDictionary<string, double> ProgressReports;
27+
28+
Action Done;
29+
30+
Timer DisplayProgressTimer;
31+
32+
int State = 0;
33+
34+
int SetData = 0;
35+
36+
public List<WorkerInstance> Workers;
37+
38+
List<Task> Tasks;
39+
40+
public readonly List<CancellationTokenSource> CancellationSources;
41+
42+
/// <summary>
43+
/// Set this property inside the constructor of your derived component.
44+
/// </summary>
45+
public WorkerInstance BaseWorker { get; set; }
46+
47+
/// <summary>
48+
/// Optional: if you have opinions on how the default system task scheduler should treat your workers, set it here.
49+
/// </summary>
50+
public TaskCreationOptions? TaskCreationOptions { get; set; } = null;
51+
52+
protected GH_AsyncComponent(string name, string nickname, string description, string category, string subCategory) : base(name, nickname, description, category, subCategory)
53+
{
54+
55+
DisplayProgressTimer = new Timer(333) { AutoReset = false };
56+
DisplayProgressTimer.Elapsed += DisplayProgress;
57+
58+
ReportProgress = (id, value) =>
59+
{
60+
ProgressReports[id] = value;
61+
if (!DisplayProgressTimer.Enabled)
62+
{
63+
DisplayProgressTimer.Start();
64+
}
65+
};
66+
67+
Done = () =>
68+
{
69+
Interlocked.Increment(ref State);
70+
if (State == Workers.Count && SetData == 0)
71+
{
72+
Interlocked.Exchange(ref SetData, 1);
73+
74+
// We need to reverse the workers list to set the outputs in the same order as the inputs.
75+
Workers.Reverse();
76+
77+
Rhino.RhinoApp.InvokeOnUiThread((Action)delegate
78+
{
79+
ExpireSolution(true);
80+
});
81+
}
82+
};
83+
84+
ProgressReports = new ConcurrentDictionary<string, double>();
85+
86+
Workers = new List<WorkerInstance>();
87+
CancellationSources = new List<CancellationTokenSource>();
88+
Tasks = new List<Task>();
89+
}
90+
91+
public virtual void DisplayProgress(object sender, System.Timers.ElapsedEventArgs e)
92+
{
93+
if (Workers.Count == 0 || ProgressReports.Values.Count == 0)
94+
{
95+
return;
96+
}
97+
98+
if (Workers.Count == 1)
99+
{
100+
Message = ProgressReports.Values.Last().ToString("0.00%");
101+
}
102+
else
103+
{
104+
double total = 0;
105+
foreach (var kvp in ProgressReports)
106+
{
107+
total += kvp.Value;
108+
}
109+
110+
Message = (total / Workers.Count).ToString("0.00%");
111+
}
112+
113+
Rhino.RhinoApp.InvokeOnUiThread((Action)delegate
114+
{
115+
OnDisplayExpired(true);
116+
});
117+
}
118+
119+
protected override void BeforeSolveInstance()
120+
{
121+
if (State != 0 && SetData == 1)
122+
{
123+
return;
124+
}
125+
126+
Debug.WriteLine("Killing");
127+
128+
foreach (var source in CancellationSources)
129+
{
130+
source.Cancel();
131+
}
132+
133+
CancellationSources.Clear();
134+
Workers.Clear();
135+
ProgressReports.Clear();
136+
Tasks.Clear();
137+
138+
Interlocked.Exchange(ref State, 0);
139+
}
140+
141+
protected override void AfterSolveInstance()
142+
{
143+
System.Diagnostics.Debug.WriteLine("After solve instance was called " + State + " ? " + Workers.Count);
144+
// We need to start all the tasks as close as possible to each other.
145+
if (State == 0 && Tasks.Count > 0 && SetData == 0)
146+
{
147+
System.Diagnostics.Debug.WriteLine("After solve INVOKATIONM");
148+
foreach (var task in Tasks)
149+
{
150+
task.Start();
151+
}
152+
}
153+
}
154+
155+
protected override void ExpireDownStreamObjects()
156+
{
157+
// Prevents the flash of null data until the new solution is ready
158+
if (SetData == 1)
159+
{
160+
base.ExpireDownStreamObjects();
161+
}
162+
}
163+
164+
protected override void SolveInstance(IGH_DataAccess DA)
165+
{
166+
//return;
167+
if (State == 0)
168+
{
169+
if (BaseWorker == null)
170+
{
171+
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Worker class not provided.");
172+
return;
173+
}
174+
175+
var currentWorker = BaseWorker.Duplicate();
176+
if (currentWorker == null)
177+
{
178+
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Could not get a worker instance.");
179+
return;
180+
}
181+
182+
// Let the worker collect data.
183+
currentWorker.GetData(DA, Params);
184+
185+
// Create the task
186+
var tokenSource = new CancellationTokenSource();
187+
currentWorker.CancellationToken = tokenSource.Token;
188+
currentWorker.Id = $"Worker-{DA.Iteration}";
189+
190+
var currentRun = TaskCreationOptions != null
191+
? new Task(() => currentWorker.DoWork(ReportProgress, Done), tokenSource.Token, (TaskCreationOptions)TaskCreationOptions)
192+
: new Task(() => currentWorker.DoWork(ReportProgress, Done), tokenSource.Token);
193+
194+
// Add cancellation source to our bag
195+
CancellationSources.Add(tokenSource);
196+
197+
// Add the worker to our list
198+
Workers.Add(currentWorker);
199+
200+
Tasks.Add(currentRun);
201+
202+
return;
203+
}
204+
205+
if (SetData == 0)
206+
{
207+
return;
208+
}
209+
210+
if (Workers.Count > 0)
211+
{
212+
Interlocked.Decrement(ref State);
213+
Workers[State].SetData(DA);
214+
}
215+
216+
if (State != 0)
217+
{
218+
return;
219+
}
220+
221+
CancellationSources.Clear();
222+
Workers.Clear();
223+
ProgressReports.Clear();
224+
Tasks.Clear();
225+
226+
Interlocked.Exchange(ref SetData, 0);
227+
228+
Message = "Done";
229+
OnDisplayExpired(true);
230+
}
231+
232+
public void RequestCancellation()
233+
{
234+
foreach (var source in CancellationSources)
235+
{
236+
source.Cancel();
237+
}
238+
239+
CancellationSources.Clear();
240+
Workers.Clear();
241+
ProgressReports.Clear();
242+
Tasks.Clear();
243+
244+
Interlocked.Exchange(ref State, 0);
245+
Interlocked.Exchange(ref SetData, 0);
246+
Message = "Cancelled";
247+
OnDisplayExpired(true);
248+
}
249+
250+
}
251+
}

0 commit comments

Comments
 (0)