Tool <> WebDialog <> Observers relationship
-
Hi,
I have noticed that when those three elements: SU Tool , WebDialog & Observer stop to exist it is extremely important to make it in a proper manner.
For example: a Tool is created, it opens a WebDialog and triggers a created earlier Observer. The Observer controls some elements in the Webdialog. The funny part starts when the Webdialog or the Tool is being closed. I presume the right order of closing the Tool would be first to deactivate the Observer, then close the WebDialog and then set active tool to nil or bring a previous tool from a stack.
What is in your experience the safest way to combine those three? I have noticed when it is done in wrong way can produce a bug-splat, a freeze or a zombie SketchUp instance (SU not closed properly).
Tomasz
-
assume that myToolsObs is the predefined observer object:
myDlg = UI;;WebDialog.new( ...parameters...) #... callback methods ... set_html ... myDlg.set_on_close do toolset = Sketchup.active_model.tools; toolset.remove_observer( myToolsObs ) # do other pre-close tasks # such as save data from WebDialog Sketchup.active_model.select_tool(nil) end #set_on_close myDlg.show
-
I have done it other way round. I was keeping a WebDialog as an instance variable in my Tool class. Now when the WebDialog governs the Tool it works much better. Thanks Dan!
What if I a tool is being cancelled. I want to close the associated myDlg with the
onCancel(reason,view)
method in the tool. But if I will do that I will triger on_close in the webdialog which will destroy the tool. Am I right?class MyTool def initialize(myDlg) @dlg=myDlg end def onCancel(reason,view) @dlg.close #Will not return here because the tool doesn't exits now end
-
@unknownuser said:
I have done it other way round. I was keeping a WebDialog as an instance variable in my Tool class. Now when the WebDialog governs the Tool it works much better. Thanks Dan!
What if I a tool is being cancelled. I want to close the associated myDlg with the
onCancel(reason,view)
method in the tool. But if I will do that I will triger on_close in the webdialog which will destroy the tool. Am I right?> class MyTool > > def initialize(myDlg) > @dlg=myDlg > end > > def onCancel(reason,view) > @dlg.close > #Will not return here because the tool doesn't exits now > end > > >
Have a read of this link I posted last year regarding WebDialogs and Tools.
-
-
@adamb said:
Have a read of this link I posted last year regarding WebDialogs and Tools.
Thank you Adam. I would never figure it out on my own. I bet it doesn't happen in SU6, because it has never frozen using exactly same script. I knew something was wrong, but couldn't figure out why!
Do you find it better to have WD inside SU tool, rather then WD controlling the tool?
-
@unknownuser said:
What if I a tool is being cancelled. I want to close the associated myDlg with the
onCancel(reason,view)
method in the tool. But if I will do that I will triger on_close in the webdialog which will destroy the tool. Am I right?NO. Just because you stop using a Tool, does not cause the Tool object to be destroyed.
Tools are meant to be re-used. The next time the user clicks a toolbar button (or menu item,) for your tool, Sketchup will call the Tool's activate method. This means that the Tool class object is still defined and should remain defined. If you purposely cause Garbage collection on the Tool object, and Sketchup trys to call it's activate method, either an Ruby exception will be raised, or worse, a Bug Splat!I would always advise, making a reference (variable) that points to: Tool instances, WebDialog instances, Command instances, etc. that persist, and can be accessed again. You can put these references in a Set class object, or a Hash.
I suggest using a Hash structure called $PLUGIN which is a global. Your plugin (and other plugins,) then create sub-Hashes inside $PLUGIN, and use a "short-name" for the specific plugin as a keyname.
Within the sub-hash, who's keyname is your plugin "shortname", you can save any kind of settings or data, and most important, refernces (aka variable names,) to important things your plugins "owns" such as menu items, command objects, webDialog objects, tool objects, etc. Then anytime after creation, you could access those objects, say to dynamically change the tooltip for a toolbar button assigned to a command object.
(I actually have modified the extension.rb file [SketchupExtension class definition,] that automates the use of a $PLUGIN hash.)The reason, I am talking about this is, I have seen people create WebDialogs, and Tools, without assigned a reference to the object. The way I understand Garbage Collection is 'supposed' to work, is that if an object has a reference pointing at it, Ruby 'marks' it as 'in use'. When no references are pointing at an object, Ruby will 'unmark' the object, making the object eligible to be 'swept up' by Garbage Collection.
So when I want an object to stay around, I make sure I assign a reference to it, that will persist (an instance ref, a class ref, and last resort global.)
When you hear people say "To get rid of an object, set it to nil", they are wrong. You cannot set an object to nil. You can only set a reference (which may be one of several,) that is pointing at the object in question, to point at at different object. In this case, the new object chosen (by tradition,) is the nil object (ie: the singleton instance of the NilClass class.) -
@dan rathbun said:
Tools are meant to be re-used.
I have downloaded Google's 'rectangle.rb' tool example and I find following on the start-up:
if( not $rectangle_menu_loaded ) add_separator_to_menu("Draw") UI.menu("Draw").add_item("Rotated Rectangle") { Sketchup.active_model.select_tool RectangleTool.new } $rectangle_menu_loaded = true end
The tool is created each time the plugin is started.
Maybe if I have a Tool as a global variable inside a MyWebDialog class it multiplies now tools, every-time I open the dialog..What you propose has definitely a lot of merit. I will check if it improves my tool stability.
-
Yea - I used to create tools every time it was activated as well - due to the examples in the manual always do so. Can't remember if it was Fredo or Whaat that mentioned it was not necessary.
-
@thomthom said:
Yea - I used to create tools every time it was activated as well - due to the examples in the manual always do so. Can't remember if it was Fredo or Whaat that mentioned it was not necessary.
Well Dan is sensible guy who says smart stuff. But I'm not quite sure why you want to keep a reference to the Tool around rather than release it back to the system. The specific reason I generally keep dialogs as class variables is to avoid the weird GC behaviour I wrote about earlier. But the rule of thumb in OO systems, is you create stuff, use it and explicit/implicit release stuff.
The exception is sometimes performance when you pre-create Pools of objects to use/share. But this isn't one of those cases.
Adam
-
@adamb said:
The exception is sometimes performance when you pre-create Pools of objects to use/share. But this isn't one of those cases.
That is what I am doing. I was going a bit OT. I wasn't referring to a tool that created webdialogs.
-
@unknownuser said:
@dan rathbun said:
Tools are meant to be re-used.
I have downloaded Google's 'rectangle.rb' tool example ...
Warning! Most of the Google Ruby examples, have very poor Ruby programming practices. (One of the things on my to-do list is to edit these examples, so newbies see the proper Ruby way of scripting. Just need to find the time is all.)@unknownuser said:
The tool is created each time the plugin is started.
And that makes my point. The example is a poor example.@unknownuser said:
Maybe if I have a Tool as a global variable inside a MyWebDialog class it multiplies now tools, every-time I open the dialog..
Globals are not kept inside a class, even if they are defined inside a class. -
@dan rathbun said:
Warning! Most of the Google Ruby examples, have very poor Ruby programming practices. (One of the things on my to-do list is to edit these examples, so newbies see the proper Ruby way of scripting. Just need to find the time is all.)
Yes - I have some to realise this. Bit of a shame as one easily use the documentation as guideline when learning a language.
-
@adamb said:
Well Dan is sensible guy who says smart stuff. But I'm not quite sure why you want to keep a reference to the Tool around rather than release it back to the system.
Depends... if it's a base drawing or editing tool, that will be used often.. keep it around. If it's say a import, export or report tool, that will be used only on occasion, then yes create it when it's used, and release the reference to it when it's no longer needed. (Hoping GC will dispose on it.)@adamb said:
The specific reason I generally keep dialogs as class variables is to avoid the weird GC behaviour I wrote about earlier.
So is there a difference when the WebDialog instance, is created inside Object (at the console,) as opposed to within a custom namespace (Class or Module.) ?? It's always dangerous to create objects in the ObjectSpace, because all other objects may inherit them in some way (especially true of methods.) Local variables, Class variables and Instance variables defined in Object (at the console,) act strangly like Global variables when they're inherited by all the other objects.@adamb said:
The exception is sometimes performance when you pre-create Pools of objects to use/share. But this isn't one of those cases.
Well I have no idea what kind of tool he's talking about. If it's an occasional tool, then you are correct AB. If it will be used often, he will want to, keep the instance alive. -
@adamb said:
But the rule of thumb in OO systems, is you create stuff, use it and explicit/implicit release stuff.
Agreed.. however, it would nice to have direct control over "my" objects. When I want to get rid of an object, I'd like to call a dispose method. Perhaps also a release method that would release ALL references (vars) to the object. And finally, I'd like the undef function/keyword to act upon references (of all kinds,) so when I'm done with a reference, it can be removed from the module/class reference hash-table. (There is cheat to this. Use vars that begin with a capital letter so Ruby sees them as Constants, then you can use the remove_const to remove the definition.)
Anyhow... these wishes need to be voiced over at
http://redmine.ruby-lang.org/projects/ruby/issues -
@dan rathbun said:
The next time the user clicks a toolbar button (or menu item,) for your tool, Sketchup will call the Tool's activate method. This means that the Tool class object is still defined and should remain defined.
I have noticed that 'activate' method of my tool is never called. I create a
object=MyTool.new
, it launchesinitialize()
.@dan rathbun said:
Warning! Most of the Google Ruby examples, have very poor Ruby programming practices. (One of the things on my to-do list is to edit these examples, so newbies see the proper Ruby way of scripting. Just need to find the time is all.)
Could we create a simple structure of a properly programmed tool?
I have written something for a start. I think it is bugged because set_on_close is fired up twice.require "sketchup.rb" module ProperTool #Observer class ProperTool;;ViewObserver < Sketchup;;ViewObserver def onViewChanged(view) puts "View changed; #{view}" end end #Create observer version="ProperTool_001" unless file_loaded?(version) ProperTool;;VERSION=version @@view_observer=ProperTool;;ViewObserver.new end #Define reader method for the observer def ProperTool;;get_view_observer return @@view_observer end #Tool class ProperTool;;TestTool def initialize(web_dialog) @ip = Sketchup;;InputPoint.new @web_dialog=web_dialog end def onCancel(reason,view) @web_dialog.close end def deactivate(view) @web_dialog.close end end #Web Dialog controls the rest class ProperTool;;MainDialog def initialize @dlg=UI;;WebDialog.new("ProperTool") @dlg.set_html("<html><body> <form> <input type=\"button\" value=\"Close Me\" onclick=\"window.location.href='skp;close_me'\" /> </form></body><html>" ) @dlg.set_on_close { puts "Set on close fired." Sketchup.active_model.active_view.remove_observer ProperTool;;get_view_observer Sketchup.active_model.select_tool(nil) } @dlg.add_action_callback('close_me'){ |dialog, params| self.close() } @tool = Sketchup.active_model.select_tool ProperTool;;TestTool.new(@dlg) Sketchup.active_model.active_view.add_observer ProperTool;;get_view_observer @dlg.show end def close @dlg.close end end end # Module unless file_loaded?(ProperTool;;VERSION) main_menu = UI.menu("Plugins") main_menu.add_item("ProperTool") {ProperTool;;MainDialog.new} end file_loaded(ProperTool;;VERSION)
-
@unknownuser said:
I have noticed that 'activate' method of my tool is never called. I create a
object=MyTool.new
, it launchesinitialize()
activate
andinitialize
both works - for completely different purposes.initialize
triggers when you create an instance of your Tools class - as any other class.activate
triggers when the tool is being activated - either bySketchup.active_model.select_tool
orSketchup.active_model.tools.push_tools
.Are you not seeing that behaviour?
-
@thomthom said:
Are you not seeing that behaviour?
When I have added the
activate
method to the posted sample, it does trigger, but theinitialize()
doesn't.
For some odd reason activate doesn't fire up in my own tool. I start both tools exactly in the same way:
@tool = Sketchup.active_model.select_tool ToolName.new
-
` > class MyTool; def initialize; puts 'init'; end; def activate; puts 'activate'; end; end;
nilt = MyTool.new
init
#MyTool:0x11752260
Sketchup.active_model.select_tool(t)
activate
#Sketchup::Model:0x11269cf8
Sketchup.active_model.select_tool( MyTool.new )
init
activate
#Sketchup::Model:0x11269cf8` -
@unknownuser said:
@thomthom said:
Are you not seeing that behaviour?
When I have added the
activate
method to the posted sample, it does trigger, but theinitialize()
doesn't.
For some odd reason activate doesn't fire up in my own tool. I start both tools exactly in the same way:
@tool = Sketchup.active_model.select_tool ToolName.new
In your code:
def initialize(web_dialog) "Tool created." @web_dialog=web_dialog end
You're missing a
puts
.
Advertisement