Skip to content

GH_Component execution logic

Working outside of the standardized component workflow, such as data damming, async..etc, is quite complicated, and IMO not very well documented. Here I attempt to document the process execution flow for future reference.

A component can be considered an object with input, solution, and output processes, each can have their own logics, a breif overview of the process looks like this:

Component recieves expire request
🡫
Component expires, and expires downstream
🡫
Collects upsteam data
🡫
Runs and finishes solution
🡫
Passes data to downstream
🡫
downstream starts their solutions

CheatSheet

*Input param excluded

Upstream State Component Phase Component Executing Component Output Param Downstream Phase
Starting new solution Computed - - Computed
In solution Collecting ExpireDownStreamObjects() ExpireSolution()
Clears data
Collecting
Computed Collected BeforeSolveInstance() - "
" Collected SolveInstance()
Executed multiple times if passing list to GH_ParamAccess.item
- "
" Collected - CollectData()
Applying Graft, Flatten...etc
"
" Collected AfterSolveInstance() - "
" Computed - - Collected

Damming Output

To dam an ouput, the SolveInstance would basically have two sections of code, the compute section and output section. By default execut the compute section, and when an ouput is required, expire the solution to run the ouput section. All data that needs outputing would have to be a persistant variable.

Pseudocode:

string data = ""
bool output = false;
SolveInstance(DA)
{
  if(output)
  {
    DA.Set(0,data);
    output = false;
  }
  else
  {
    // some compute logic...
    data = computedData
    if(outputRequired)
    {
      output = true;
      //OnPingDocument().ScheduleSolution(5);
      ExpireSolution(true);
    }
  }
}

ScheduleSolution creates expires the solution and queues a new solution after x milliseconds. In theory it is thread-safe, but may be unpredictable, since there can only be one queue at any given time, say you queue for 100ms later, but another component created a queue for 1ms at the same time, the both solutions are calculated after 1ms. ExpireSolution expire the solution and recalculates immediately if true; false still expires, but doesn't request a recompute nor does it queue. If this is called while in the middle of a solution, exceptions will be thrown, it should only be called at the end of some compute logic. Functionally speaking for data damming, ScheduleSolution and ExpireSolution is basically the same. This however will still output data when output is false, it'll be null if you didn't set any data. for most usages this is probably not desirable, to prevent the output of null when output isn't set, there's 2 methods:

Method 1: Disable Gap Logic

One options if to use DisableGapLogic() in the SolveInstance(), this prevent the component from outputting null's, and treats the output as not valid/empty (as if the component didn't execute).

Pseudocode:

string data = ""
bool output = false;
SolveInstance(DA)
{
  DA.DisableGapLogic();
  if(output)
  {
    DA.Set(0,data);
    output = false;
  }
  else
  {
    // some compute logic...
    data = computedData
    if(outputRequired)
    {
      output = true;
      //OnPingDocument().ScheduleSolution(5);
      ExpireSolution(true);
    }
  }
}

*Do note that depending on how the downstream object's logic works, it may execute despite your component not outputting anything, since ExpireDownStreamObject() is still called (This may or may not be desirable).

Method 2: Override ExpireDownStreamObjects

Another method is to not expire the downstream components, this avoid the downstream object from executing, instead of relying on that component to recognize that the input is not valid and shouldn't execute.

Pseudocode:

string data = ""
bool output = false;
SolveInstance(DA)
{
  if(output)
  {
    DA.Set(0,data);
    output = false;
  }
  else
  {
    // some compute logic...
    data = computedData
    if(outputRequired)
    {
      output = true;
      OnPingDocument().ScheduleSolution(5)
    }
  }
}

override ExpireDownStreamObjects()
{
    if (output)
    {
        base.ExpireDownStreamObjects();
    }
}

Since the clearing of data on the output param (ExpireSolution) is called by base.ExpireDownStreamObjects, by not doing anything in ExpireDownStreamObjects when an output is not required, the out params keeps their previous values despite a new solution being computed.

Async/Background Task

Another situation where data control is important is when async computation is required. Instead of locking up the UI with a complex/lengthy computation, the compute can be done on a different thread in the background asynchronously, freeing up the UI. When the compute is finished, the component can then self-expire to output the result. Async in C# is relatively simple to define:

Task.Run(() =>
{
  // do something...
});
The real challenge here is that ExpireSolution cannot be be called from a non-UI thread. To execute commands on the UI thread from a non-UI thread, InvokeOnUiThread can be used, such as:
public delegate void ExpireSolutionDelegate(bool recompute);
protected override void SolveInstance(IGH_DataAccess DA)
{
  Action callback = () => RhinoApp.InvokeOnUiThread(new ExpireSolutionDelegate(ExpireSolution),true);
  DoWork();
}
private void DoWork(Action callback)
{
  Task.Run(() =>
  {
    // do something...
    callback();
  });
}
since this delegate is extremely simple, it can be simplified. Combined with data flow control it'll look something like this:

Pseudocode:

string data = ""
bool output = false;
SolveInstance(DA)
{
  if(output)
  {
    DA.Set(0,data);
    output = false;
  }
  else
  {
    Task.Run(() =>
    {
      // do something...
      data = computedData
      if(outputRequired)
      {
        output = true;
        RhinoApp.InvokeOnUiThread((Action)delegate { ExpireSolution(true); });
      }
    });
  }
}

override ExpireDownStreamObjects()
{
    if (output)
    {
        base.ExpireDownStreamObjects();
    }
}


Side Remark:

If only select output params needs to expire, instead of calling base.ExpireDownStreamObjects which expires all the output params, a specific param can be expired using Params.Output[i].ExpireSolution(false);

Reference: Melanoplus | Idempotent
Reference: Dynamic Data Tools