Event listening in Grasshopper: Corrupted OldState properties

I am using a LayerTable event listener in a Grasshopper plugin, and trying to access the state properties to categorize the type of event that just occurred at a more granular level than is provided by e.EventType. I am running into issues with OldState in quite a few cases, particularly when generating events from the Grasshopper side.

Let’s start with the simple to more complex:

  1. Rhino-generated + GH-generated events
    This appears to be a Rhino bug – OldState reports the “Name” of a layer in the “FullPath” field. NewState reports properly. This sample comes from a Layer move.

  2. GH-generated visibility event
    If I change the visibility of a parent layer from a GH plugin component, I get an invalid OldState. I am currently calling CommitChanges from outside SolveInstance because this was causing Memory Access Violations (Rhino 5 crashes) when called from within SolveInstance while my Layer listener component was active.

Note that:

  1. the Name is empty (causes crashes if accessed)

  2. IsValid is invalid (d’oh).

  3. For some reason, the HasUserData property actually changes during the execution of the layer listener component, changing from false to true during the course of an if-else statement. (No layers have user data at any time in this example.)

  4. Setting GH layer visibility - Case 2

Occasionally getting a corrupted user dictionary. It’s bizarre. HasUserData for NewState and OldState flip flops in the middle of execution.

The code for SetLayerVisibility:

 public class LayerTools_SetLayerVisibility : GH_Component
    {
        /// <summary>
        /// Initializes a new instance of the LayerTools_SetLayerVisibility class.
        /// </summary>
        public LayerTools_SetLayerVisibility()
          : base("Set Layer Visibility", "SetLayerVisibility",
              "Set the visibility of a Rhino layer.",
              "Squirrel", "Layer Tools")
        {
        }

        /// <summary>
        /// Registers all the input parameters for this component.
        /// </summary>
        protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
            pManager.AddBooleanParameter("Active", "A", "Set to true to change the active layer in Rhino.", GH_ParamAccess.item, false);
            pManager.AddTextParameter("Path", "P", "Full path of the layer to be activated.", GH_ParamAccess.item);
            pManager.AddBooleanParameter("Visibility", "V", "Visibility of the layer.", GH_ParamAccess.item, true);
        }

        /// <summary>
        /// Registers all the output parameters for this component.
        /// </summary>
        protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
        {
            pManager.AddIntegerParameter("Layer ID", "ID", "Index of layer that has been modified.", GH_ParamAccess.item);
            pManager.AddBooleanParameter("Status", "St", "True when the layer has been modified.", GH_ParamAccess.item);
        }


        // Set up a list of layers outside SolveInstance
        List<Layer> layersToModify = new List<Layer>();

        
        // Clear any layers from previous solve
        protected override void BeforeSolveInstance()
        {
            layersToModify.Clear();
        }

        // After solve instance, commit all the changes
        protected override void AfterSolveInstance()
        {
            foreach (Layer layer in layersToModify)
            {
                layer.CommitChanges();
            }
        }

        /// <summary>
        /// This is the method that actually does the work.
        /// </summary>
        /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param>
        protected override void SolveInstance(IGH_DataAccess DA)
        {
            bool active = false;
            string path = "";
            bool V = true;

            if (!DA.GetData(0, ref active)) return;
            if (!DA.GetData(1, ref path)) return;
            if (!DA.GetData(2, ref V)) return;

            bool status = false;
            int layer_index = -1;

            

            if (path != null)
            {
                RhinoDoc doc = RhinoDoc.ActiveDoc;
                Rhino.DocObjects.Tables.LayerTable layertable = doc.Layers;

                layer_index = layertable.FindByFullPath(path, true);

                if (active)
                {
                    if (layer_index > 0 && layer_index < layertable.Count && layer_index != layertable.CurrentLayerIndex)
                    {  // if exists

                        // Get the layer
                        Layer thisLayer = layertable[layer_index];

                        thisLayer.IsVisible = V;
                        layersToModify.Add(thisLayer);
                        // thisLayer.CommitChanges();
                        status = true;
                    }

                }
            }

            DA.SetData(0, layer_index);
            DA.SetData(1, status);
        }

Link to the original discussion about memory access violations I’ve been battling in this plugin:

I’ve recategorised this since it doesn’t have much to do with GH itself.

OK - I give in…
Is that Sweris Waddanders as in 'tKanni alted 'tzelfde zen?

In case it wasn’t clear, the corrupted states only happen when triggering a Rhino document change from within a GH plugin. So I assume that it is related to GH flow control, etc. But that’s fine, happy to hear from the Rhino side if you think it’s a Rhino issue.

Thanks,
Marc

Kfinteemaal nilökor, enksi erde lollok nifan in.

2 Likes

There’s three kinds of context that might make a difference when invoking RhinoCommon methods:

  1. Code runs from within a command. In which case a try...catch block will deal with any exceptions that you throw. You also get undo records thrown in for free this way.
  2. Code runs somewhere else on the UI thread, usually due to some sort of event. This is probably what happens when the code is part of a GH plugin.
  3. Code runs ‘out of the blue’ from a non UI-thread. This is almost always really bad if it’s code that is supposed to modify the Rhino document or Rhino UI. Think instant crashes.

I think the only relevant information here is that your code isn’t run from within a Rhino command. That it just happens to be called from a component should be irrelevant, but to be honest one never knows for sure ahead of time.

I’ll try and run the code you posted to see if I can replicate the crash. Would you happen to have a specific *.3dm file and maybe even *.gh file you use to test this?

Oops, seems the behaviour of Layers is different in Rhino6:

So in Rhino6 the BeforeSolveInstance() and AfterSolveInstance() need to be removed because CommitChanges() is obsolete. Once I change the layers directly from within the SolveInstance() method, everything works fine.

This could be a Rhino5 problem only. Rereading your original post now in case I missed something twice…

Some general event-handling advice. Rhino typically doesn’t like it if you take action from within your event handler. If an object or a layer is being modified, you will get an event, but Rhino expects you to do nothing with this information for the time being. Rather, you have to set some sort of dirty flag in your code and take action at a later point in time when whatever process is currently making changes has finished.

This is cumbersome for sure, but I didn’t see the code of your handler so I don’t know how you’re triggering GH updates from your RH event handler.