Funny behavior of delete_attribute


#1

Running Sketchup Pro Windows 64-bit.

please read fully to understand the issue.

When I use delete_attribute to delete a key in a dictionary of an entity in our plugin , and then after a while when I add entities to the model also thru ruby code, I find spurious AttributeDictionary objects in the Sketchup.active_model.entities collection. This is causing error. Also, if I access these AttributeDictionary objects I get the sketchup bug splat. I am able to repeat the same sequence of operations and get these errors. Now here is what I did. I changed the delete_attribute method to dictionary.delete key, and viola, none of these spurious AttributeDictionary objects appear in entities collection and my plugin works perfectly without any issue.

Have been having this issue with AttributeDictionary for more than 3-4 months but only today was I able to pin it down consistently.

Question to the team is what is the difference between entity delete_attribute, and dictionary delete_key, and is it a bug or am I doing something wrong ?


#2

Test case, please, that would be nice. (I don’t think you want me spend the afternoon figuring out what you did and whether it’s wrong in the API or in the code that uses the API). The advantage of having executable test cases of your code and of unexpected behaviors/errors is that you can repeat the procedure without new effort again and again, at any time, and automatedly.


#3

The three variants are:

entity.delete_attribute(dictionary, key)

This deletes the named key in the given dictionary
If no second key argument is passed, then it deletes the whole dictionary.
The dictionary can be passed either as a reference or by its string-name.
It returns nil.

dictionary = entity.attribute_dictionaries.delete(dictionary)

This deletes the entire dictionary, by a either passing a reference to it or by its string-name.
It returns a reference to the modified entity.attribute_dictionaries object,

value = entity.attribute_dictionary.delete_key(key)

This deletes the named key in the referenced dictionary - much the same as setting its value to nil.
However, this way should return the previous value of the key it has just deleted.


So as you can see each does a subtly different thing...

#4

The challenge with getting a test case out to you folks is, this happens in a sequence of operation in a plugin for solar design, which is an internal tool in the company I work. There is quite a bit of code in multiple operations which leads to this. I could potentially do a screencam and send it, but that may not help.
Give me a couple of days, and let me see if I can write a simpler code and send it.
However, one question which struck me after reading your and TIG’s response is, does entity.delete_attribute trigger a entity observer which messes up the entities while dictionary.delete_key does not trigger any observer call. While my plugins have a few observers, I do not suspect that could be the culprit. But are there other plugins/extensions which could potentially cause ent.delete_attribute to corrupt the entity collection. To answer TIG, I used ent.delete_attribute with a key always.

One more thing I want to add to my original problem. The problem occurs in a sequence as follows…

  1. Create a set of faces/objects and do a bunch of set_attributes to these and associated faces
  2. user clicks on undo in my plugin, i remove all created faces, and call delete_attribute on associated faces
  3. User does step 1 again. That is when the issue happens. The entities collection seems to have a bunch of dictionaryobjects.

#5

Have you heard about [MCVE][1]? True, coming up with a good test case is can be a good deal of work, but that work is well invested in approaching the solution.

You don’t even have to extract it from your code base (that would imply you know it only happens in combination with the other operations of your tool).

As an alternative to this analytical approach, you can also go the synthetical approach. Your hypothesis was the API method could behave incorrectly. You could confirm this by building up a short test case that comprises all that your hypothesis requires (delete an attribute from an entity and check all items in model.active_entities).
This way you can also try out whether the respective API method triggers observers or not: Add an entity observer to an entity, delete an attribute from the entity, check whether the observer triggered.
[1]: http://stackoverflow.com/help/mcve


#6

Just for general FYI: In the API under Entity#set_attribute

Note, a bug prior to SketchUp 2015 would corrupt the model if the key is an empty string. This also includes values that will evaluate to empty strings, such as nil.


Is the above underlined part, a statement, or a question ?

It also seems to ignore the fact that both the AttributeDictionaries and AttributeDictionary classes, are sub-classes of Sketchup::Entity and should trigger callbacks in an entity observer, if such an observer is attached to them, (like any other entity.)


#7

If your code is wrapped within a model.start_operation ... model.commit_operation block SketchUp handles doing the “undo” which includes removing the dictionaries or new key/value pairs, or any value changes.

There is also a safer operation library available at the SketchUp GitHub repo:
https://github.com/SketchUp/sketchup-safe-observer-events


#8

This sounds like a bug in SketchUp we’ve recently become aware of. A few months ago I implemented a Ruby Error Reporting tool in some of my extensions and I noticed a number very odd errors. For instance when calling face.edges I’d iterate the returned collection assuming they’d all be edges I would some times get errors saying that I tried to call a missing method on an AttributeDictionary or OptionProvider or some other unexpected class.

I wasn’t able to reproduce this however, so it was very puzzling to see these errors trickle in.

But recently we came across something in our API layer code which might explain it. There are some cases where after erasing an entity our internal object manager which maps C++ objects to Ruby objects gets confused and servers the wrong Ruby wrapper. From what we can tell this has existed since the dawn of the Ruby API. :frowning:

But since it’s so hard to reproduce it’s eluded us for all this time. (This could explain a good number of “odd” error reports I’ve had from my extension users through up the years. Reports of one-off errors the users and I could never reproduce.)

We got various ways to reproduce this internally now - but it always involve a loop of creating and deleting entities in various particular ways. It doesn’t manifest itself immediately because it depend on memory allocation.

What’s more concerning is that when you see a Ruby error because you get a different Ruby class than you expected - that’s probably the best case scenario. There is potential here that two entities of the same class gets mixed up - when then you don’t get any TypeError thrown at you - instead the method will blindly be invoked on a completely different entity.

Just to be clear - this is not limited to delete_attribute and it’s not this method itself that produce this. It’s just one of the circumstances that might trigger it.

I’m afraid there is no workaround for this. Not even anything extension developers can do to avoid it.

We’re actively tracking down this issue looking for a solution.


#9

This reminds me of an old bug where Loop objects would appear in an entities collection after exploding a group. I have not seen it in a long time, but maybe that is because the best practice is now to grep only the types we are interested in processing.


#10

Yea, I recalled that as well. Though in my testing I never saw these random types. Maybe that was an unrelated issue? Wasn’t that consistent every time one exploded?


#11

This is great. I was beginning to suspect my plugin code. Would love to hear when you get a fix for it.


#12

Yea, it’d be good to get feedback from developers who have managed to pint point this behaviour - as fixing this is very tricky,