Disabling Observers upon undo

In my extension, upon the user placing a component instance, my extension will place a Text object instance onto the active model via an EntitiesObserver subclass (1 to 1 relationship between ComponentInstance and Text instance). However, I have a bug whereby if the user deletes the component instance (which will also delete the text label via an observer), then hits undo, the old label re-appears which is good, but the EntitiesObserver will fire again upon the undo and add a second unwanted text label.

Iv’e looked into disabling the observer by inspecting the call stack and disabling if called from an undo related location, however the call stack appears to be the same when the observer is called via the normal way of the user placing something on the model (being from ‘commit_operation’) so I am unable to distinguish between the use cases. Any suggestions?

Also ‘onTransactionUndo’ is called after the bug has occurred so I can’t really make use of it.

You might “mark” the instance with your extension’s uniquely named attribute dictionary, which has an attribute holding the text callout’s persistent id. Your code that adds the text should check for the existence of the attribute dictionary and the attribute for a text object, if it’s there skip adding the text.
The callback may also need to update the id after an undo or redo.

The problem you encounter is one reason why the API docs say not to modify the model within an EntitiesObserver callback. (See Overview at top of the class doc.)

You should also have read the detailed file on the 2016 observer overhaul.
https://ruby.sketchup.com/file.ReleaseNotes.html#label-Observer+Upgrades
which points to https://assets.sketchup.com/files/ewh/Observers2016.pdf

There may be some open issues about undo operations in the API Issue Tracker.
https://github.com/SketchUp/api-issue-tracker/issues?q=is%3Aissue+is%3Aopen+undo

1 Like

Thanks @DanRathbun… of course! I didn’t think to check if the entity has pre-defined attributes in the observer or not (as I am using the attribute dictionaries already). So obvious now you’ve pointed that out! I can now distinguish between elements added via the user, and elements added from an undo operation.

Yes I’ve seen the warnings however i’m pressing on with notion of modifying the model via observers. Is modifying the model in some way in response to user action literally the crux of all extensions? I see that developers are being told not to do it, but like is that not the whole point of having this API?

I’ll have a look.

Thanks

Ben

Well the warning in the docs is an old one. Crashes caused by modifying the model dB within observer callbacks were so common that it was a major impetus for the v2016 observer overhaul.

So definitely download and read the PDF doc on that overhaul.

But think about generally, an EntitiesObserver that gets fired when changes are made, which fire the observer because itself makes changes, which fire the observer … etc., … etc. (The “vicious circle” syndrome.)


Just a note to say that although the API doc examples show the subclassing paradigm for observers, in reality all the observer class (since the 2016 overhaul) are abstract. (This means they have no functionality to pass down to subclasses. Ie, all the previously empty callback methods were removed so that the SketchUp API engine can poll observer objects to ask if they respond to each callback. If not, the engine does not call that method for that particular observer. Since method calls eat time, this speeds up the processing of observer calls.)

Also, the various add_observer attachment methods do not do any type checking to ask if the argument object is a subclass of any particular class. All the object’s callback methods need be is publicly accessible. So this means the observer object can be of any class, or it could be a module with public callback module methods, or even some instance object with dynamically defined singleton callback methods.
I myself have found that the combo-observer module paradigm to be the most useful and easiest to implement. One submodule with most of the observer callback methods all together is easier because you need not pass information back and forth so much. Many times my observer module is the extension submodule itself. (Data can be kept in local objects accessible to many callbacks that are published as being in different observer classes.)
If you will support MacOS where multiple models might be open, then you’ll need to take care to store data in a hash whose keys are model references. On Windows it’s simpler as only 1 model can be open at a time.
There still are times when instantiating an observer object for each open model makes sense. Extension code needs to track these and dispose of their references when the model is no longer open.

Thanks @DanRathbun, interesting stuff.

Iv’e got another question if you’re keen to give it a ponder… how can I distinguish between a ComponentInstance added to the model via an undo action and a ComponentInstance added to the model via the duplicate tool? As in both cases, the attribute dictionary i’m using will be populated on the ComponentInstance (whereas with the original problem I was able to distinguish between ComponentInstance added via undo or added fresh through checking if the attribute dictionary existed or not). Thanks

Well using the MoveTool with the copy modifier key (CTRL) I would think perhaps you can use
Sketchup::DefinitionObserver#onComponentInstanceAdded callback, and check what the active tool is by calling the Sketchup::Tools#active_tool_id method.

The tool id for the MoveTool is 21048.

The comprehensive list of tool IDs is in the Overview for the Sketchup::ToolsObserver class.

But hmmm… if the MoveTool happens to be the active tool during an undo … maybe more thought is required.

And for SketchUp 2015 and older ...

Oh I forgot about this …

https://github.com/SketchUp/sketchup-safe-observer-events

… perhaps it will help.

The “safe observers” was to workaround issues pre-SU202016. Prior to that versions observers would trigger in the middle in operations and if any model changes was made in the observer event it would corrupt the state of the model.

Well then please update the repo’s README to reflect SU version target(s).

Done!

1 Like