algorithmic modeling for Rhino
ScheduleSolution should be the preferred method. It is conceivable it doesn't do what you need, in which case you'll need to find a different solution, but always start with ScheduleSolution. The way it works is as follows:
Let's imagine the following scenario (with insanely scaled up time spans):
Note that (A) and (C) got called back way earlier than they requested. They scheduled solutions for 15 and 10 minutes respectively, but instead got the call only 5 minutes in. They can either play ball and accept the new schedule, or they can choose to not expire themselves and instead schedule a new solution for the future.
If at point 7 instead of nothing happening, a new solution was triggered by some other event (user dragging a slider or changing a wire), all the callbacks are still handled, but now even earlier than they expected.
You can always schedule a solution, which is what makes this solution more flexible than other approaches. It doesn't matter that a solution is already running. It doesn't matter that the schedule comes from another thread*.
On the other hand, schedules are annoying because the time you request is not necessarily the time you get.
Also, if you have 50 components that all want to schedule, you must pick a delay big enough so that they all manage to register their callbacks before the new solution starts. This may be tricky.
* I'm actually not 100% sure about the threadsafety, it could be that there's bugs under rare conditions.
What is the difference between ExpireSolution(true) and ExpireSolution(false)? When is it safe to call either of them?
ExpireSolution expires the object it is called on, including all dependent objects. If the argument is false, there will not be a new solution right after the expiration. If the argument is true, there will be a new solution right away.
Basically, use ExpireSolution(false) if you intend to do more stuff before the next solution starts. So if you want to expire 12 components, you call ExpireSolution(false); on them all, then when you're done you call GH_Document.NewSolution(false);
How do I determine if it's safe to request a new solution?
If it's scheduling, it's always safe.
If you're going to call GH_Document.NewSolution(bool) yourself, or ExpireSolution(bool) on any object in the document, then you need to check the GH_Document.SolutionState. If it's GH_ProcessStep.Process, then you are not allowed to start new solution.
Any thoughts on a generic way to solve this (Rather than each component subscribing to events, a way to subscribe to an expiration at the next earliest conveinience?)
I need to think about this some more...
I'll give ScheduleSolution another try, and figure out why it did not work before; it seems to be a rather complete solution to my questions (thanks!). Am I correct in assuming that GH_Document.NewSolution() will be called after the Schedule solution executed all callbacks?
Thanks a lot for the explanation!
Ok, small follow up of lessons learned:
Not treating Schedule Solution as an event. During the solution I called Schedule solution multiple times for the same component. This seemed to cause the lock-up scenario's.
Calling ExpireSolution(true) inside the callback of schedule solution, this seems a recipie for instant lock-up scenario's.
This seems to work pretty well:
private bool _askingNewSolution;
public void ScheduleSolution(int miliseconds)
// only ask once.
_askingNewSolution = true;
OnPingDocument().ScheduleSolution(10, doc =>
_askingNewSolution = false;
Thanks a lot for the insight in solving this!
Do you really need the private bool there? As long as you don't trigger new solution from your callback, it should be fine. Even if you end up having multiple callbacks it shouldn't matter, as ExpireSolution(false) only does something the first time. The second time you call it the object is already expired and nothing happens. It takes a successful solution to make the object expirable again.
Ok, it could be that my mistakes are limited to #2. I'll have a good use case on friday to test it. (A controller that send out events every x miliseconds). I was not sure how expensive the ExpireSolution(false) call would be.
[Edit] My main line of though was: I don't trust the speed at which the controllers events (and thus requests for a new solution) are coming in. Nor will I know how fast or slow the entire solution will be. If e.g. 1000 requests for a new solution arrive in the time that it takes grasshopper to calculate one solution; I wouldn't want 1000 callbacks to be executed. Hence the boolean check to only add one callback to expire the component.
On an related note: Is it possible that ScheduleSolution(0, callback) in a loop will crash rhino in a die-hard fashion? (For a test case: change the timeout on line 129 in the C# component in the attached file.)
Good to know, thanks for the heads up. I'm still a bit puzzled when would be a good use case to use Invoke? Is code running in a grasshopper solution for example in a different thread than the UI thread?