Persistent observer wrapper

I’ve tinkered a bit with a wrapper for add_observer and remove_observer that automatically attaches the observers when a new model is loaded. It basically encapsulates the boilerplate AppObserver so you don’t need to repeat it all over the time.

Here’s a small example. The onSelectionBulkChange method should be called whenever the selection changes, even after a new model has been loaded.

class SelectionInspector < Sketchup::SelectionObserver
  def onSelectionBulkChange(selection)
    puts "onSelectionBulkChange: #{selection}"
  end
end

unless @loaded
  @laoded = true
  PersistentNotifier.add_observer(SelectionInspector.new)
end

It’s mostly a proof of concept and hasn’t been used in any of my extensions (yet). What do you think of this?

1 Like

Cool idea!

  • It is required that observers inherit from the API observer classes (interfaces).
  • It is also fixed which observer classes can be added to the model’s subelements (guess_subject model, selection, pages etc.).

I have a similar need in AttributeInspector (but very limited to my use case). I forward the different events and notify a single “observer” object.


I once had the thought one could define nesting paths to address to which element an observer should be added, even when the parent(s) (the active model) is subject to change. Like delegated event handlers in jQuery (but with the SketchUp app as the bound/root element):

PersistentObserver.add_observer(target_object_path, observer_object)
# e.g.:
PersistentObserver.add_observer("active_model.selection", my_selection_observer)
# or
PersistentObserver.add_observer("active_model.selection.first", my_entity_observer)

But it would definitely be a bit overengineering as long as I don’t use deeply nested target objects (anyways difficult no native event bubbling). It would be cool if the SketchUp API could support event delegation to avoid listening parent elements just in order to setup observers on their children. At list for AppObserver and active_model.


(@laoded@loaded)

It is and it’s used to determine what kind of object to listen to.

I think I’ve read somewhere that SU allows observers to not inherit from the observer classes except for one rare case where it is used to distinguish observers with similar callbacks, but I can’t remember where. Also the API docs (currently) says observers should be inherited from the API observer classes, so I think it’s safest to adhere to that.

It is, and it’s limited to objects there are only one of in each model (pages, selection, model). This was my solution to knowing what object to listen to.

It would be nice though to let the same “observer” observer multiple things. For instance, typically when listening to selection change you also want to listen to model change too, as it swaps the relevant selection.

Maybe there could be an additional argument for observer class(es), and the wrapper could create new observer objects on the fly and delegate them to the “observer” you supply in the first argument. This would allow a single object to function as multiple observers and reduce a lot of boilerplate.

class MyObservantClass
  def onOpenModel(model); end
  def onSelectionBulkChange(selection); end
  # etc...
end

PersistentNotifier.add_observer(
  MyObservantClass.new,
  [Sketchup::ModelObserver, Sketchup::SelectionObserver]
)

I’ve thought a bit about passing passing a block that is called e.g. when a model is loaded and that the return value from is used to specify what to observer, but I’m not sure how to design it. Maybe something like this?

PersistentNotifier.add_observer(
  MyObservantClass.new,
  Sketchup::EntitiesObserver,
  [
    [Sketchup::ModelObserver, [:onOpenModel, :onOpenModel, :onActivePathChanged], Proc.new { |m| m.active_entities } ]
  ]
)

Can we have a link (or quote) ?


IE, when the observers were overhauled, all the empty callback methods in the API observer superclasses were removed. (This made them nothing but Object clones.)

This was done so that the SketchUp engine could first poll “added” (or attached) objects which “act” as observers, for specific existing callback methods prior to calling them. (Or prior to setting up callback calling queues.)

This polling has the effect (according to Thomas) of greatly speeding things up as polling first seems to be faster than the old way of calling all callback methods even when they have not been overridden in custom subclasses, which then need to get passed on up the inheritance chain only to execute empty methods in the old superclasses.

So the effect is since the change, that API observers are pseudo-abstract.

Add to this the fact that the #add and #remove methods do no type checking at all, allowing the use of any kind of object to act as a repository for observer callbacks.
This allows the creation of combo or hybrid observers (or as some call them meta-observers) that contain callbacks for multiple uses. (Ie, combining say AppObserver and ModelObserver callbacks in one class.)
It also allows (as I’ve come to appreciate,) that singleton objects such as the plugin module itself can serve as the AppObserver in most cases. This greatly simplifies things as the plugin doesn’t need to pass state back and forth between the module and a singleton class instance. (Please remember that “a module” is an instance of class Module.)

2 Likes

Just taking the EntitiesObserver as an example, I interpret this sentence along with the example as we are supposed to subclass the API class.

To implement this observer, create a Ruby class of this type, override the desired methods, and add an instance of the observer to the objects of interests.

However I said I had a vague memory that SketchUp no longer care for the class of the observer.

It would be good if the documentation could include hybrid observers, if they are supposed to work even in the future.

1 Like

Okay that is a good example of the outdated docs. And BTW, the 2nd phrase should have always said “create a Ruby subclass of this type, …”. And as we’ve all noticed all the observer use examples showing this subclassing and instantiation attachment (which we can agree is tedious.)

All this observer documentation predated the observer overhaul which occurred with the SU 2016M0 cycle.

(I think someone has said that observers made their debut in version 6 ?)

Well I’ve been using them. And I know Jim Foltz used them for years.

Regardless, Observers could use a tutorial file(s) for themselves added to the File List page of the API.

1 Like