How Many Observers?
-
Is there any concern about having too many Observers on a model? Will it have an effect on performance at some point?
-
It has to have an effect given each instance of observer has to be dispatched. I'd be wary of going crazy with them.
My 0.02 worth.
Adam
-
I've had some concerns about this. For Smustard scripts, I've started creating a few globals containing observer instances, and those globals can have observer actions added to them dynamically. This limits the number of observers to one of each type, and still allows multiple actions to be observed.
So far, so good. It would be nice if the SketchUp folks would handle this for us, but if we could all agree on a format, we could just get it done.
-
@rickw said:
I've had some concerns about this. For Smustard scripts, I've started creating a few globals containing observer instances, and those globals can have observer actions added to them dynamically. This limits the number of observers to one of each type, and still allows multiple actions to be observed.
So far, so good. It would be nice if the SketchUp folks would handle this for us, but if we could all agree on a format, we could just get it done.
I'm with you. One of the reasons I asked was that, for most observers, there need only be one per model. A model really only needs one: AppObserver, DefinitionsObserver, LayersObserver, MaterialsObserver, ModelObserver, PagesObserver, RenderingOptionsObserver, SelectionObserver, ShadowInfoObserver, ToolsObserver, and possibly ViewObserver.
But I'm not sure the best way to implement them. I have been experimenting with using the Ruby Singleton class.
require "singleton" class AppObserver < Sketchup;;AppObserver include Singleton def onNewModel(*args) # ... end end
This ensures there will only be one instance of AppObserver. You can't call "new" on it because new is made private (raises an exception.) You access it by calling "instance" on it.
Sketchup.add_observer(AppObserver.instance) # later that day... Sketchup.remove_observer(AppObserver.instance)
Both the above calls access the one and only instance of AppObserver. It has the advantage of not needing to create a global variable to store it.
Then, any plugin that wanted to use it could just require it, and start using it.
# Some Plugin require "app_observer.rb"
# app_observer.rb require "singleton" class AppObserver < Sketchup;;AppObserver include Singleton def attach Sketchup.add_observer self end def detach Sketchup.remove_observer self end end unless file_loaded? "app_observer.rb" AppObserver.attach file_loaded "app_observer.rb" end
Sorry to go on so long about it. And I still haven't addressed how a plugin might interact with a singleton observer. Thoughts?
-
An instance variable (or perhaps a class variable?) would contain an array of commands that are eval-ed in the observer definition. These commands could be added to the observer by other scritps, and the commands would call those scripts when the observer is fired.
require "singleton" class AppObserver < Sketchup;;AppObserver @@Commands = [] include Singleton def onNewModel(*args) @@Commands.each{|command| eval command} end def addCommand(*args) @@Commands<<args if args.class==String end end
I know the 'eval' command gets a bad rap, but if someone is going to trust a script enough to download it, then 'eval' shouldn't be any worry, either, since the whole script gets 'eval'-ed anyway.
Obviously, the above example is pretty generic. There should probably be class/instance variables for each possible reactor in an observer, so that a script can reference the relevant actions individually. There should also be error-trapping (begin-rescue-end) and all that.
-
Another thought: while an AppObserver is model-independent (and is valid for the whole modeling session), all the others are model-dependent, and need to be recreated when a new model is started or a file is opened. Even though the observer instances are themselves still valid, the things they observe are gone, and thus nothing triggers them.
So, then, what are the ramifications of using a Singleton for model-specific observers?
-
I think this is moving in the right direction. I thought of 2 possibilities, one being similar to what you have shown. I don't think eval is what you want. I think the thing to do is use a Proc object, or code block; which you then call, or yield (not sure.) I don't know enough about using Procs, or blocks to know if this is the way to go.
But even so, how can you un-add an added command?
The second idea leaves all the details up to the plugin. A plugin would register itself to the AppObserver, and then AppObserver would call the onNewModel method of the registered plugin. Example:
class AppObserver < Sketchup;;AppObserver @@callees = [] include Singleton def register(object) @@callees << object end def onNewModel(*args) @@callees.each{|callee| callee.onNewModel(args) } end end
This also makes it easy to remove an object from the AppObserver. So if a plugin would need to only need:
# The Ultimate Plugin require "app_observer.rb" class Plugin AppObserver.instance.register(self) def onNewModel # What to do when a new model happens end end
Now, this could really slow thing down if someones plugin mis-behaves, and is called before yours. Could, or should Threads be used in a case like this?
def onNewModel(*args) @@callees.each{|callee| Thread.new { callee.onNewModel(args) } } end
-
I don't know if there are specific advantages to either threads or begin-rescue-end. b-r-e should work fine, calling things in order then moving to the next, even if an error occurs. I suppose that the 'rescue' section could even lock down and block the trouble-causing item from being called the next time the observer is fired.
You would probably want b-r-e even with threading...
-
I'm getting back to thinking about a single AppObserver that everyone can (hopefully) agree on and use. I want to focus on how to use this observer over how it is implemented, i.e. its interface. In that vein, what about a simple interface as follows?
require 'sketchup' require 'sk/app_observer' module SelectToolAtStartup def self.selectSelectTool Sketchup.send_action "selectSelectionTool;" end end Sk;;AppObserver.instance.register(;onNewModel) { SelectToolAtStartup.selectSelectTool } Sk;;AppObserver.instance.register(;onOpenModel) { Sketchup.send_action "selectSelectionTool;" }
There would be one and only one AppObserver. The Observer transparently attaches itself when a method is registered, and removes itself when all methods have been unregistered.
So any plugin which needs an AppObserver only need require it, and register a method.
This same type of interface could be used for all the other "single instance" observers available in SketchUp. I'd like to go ahead and code them all up so plugin devs can start using observers, instead of everyone implementing them individually. I think a library of standard observers would greatly enhance a lot of current plugins, and make writing new plugins with advanced features easier and quicker. But being an amateur, I'd like to hear from more experienced devs on the matter.
Advertisement