Unexpected updating behavior passing classes between components

Edit after solved: This was a lot of words to describe a problem that stemmed from-

  • Passing classes around as generic parameters instead of implementing GH_Goo/GH_Param.
  • Not completely understanding how class references work.

I was basically appending data to an upstream class because I thought a reference was actually a whole new copy. (It used new, but just passed a reference into the constructor.)


I’ve run into an issue with data “persisting” when the input for an upstream component is changed. It only happens when the data is stored in a class and passed as a generic parameter (instead of passed directly as a curve, point, etc.). If I force a recompute the program runs correctly with the “real” input, but until then every change is “stored” somehow.

I managed to put together a minimal (smaller than the actual, but still a handful of code) reproduction here, if you want to take a closer look:
https://github.com/cdriesler/ghBug

And here’s a quick step through. Data in a class is enclosed in these <>.


Packager takes the rhino geometry and makes a new instance of GeometryFormat <G>.
The class contains a list of curves and a list of points.
Operator takes <G> and counts how many points are contained in a curve.
Adjuster takes curves and makes a new instance of AdjustmentFormat <A>


Merger takes the old <G> with <A> and outputs a new GeometryFormat that contains both curves.
This new <G> is run into another copy of the Operator component and outputs the right amount.


If I move the curve being fed into Adjuster, the final count is updated but includes the 12 points previously counted. I don’t think this is an issue with the counter variable because it’s set to 0 at the start of Operator’s SolveInstance. And because…


If I move the rectangle back down, it will not re-count the points included in step 2.


And last, if I tell grasshopper to recompute, I get the expected result.

In short, if I make changes to the Adjuster component, grasshopper will solve as if it had a copy of every input since the last recompute. I’d like it to only work with the current input.

Am stumped on what’s going on because I’ve been passing classes around in this project for a while now. One new thing here is the repeat use of the operator component, though. If this has something to do with how grasshopper is handling data, is there a way to tell it to “fully recompute” each time?

Thanks for any tips.

I admit I’m tired today, but your question takes a lot of effort to understand. So let me throw a triple without looking in the basket, and if not, someone else can help you better.

You should create a GH_Goo< T> wrapper for your data and a parameter to receive it. That’s how you get into the GH controlled protocol. The point is that GH_Goo or GH_Param (I don’t remember now), forces you to create a method that duplicates the data. So when a component with this parameter receives this data, it duplicates it, to become independent from the source instance, so that each component works with its own instance of that class, and there are no strange problems like the ones you describe.

I think you can still go the easy way and still use a generic parameter, but you have to make sure that each component has a separate instance of your class (including the data they contain of course). Your class GeometryFormat should have a Duplicate() method and call it right after of DA.GetData() to use that new instance in that SolveInstance.

Thanks for the advice, sorry for the word soup. (Still learning the jargon, made some edits, hopefully more clear.)

I was actually avoiding GH_Goo because I didn’t grasp all the duplication/what needed duplication + things still so far worked without it. And here we are I guess.

One clarification: when you say GeometryFormat should have a Duplicate() method, how/is that different than calling something like new GeometryFormat() ? I couldn’t find any examples searching around.

It’s a bit if code to read and understand, but if I understand your code correctly, you are creating one GeometryFormat instance in the beginning, which is passed downstream, so all the components always refer to the same instance of your class, correct?

When you change something in the Adjuster component, everything downstream gets expired, i.e. you are adding a new curve to the curve list contained in GeometryFormat. The creation of the GeometryFormat instance does not get expired, thus not reset.

If you recompute the entire solution, then the GeometryFormat gets newly constructed, thus the list with curves reset.

Basically the logic of Grasshopper is to always create light duplicates of data each time things are plugged together. Thus the recommendation to work with GH_Goo and GH_Param when passing custom classes between components. Don’t know what other issues can potentially appear, when bypassing this.

I think triggering a full recompute of the document inside a component will create an infinite loop (component gets solved, everything gets recomputed, so component solved again, etc.)

1 Like

Yes, that’s what I’m trying to do. Thanks for explaining how it breaks down so clearly.

I think you’re right about the recompute loop too, unfortunately. Will try to wrap my head around GH_Goo + the duplication logic. If/when I confirm that’s what’s going on here, I’ll make the necessary changes to the repo.

Well I guess technically you could also just create a copy of your GeometryFormat in the Merger and pass that further on downstream. However, I would say you have to make sure to make a deep copy (i.e. at least create a copy of the list, if not also a copy of the items contained in that list).
But basically this seems like some kind of workaround to a problem that shouldn’t be there in the first place. Implementing GH_Goo and GH_Param will force you to take care of this once (when overriding the Duplicate() methods), but will safe you from those problems when actually creating the components.

The deep copy workaround you mention is what I just finished implementing as a stopgap (facing a deadline). I used the top answer here.

But I agree it’s just solving a problem I made for myself. Will still return to the repo with the GH_Goo/GH_Param implementation when solved. Thanks again for helping me make sense of this.

If class “Wrap” contains a Curve type inside,

class Wrap {
    Curve Crv;
    public Wrap(Curve C){ Crv = C; }
}

and:

Curve crv = Curve.Create(...);
Wrap A = new Wrap(crv);
Wrap B = new Wrap(crv);

A and B are diferent instances, but the Wrap.Crv in A and B are the same instance. I think that’s what’s going on here, you have to duplicate Wrap.Crv, so that A and B really are completely different instances:

Curve crv = Curve.Create(...);
Wrap A = new Wrap(crv);
Wrap B = new Wrap(crv.DuplicateCurve());
1 Like

Got it, thanks for taking the time to explain.