How does component calculation in Grasshopper work, and how can we control how to expire downstream components in Grasshopper?
The calculation of a Grasshopper canvas is quite complicated. It has to be able to calculate which components need calculating, and the order it does it in.
How Grasshopper does calculations – usually
It does this through a system of ‘expired’ markers. If a component needs calculating, it is marked as ‘expired = true’. It then solves each component marked as expired, one at a time, until all expired components have been calculated. A calculated component can then have its ‘expired’ flag set to false.
A component will be marked as expired if any changes are made to it, for example, it receives a new value in one of its inputs. Furthermore, if a component is marked as expired, by default, every component downstream of this component will also be marked as expired.
This behaviour is quite reasonable in most cases in Grasshopper. It guarantees that all components will be up-to-date, and that each component’s output will be correct according to the latest input values happening upstream. It also ensures that components are not re-calcualted needlessly – only components that need calculating will be recalculated.
What if this behaviour isn’t what I want?
On a rare occasion, this method of component calculation is actually not helpful for us. This usually happens when we are trying to perform another task which is also not in the spirit of Grasshopper workflows.
I have recently been working on the BatchRun component to make it more usable in design workflows. This component remotely adjusts sliders on the canvas, allowing a form of iterative workflow not normally possible in Grasshopper.
One key feature I needed from BatchRun was that, for every movement of the slider, a new set of geometry would be generated in Grasshopper, and this requires a unique name. The most logical solution is to generate a name from the slider values. So I added a ‘name’ output:
When the user presses ‘startme’, BatchRun will automatically cycle through the sliders. Two sliders, each with 11 values, will produce 121 combinations of the two sliders. To be sure that the user wants to do this possibly time-consuming task, a message box appears:
If the user presses ‘yes’, and only when the user presses ‘yes’, Grasshopper should start to iterate through the sliders.
Why wouldn’t this normally work?
Notice that the component is initiated by a boolean button. The behaviour of the BatchRun component, explained fully here, dictates that BatchRun will then adjust the sliders. The intended action is that the sliders will then trigger the ‘building height’ and ‘building width’ params, which then will trigger the ‘doCalcs’. DoCalcs will pick up the name from the BatchRun ‘name’ output.
This normally works well. DoCalcs recognises that, when a slider has changed, both the slider values and the name have expired, and doCalcs will only function once both the sliders and the ‘name’ are up-to-date.
But what about, right at the beginning, when the user has pressed the button just to trigger the message box? The user may press ‘no’, in which case, we shouldn’t expect that any calculations have taken place. But watch what happens instead:
Because the button is pressed, this instantly expires the BatchRun component. This is necessary – since it’s this expiration which causes BatchRun to wake up, calculate itself, and realise it needs to show a message box.
But – BatchRun itself also has outputs. Anything connected to these outputs will then be expired. So the panel becomes expired. And, in turn, doCalcs becomes expired.
Then, once this ‘expiration wave’ has come to a stop, Grasshopper then sets itself up with doing the calculations on these expired components. As a simplified rule, it starts with the left-most components and works right.
The trouble is that, even when a component’s inputs’ data has not changed, if that component has been marked as ‘expired’, it still feels the need to recalculate itself. So, the simple act of pressing the button will compel doCalcs to recalculate – even if the user then selects ‘no’ on the messagebox that is triggered.
What’s the solution?
Ideally, we want BatchRun to be expired whenever the startme button is pressed. This is of course important – since it’s expiration which triggers BatchRun to do calculations on itself.
But, we want control on when BatchRun expires downstream objects. Can we use some logic to control whether or not BatchRun will send an expiration to downstream objects?
To try this out, I will be working on a component developed in Visual Studio. As far as I know, these techniques aren’t possible directly in the C# component in Grasshopper. You can set yourself up for developing components in VS here.
How not to expire downstream objects
My first idea was to try and find a component property called ‘Expired’ or something like it. But that failed – there’s nothing there!
There are plenty of methods to force a component to expire and recalculate, but nothing to cancel it.
But there must be some other way of controlling the expiration wave, right?
How not to expire downstream objects, v2
When developing Grasshopper components in Visual Studio, you have access to a wide range of override methods. Override methods control various behaviours of our component class. If we don’t implement an override, these methods work in the background and implement default behaviour. By implementing an override, we can override this default behaviour. To view which overrides are possible, simply type ‘override’ in your component class, and IntelliSense should show you a drop-down.
We can then select the method for which we want to change the default behaviour, for example:
The ‘base’ part is added automatically when we select the override from IntelliSense. The ‘base’ calls the default behaviour of that method.
We can add extra code beneath or above ‘base’. This code will be called in addition to the ‘base’ code. We can also remove the ‘base’ line, which effectively removes the default behaviour alltogether.
Having a dig through the various overrides reveals one called ExpireDownStreamObjects(). Perfect – this is the one! What I can do is remove the base from this override, so Grasshopper will no longer expire downstream objects. Then, I can use the methods above to manually expire the components that I want, when I want, during the solution.
So, I simply comment out the base, and add a few lines into my SolveInstance. This extra code finds the components I want to expire at strategic times, and asks them to expire and recalculate.
foreach (var receiver in Component.Params.Output.Recipients)
(Note: I have defined ‘Component’ to be the same as writing ‘this’.)
This does actually work in principle. But, for reasons I can only guess, Grasshopper prohibits expiring components during the solution, at least in certain cases. The result is an error message, below, saying that “an object expired during a solution”. This message can be cancelled, and the component will then work as designed, but this still presents a poor user experience.
What you SHOULD do
The solution is to bring out the logic from the ‘solveInstance’ method. The best way to do this is to be slightly more sophisticated in our ExpireDownstreamObjects override.
One way to do this is to add a conditional to the ‘base’.
protected override void ExpireDownStreamObjects()
if (_run || _running)
The above method now only allows downstream objects to be expired if the class variables _run or _running are set to true. If they’re both false, nothing will happen. Grasshopper is now much happier – no error messages, and objects are being expired as we designed them to be.
Another option is, instead of using base, we can manually expire any component we want here:
protected override void ExpireDownStreamObjects()
if (_run || _running)
The above code will now only expire the first connected component to the second output, and only if either _run or _running are true.
So in the image above, if BatchRun is not set to _run or _running, then pressing the button will not expire the panel. This is because the component has been repressed from transmitting an ‘expire’ command to the panel. Consequently, the doCalcs component will not recalculate – which was the intended behaviour.