Why is the first time accessing RhinoObjectSelectionEventArgs.RhinoObjects so slow?

I’m writing a RhinoCommon plugin that adds a bunch of geometry to the Rhino document. When that geometry is later selected I want to do something, so I’m adding a handler for the RhinoDoc.SelectObjects event, in which I iterate through the contents of the RhinoObjects collection. What I’ve noticed is that when I select a bunch of the geometry at once Rhino freezes for an extremely long time, but that subsequent times selecting the same geometry go much faster.

Initially I assumed something in my code was causing this, but what I eventually found was that even if I commented out all of the code inside the loop (and ran it in debug mode so the JITer didn’t optimise it out) the same thing still happened.

Here’s the relevant function in my code:

public void OnObjectsSelected(object sender, RhinoObjectSelectionEventArgs e)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    foreach (RhinoObject rObj in e.RhinoObjects)
    {
        //Commented out code...
    }
    sw.Stop();
    Core.PrintLine("Selection Time: " + sw.Elapsed);   
}

The output in the command history window, for the first time I select everything and then a second time I select the same objects again:

Selection Time: 00:01:19.6336440
39916 meshes, 13069 curves, 46939 points added to selection.
Selection Time: 00:00:00.0195766
39916 meshes, 13069 curves, 46939 points added to selection.

It goes from 80 seconds the first time to next to nothing the second time. It also seems from testing that what is important for this is whether those specific objects have been selected before. So, my questions are:

  1. Huh?
  2. Whaa-?
  3. What could be causing this?
  4. Is there a way to stop it? I’d rather the user didn’t have to sit and twiddle their thumbs for a minute and a half when they simply select some objects, especially since they are going to assume it’s my fault!

My wild guess for 3 is that RhinoObjects are being generated as-needed to wrap the pointers to the actual objects and are then cached, so that accessing the RhinoObjects property the first time is causing a whole load of them to be initialised at once and causing the slowdown, but that subsequent calls to it can re-use the same set of objects. Am I close? Even if that is the case it still seems to be incredibly slow, however - it takes much less time than that to generate all the objects in the first place.

I think you are not far off in that objects are being generated on demand when you access the objects table. I don’t know what your code is doing when it is accessing the table, but it might be faster if you use GetObjectList(ObjectEnumeratorSettings) to get the type of objects that you need. If I’m not mistaken, this performs the “query” in the native C++ code, and only creates managed wrappers for the result, not all the objects.

The ObjectEnumeratorSettings is quite configurable to return the objects you need.

See http://4.rhino3d.com/5/rhinocommon/html/M_Rhino_DocObjects_Tables_ObjectTable_GetObjectList.htm
and
http://4.rhino3d.com/5/rhinocommon/html/T_Rhino_DocObjects_ObjectEnumeratorSettings.htm

Thanks menno, I’ll check out those links and see whether they make a difference. The only thing I actually need for what I want to do is the IDs of the selected objects, so maybe there’s a way I can get those without needing to go through the RhinoObject wrappers…

Hi Paul,

Do you have some simple sample code that we can run here that will replicate the problem?

– Dale

Hi Dale, I don’t to hand, but I’ll put something together when I get the chance. I imagine it would be reproducible just using a standard C# plugin setup with the code I gave above as a handler for the SelectObjects event, but I haven’t tested that.

Unfortunately GetObjectList doesn’t solve the problem. GetObjectList itself returns very quickly, but then once I try to iterate through the resultant IEnumerable the same thing happens.

I’ve dug a bit deeper into RhinoCommon trying to understand what is going on and have been able to trace the problem back to the call to UnsafeNativeMethods.RhinoObjectArray_Get(this.m_ptr, i) in INTERNAL_RhinoObjectArray.ToArray(). I’ve been using Reflection to try to replicate some of what goes on in the RhinoObjects getter to see whether I could pull out the object UUIDs without needing to instantiate any wrappers, but the slowdown still happens and it’s invoking that specific function which seems to take the time. Here is the code I used to test that:

public void OnObjectsSelected(object sender, RhinoObjectSelectionEventArgs e)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();

    Type t = e.GetType();
    FieldInfo fI = t.GetField("m_pRhinoObjectList", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance);
    IntPtr arrPtr = (IntPtr)fI.GetValue(e);
    Core.PrintLine(arrPtr.ToString());

    Type unsafeNM = t.Assembly.GetType("UnsafeNativeMethods");
    MethodInfo countM = unsafeNM.GetMethod("RhinoObjectArray_Count", BindingFlags.NonPublic | BindingFlags.Static);
    MethodInfo getM = unsafeNM.GetMethod("RhinoObjectArray_Get", BindingFlags.NonPublic | BindingFlags.Static);
    MethodInfo getAttM = unsafeNM.GetMethod("CRhinoObject_Attributes", BindingFlags.NonPublic | BindingFlags.Static);
    MethodInfo getIDM = unsafeNM.GetMethod("ON_3dmObjectAttributes_m_uuid", BindingFlags.NonPublic | BindingFlags.Static);
    int length = (int)countM.Invoke(null, new object[] { arrPtr });
    for (int i = 0; i < length; i++)
    {
        //This next line (invoking UnsafeNativeMethods.RhinoObjectArray_Get) seems to cause the slowdown:
        IntPtr objPtr = (IntPtr)getM.Invoke(null, new object[] { arrPtr, i });
        IntPtr attPtr = (IntPtr)getAttM.Invoke(null, new object[] { objPtr });
        Guid id = (Guid)getIDM.Invoke(null, new object[] { attPtr });
    }

    sw.Stop();
    Core.PrintLine("Selection Time: " + sw.Elapsed + ", Count: " + length);   
}

So, it appears that initialising the managed RhinoObject wrapper is not the problem - the thing that is causing the slowdown is somewhere in the unmanaged code (specifically, somewhere in RhinoObjectArray_Get).

The plot thickens: I put together a simple plugin to send to you guys which simply generated a bunch of objects and subscribes to the SelectObjects event, in the handler for which I access the RhinoObjects collection. However; this didn’t seem to replicate the same problem.

So, this suggests that there’s something about the specific objects or the way that I’m creating them which is the root cause of this problem. That said, I’m not sure what that could be since I’m not doing anything too extravagant, I’m simply:

  • Creating the objects by calling AddPoint/AddLine/AddMesh/etc. on the object table.
  • Using ‘Find’ with the resultant GUID to get the relevant RhinoObject
  • Using the RhinoObject to set a user string and the object layer

I can keep going trying to replicate the problem through trial and error, but since I’ve already traced the issue back to the RhinoObjectArray_Get call if somebody at McNeel could take a look in that function and let me know whether anything jumps out as a likely culprit it could help me narrow down the search.

Who did you send this plug-in to?

Hi Dale,

Nobody yet! Like I say, it doesn’t seem to reproduce the problem so there didn’t seem to be much point!