A few questions on scripting with DataTrees

Continuing the discussion from Problems appending elements to GH_Structure at a higher path level:

This is mainly to try to understand why GH_Structure should always be used in VS rather than DataTree class as outline by David Rutten here:

I have definitely used DataTree for VS components before and I haven’t noticed any issues resulting from this, so I am curious to know what the drawbacks are.

While I am here and on the topic maybe someone can shed some light on the best practices of scripting with DataTrees. Many times I found it important that the output maintains or appends the input tree structure. My approach has always been something along the lines of the following:

Grasshopper.Kernel.IGH_Param inputParam_0 = this.Params.Input[0];
Grasshopper.Kernel.Data.IGH_Structure structure_0 = inputParam_0.VolatileData;

int pathCount_0 = structure_0.PathCount - 1;
int runCount = this.RunCount - 1;

GH_Path path = structure_0.get_Path(Math.Min(runCount, pathCount_0));

It has usually done what I needed it to do, but I’ve always felt that there must be a more robust way than using the component’s RunCount to retrieve the current path of the data in SolveInstance.

The data inside parameters is stored as GH_Structure<T>, so in some cases it means you don’t have to convert the data to another type.

The main issue with DataTree<T> is that it has no type constraint on T, which means you could have a DataTree<bool>. You can only store true or false values in such a tree, you cannot store nulls. If you were using GH_Structure<GH_Boolean> then nulls can be stored.

Not that it helps you now, but this split between GH_Structure<T:IGH_Goo> and DataTree<T> will be gone in future versions. The whole concept of ‘goo’ is gone and trees just store the data directly, with some added plumbing to cater for nulls, meta-data and so on.

I’ll have to look into this after my bike-trip… tbc.

Can you elaborate on what sort of data management you’re looking for? Like, how many input parameters, what their access levels are and what you expect to get out of that component?

Thanks for clarifying the GH_Structure makes a lot more sense now. Looking forward to those Goo changes, sounds like there will be no more need for class wrappers too?

Sure. I’ve put together a very simple example to illustrate the point.

the idea here is that the first param is item access and second access is a list. the desired output branch structure should be {param0[path]; param1[path]; param1[index]}(i)

In the file, I’ve also included a far simpler solution where both params are item access and the result returns a similar structure. Through the downside here is that the component runs much more times and this can be unnecessary and even problematic when there are computationally heavy functions called before the output data is populated.

DataTrees.gh (8.1 KB)

Well, functionality will have to be provided somehow, but instead of wrapper classes (one for each value), there will be type-assistants instead, of which only one will ever be instantiated. Then methods on those assistants will be called. This makes (a) trees a lot simpler and memory efficient and (b) easier to choose the level of support for a new type.

It’s a bit weird, because you claim in your inputs that you only case about items and lists, but then inside your function you actually care about the entire trees. This makes it a lot harder.

I’m wondering if it would be best to just be honest about it, set your input access levels to Tree and do all required iteration yourself. More code, but it will be easier to make it reliable.

You certainly should not use Component.RunCount because that was for the previous solution, not this one right now. So it’ll often be incorrect.

I wonder if you can get away with defining your own counter. It doesn’t tell you how often the RunScript() method will be called, but it may be enough information nevertheless…

private void RunScript(int x, List<int> y, ref object A)
{
  _runCount++; // up the local variable.
  
  var input0 = Component.Params.Input[0];
  var tree0 = input0.VolatileData;
  int pathCount0 = tree0.PathCount - 1;

  var input1 = Component.Params.Input[1];
  var tree1 = input1.VolatileData;
  int pathCount1 = tree1.PathCount - 1;

  int runCount = Component.RunCount - 1; // this is a bug.

  var primary = tree0.get_Path(Math.Min(runCount, pathCount0));
  var indices = tree1.get_Path(Math.Min(runCount, pathCount1));

  foreach(int index in indices.Indices)
    primary = primary.AppendElement(index);

  var tree = new DataTree<int>();
  for(int i = 0; i < y.Count; i++)
  {
    int count = 0;
    var path = primary.AppendElement(i);
    while (count < y[i])
    {
      tree.Add(count, path);
      count++;
    }
  }
  A = tree;
}

// <Custom additional code> 
// Define your own counter.
private int _runCount;
public override void BeforeRunScript()
{ 
  _runCount = -1;
}
// </Custom additional code> 

I see that makes a lot of sense actually especially because the first input will always need to be grafted anyway. Otherwise you will have some sort of cross reference with the list and items access.

Thanks for the clarifications.