Need some help to understand why I got a Grasshopper Breakpoint

Hello World,

I need some help to figure out was it going on behind the hood, so I can debug my code properly, I would appreciate any insight.

I am pretty sure this can be categorized as a grasshopper bug, but it is also possible that I just lack the knowledge to properly apply this concept.

I got the following Grasshopper breakpoint message: Grasshopper undo recording threw an exception source parameter is not linked to target parameter.

Here is a screenshot with some details.

Here is the GH File:
SlidersModifiedBug_v1.gh (17.9 KB)

To recreate this bug just open the file and move the sliders in the “Merger Input Lenghts”, and then continue to move the sliders in the “Input: Sliders” group. The trick for triggering this bug is if you actually type a value on the slider and press “enter”, instead of dragging the slider with the mouse.

I tried to record this, but for some reason windows does not register the additional window of the Grasshopper Breakpoint (eventhough it did show on my screen when recordin), but if it help anyone understand what I mean, this is the videorecording and just follow the same steps as I do…

Another weird thing is that this bug happens more often on the sliders between 10 and 50, I don’t know if that gives any useful clues.

Anyways, I know it is a long shot, but I really wanted this functionality to ease the user interface with my grasshopper definition.

If there is anything I can do to help and make clearer the bug that I’m trying to point out (since it triggers in such weird conditions), just let me know.

Again, if anyone has any input, I would appreciate it!

Edit: Removed the DLL from the GH file.

Day 4… I really need some second opinion

The culprit is most originating from your C# component which links to a DLL on your OneDrive, I don’t think the DLL is necessary though, but I can’t be certain. What does or did this DLL do? Regardless, I can reproduce on mac quite easily with your instructions.

Full Code
using System;
using System.Collections;
using System.Collections.Generic;

using Rhino;
using Rhino.Geometry;

using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;
using Grasshopper.Kernel.Special;
using System.Linq;
using Grasshopper.Kernel.Parameters;
using System.Reflection;

/// <summary>
/// This class will be instantiated on demand by the Script component.
/// </summary>
public class Script_Instance : GH_ScriptInstance
{
  
  /// <summary>
  /// Gets the current iteration count. The first call to RunScript() is associated with Iteration==0.
  /// Any subsequent call within the same solution will increment the Iteration count.
  /// </summary>
  private readonly int Iteration;

  /// <summary>
  /// This procedure contains the user code. Input parameters are provided as regular arguments,
  /// Output parameters as ref arguments. You don't have to assign output parameters,
  /// they will have a default value.
  /// </summary>
  private void RunScript(bool Reset, List<int> Merges)
  {
    //--------------------------------------------------------------------------------------------------------------------------------------------------\\
    //---------------------------------------------------------- Code: Create Sliders for each segment -------------------------------------------------\\
    //--------------------------------------------------------------------------------------------------------------------------------------------------\\
    if (Reset || (_mergeParams.Count == 0 && _sliders.Count == 0))
    {
      // Clear Previous Data Sets
      _mergeParams.Clear();
      _sliders.Clear();

      // Set New Data Sets
      FindMergers(this.Component, this.GrasshopperDocument, ref _mergeParams);
      FindSliders(this.Component, this.GrasshopperDocument, ref _sliders);
    }

    // Schedule Solution to update Grasshopper Canvas
    this.GrasshopperDocument.ScheduleSolution(10, (GH_Document.GH_ScheduleDelegate)((d) =>
    {
      // Initializze Global Counter
      int counter = 0;

      // Connect Slider to Merger
      for (int i = 0; i < Merges.Count; i++)
      {
        IGH_Param ejeMergeParam = _mergeParams[mergeParamsBaseName + i.ToString()] as IGH_Param;
        ejeMergeParam.Sources.Clear();

        for (int j = 0; j < Merges[i]; j++)
        {
          GH_NumberSlider iSlider = _sliders[counter.ToString()] as GH_NumberSlider;

          ejeMergeParam.AddSource(iSlider, j);
          counter++;
        }
      }
    }));
  }

  // <Custom additional code> 
  // Define OuterScope Variables
  Dictionary<string, IGH_DocumentObject> _mergeParams = new Dictionary<string, IGH_DocumentObject>();
  Dictionary<string, IGH_DocumentObject> _sliders = new Dictionary<string, IGH_DocumentObject>();

  string mergeParamsBaseName = "Merge #";


  private bool FindMergers(IGH_Component ghComponent, GH_Document ghDocument , ref Dictionary<string, IGH_DocumentObject> mergeParams)
  {
    try
    {
      // Iterate over all objects in the document
      foreach (var obj in ghDocument.Objects)
      {
        // Check if the object is a group
        if (obj.GetType() == new GH_Group().GetType())
        {
          // Extract the group object
          GH_Group parentGroup = (GH_Group)obj;

          // Check if the group contains the component (verifying if the group is the correct one)
          if (parentGroup.ObjectIDs.Contains(ghComponent.InstanceGuid) && parentGroup.NickName == "Process: Mergers")
          {
            // Find mergers in Parent Group and Add to Dictionary
            foreach(Guid id in parentGroup.ObjectIDs)
            {
              var groupObj = this.GrasshopperDocument.FindObject(id, false);
              if (!(groupObj == null) && groupObj is Param_Number) mergeParams[groupObj.NickName] = groupObj;
            }
          }
        }
      }
      return true;
    }

    catch (Exception)
    {
      ghComponent.AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Failed to find Merger Components.");
      return false;
    }
  }


  private bool FindSliders(IGH_Component ghComponent, GH_Document ghDocument, ref Dictionary<string, IGH_DocumentObject> mergeParams)
  {
    try
    {
      // Iterate over all objects in the document
      foreach (var obj in ghDocument.Objects)
      {
        // Check if the object is a group
        if (obj.GetType() == new GH_Group().GetType())
        {
          // Extract the group object
          GH_Group parentGroup = (GH_Group)obj;

          // Check if the group contains the component (verifying if the group is the correct one)
          if (parentGroup.NickName == "Input: Sliders")
          {
            // Find mergers in Parent Group and Add to Dictionary
            foreach (Guid id in parentGroup.ObjectIDs)
            {
              var groupObj = this.GrasshopperDocument.FindObject(id, false);
              if (!(groupObj == null) && groupObj is GH_NumberSlider) mergeParams[groupObj.NickName] = groupObj;
            }
          }
        }
      }
      return true;
    }
    catch (Exception)
    {
      ghComponent.AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Failed to find Merger Components.");
      return false;
    }
  }
  // </Custom additional code> 
}

I think likely the code that causes the bug is here, and the way you are connecting elements is failing somehow. I would try using the new C# Script editor, using breakpoints and checking to see which line causes it to fail.

IGH_Param ejeMergeParam = _mergeParams[mergeParamsBaseName + i.ToString()] as IGH_Param;
ejeMergeParam.Sources.Clear();

for (int j = 0; j < Merges[i]; j++)
{
  GH_NumberSlider iSlider = _sliders[counter.ToString()] as GH_NumberSlider;

  ejeMergeParam.AddSource(iSlider, j);
  counter++;
}
1 Like

Hi Callum, thanks for the reply!

Didn’t notice the DLL was there still, but it is part of the main project I was building, for this example it is not necessary. It just looks like I forgot to un-reference it, after simplifying and partitioning the code.

Also, you are right, it does not trigger the breakpoint without the DLL, which is weird, since we are not really using it at all for this example.

Let me see how this applies to my main code, and I’ll get back to you with the results.

Thanks for the help, I appreciate it!

That is quite strange and surprises me. But if it works it works.

1 Like

Yeah! In a way that solves the issue, but I do need to use the DLL. For my main project, which I’m still stuck with.

It is just weird. I have practically deleted the C# after the bug is presented, and the slider looks like it has been infected (I don’t know how else to describe it LOL).

What I mean by cursed is that it still generated breakpoints, even without the c# in the file. This points to something wrong with the slider itself (after being modified by the C# component). Even if I disconnect the slider to everything and delete everything else from the code, the breakpoint behavior persists.

I’ll update if I have more findings, but this is where I’m at currently.

My first thought was that an event was causing it, but I don’t see any. If you registered an event for example, you register an action to happen somewhere, even if you then delete your C# component, which will definitely cause some exceptions.

There are other ways you can “infect” a running application and still have problems happen after your code is gone.

If you open the script, without the C# component and move things / change the text do you still run into issues?

If I open the script without the C# I can modify the values from all the sliders, without running into any issues.

The issue most definitely comes from the C#, I have a hunch that it has something to do with code line 91, where I connect the sliders into the params.

I would also avoid caching the Document objects if you can in a dict, saving GUIDs and performing a lookup is much safer.

1 Like

Most definitely I can modify that, thanks for the tip.

Another weird behavior is that I can’t delete these “infected” sliders. Not sure if this rings any bell, but it seems like a clue.

Hi,

I can’t run the code, but as already noticed by Callum, you do cache your sliders. Now, there is one thing to understand. If you run a script, the script is being compiled as an in-memory dll. So for the entire Rhino session, it remains in memory. If you cache sliders in a static dictionary you create a typical C# memory leak. The GC does not collect the sliders whenever they are still in use. So the sliders remain uncollected. This explains why you cannot properly delete them. Static dictionaries are evil. Note, if you change a single character (even a whitespace) in your code, you recompile the script. Suddenly you have two of them. Dll unloading is a big pain in C#/Net. You should always assume it does not work correctly. Even in modern Net Core.Therefore lookup tables as also mentioned are one way of dealing it, or you fire and forget if feasible…There is also something called WeakReference of T, which allows to collect instances, even if they are stuck in a static dictionary.

Edit : I noticed its not a static dictionary, but the effect could be the same if the Script instance is kept alive long enough…

1 Like

On a side note: One can simplify things substantially by replacing many sliders by one or more gene pools. They are simple to script as well, which is great if you want to add/remove “sliders” when the design topology changes for instance, or reset the values to a default set, or update the range etc. Here’s an old post with some code:

And an even older video example:

1 Like

Thanks for the explanation, Tom. This really helped me understand the “whys” of what @CallumSykes pointed out.

1 Like

Thanks Anders! Apparently, using sliders is non-negotiable, but definitely see why using gene pools would simplify the script a lot.

1 Like

It seems like I actually find the culprit, and it does not seem to be the DLL, but rather using the correct method for clearing sources from the number parameters to which I was connecting everything. The correct method is to use the RemoveAllSources() method instead of using Sourcer.Clear(), which I believed was the native way to perform such action, but realized it is just standard functionality for collections.

Thanks to everyone for the help and tips!

Here is the code with fixes (hope it helps someone at some point :stuck_out_tongue:):
SlidersModifiedBug_v2.gh (16.1 KB)

1 Like

Just out or curiosity, did they disclose why? Seems like an odd requirement. Unless they are to be published to the Remote Control Panel, which would be pretty unwieldy with that many sliders. Anywho, glad you found the issue :slight_smile:

1 Like

The argument was that gene pools do not have the capability of double clicking and typing the desired value. It’s more of a luxury, than a necessity from my perspective.

Ah yes, that’s true, and very reasonable. A feature, that along with not being publishable to the RCP, has been sorely missing from the GenePool for over a decade. One day :older_man:

1 Like

What is the RCP? Nvmind, just google it…

1 Like