Global Object Stupidity, Ruby Question
-
Was cleaning up my use of window, the global object, in my JavaScript. Put all the data into vismap.this and vismap.that, for instance.
This is just plain dumb, isn't it? My webdialog has a "window" global object. Your webdialog has another "window." You and I can choose identical "global" names, but they are attached to separate global objects. No problem. Am I missing something?
Now, I've read a bunch about avoiding Ruby name conflicts. Could someone give this an explanation? Write it as if your audience knows very little, so I can understand it.
Thanks.
-
Martin,
I knew you'd get back to this...
This topic could almost be 'how do you like to code?', as there's lots of thoughts on it, much of it not Ruby specific.
Some things I've seen and done --
- Wrap everything in modules with names like MR1.0, etc
- Prefix all public functions and class defs with something like MR1.0
- Make all methods and 'getters/setters' in classes private if there is no reason for them to be called from outside the class. Same thing for functions and class defs in modules
- I don't use global objects. Many coders never use them. I use '@@' variables in the module that loads everything
- We're not, but pretend we're writing code for the government, and everyone wants to break/hack it...
I wrote a lot of my code with class variables, and at present on Windows that seems to be fine. For instance, the active model would be a class variable. It works now, but I don't know about mac's, and SU could change to allow multiple drawings in one instance, and hence, using class variables that way could break. When I get to it, I intend to rewrite my code. Put simply, at present, the Ruby environment is scoped to a single model. What if that changes?
HTH,
Greg
-
@martinrinehart said:
This is just plain dumb, isn't it? My webdialog has a "window" global object. Your webdialog has another "window." You and I can choose identical "global" names, but they are attached to separate global objects. No problem. Am I missing something?
I think the problem is that the global variable points to an object. And it can only point to one object. So if you name your global that same as someone else, then you are playing a dangerous game. The ruby that gets loaded first will define the global to point to their window object. But then the next ruby will load and redefine the global to point to their window object. Now the first ruby still thinks that the global is pointing to its winow object, but its not. So it tries to interact with its window object, but it is the wrong window object.
At least that is my understanding.
Chris
-
@msp_greg said:
I knew you'd get back to this...
Looks like you were there before me.
You explained the fixes, but not the problem. Is the problem this: there is a single Ruby workspace. If my Ruby loads and assigns to "foo", then your Ruby is loaded and it assigns to "foo" my "foo" and your "foo" are the same and I lost the race by coming in first?
So JavaScript, which is legendary for its global object issues, in this instance has lots of window "globals" and they're all private. It's Ruby that has global object issues.
@chris fullmer said:
I think the problem is that the global variable points to an object. And it can only point to one object.
ChrisWatch out! In JavaScript in a browser "window" is the global object, so these three are all the same.
foo = 123; var foo = 123; window.foo = 123;
The first two are just shorthand for the third.
But the "global object" is not so global. If your webdialog is flying along side mine, we both have a separate "window", so our JavaScripts have no conflicts. In fact, if you use frames, each frame has a "window" object so "window.foo" could exist and be different in each frame.
-
Yes, well its the same idea. In JS, each window is separate from the other. And in SketchUp, each SketchUp instance is different than the others. So you can run 2 instances of SU side by side, and the globals of your script in one window don't mess up the globals in another.
But within a single instance of SU, all globals are in fact global. And if one script changes a global, then i rightly messes up anyone else's identically named globals. Its why they are called globals.
If you make a window object, and point a global variable to it. Then someone else comes along and re-points that global variable to their window object, your window object still exists, it just has nothing pointed at it. And no way to access it.
-
This is just a matter of scope of the script engine.
You will agree that if you run 2 instancesof Sketchup, the global variables are not shared (because Sketchup runs two different Ruby environment instances)
In Java script, the scope of the script engine is at the level of the browser session (materialized by 'window').
Anyway, Greg'advice is very relevant. You can always code without global variables. This said, Ruby'strength (or weakness) is that the exception are the constants, among which are the Module and Class names. All SU Ruby scripters should maybe maintain a table of namespace (module name) to avoid clashes.
Fredo
-
@unknownuser said:
All SU Ruby scripters should maybe maintain a table of namespace (module name) to avoid clashes.
Yes, and if you find a good "how to convert your Ruby to a module" explanation, please send it my way. I tried "module vismap" at the start and "end" at the end and found out that was just the beginning.
-
@martinrinehart said:
@unknownuser said:
All SU Ruby scripters should maybe maintain a table of namespace (module name) to avoid clashes.
Yes, and if you find a good "how to convert your Ruby to a module" explanation, please send it my way. I tried "module vismap" at the start and "end" at the end and found out that was just the beginning.
Then you need either:
- to prefix your methods with the module name (or
self
) - or you create a class, and then create an instance of that class to use the method.
Fredo
- to prefix your methods with the module name (or
-
Here's an example of a script where I've wrapped my script into a module. Like Fredo says, you then have to define each method by using "def module_name.method_name" or "def self.module_name". I've bolded the 3 things to look out for.
- Define the module.
- Define methods correctly
- Call the methods correctly
` module Clf_face_to_component
def Clf_face_to_component.main(code here)
end #method
end #moduleUI.menu("Plugins").add_item("Faces into Components") { Clf_face_to_component.main}`
Chris
-
For example, there is no top-level "active_model" - it is Sketchup.active_model. So if we ask Ruby what Sketchup is, we see it is a Module.
Sketchup.class Module.
Which, if we were to define it ourselves, might look like this:
module Sketchup def Sketchup.active_model # or self.active_model return TheActiveModel end end
It's a way to collect related methods and avoid over-writing someone else's methods and variables.
You can still use classes if they are appropriate, just define them in the module.
module ME TWO_PI = 2.0 * Math;;PI # < Module-level constant class Model end end
which would then be access externally as
model = ME::Model
And the constant can be access by:value = ME::TWO_PI
-
Thanks, guys.
# vismap.rb require 'sketchup' module Vismap class Vismap_Model def initialize() @model = Sketchup.active_model @layers = @model.layers @scenes = @model.pages end def layers() return @layers end def layer_names() names = [] @layers.each { |lr| names.push(lr.name) } return names end def scenes return @scenes end def scene_names() names = [] @scenes.each { |s| names.push(s.name) } return names end # return "VIVV...", one "V"isbl or "I"nvisbl for each scene in each layer def getVisibles() answer = 'V' * (@scenes.count*@layers.length) i = 0 @scenes.each do |s| s.layers.each do |lr| loc = Vismap.locate( lr.name, layer_names() ) * @scenes.count loc += Vismap.locate( s.name, scene_names() ) answer[loc..loc] = "I" end i += 1 end return answer end # getVisibles() # JSON to send to webdialog def getJson() ret = '{ ' begin ret += 'layers;' + Vismap.namesJson( self.layer_names ) ret += ', scenes;' + Vismap.namesJson( self.scene_names ) vis = self.getVisibles() ret += ", vis;'" + vis + "'" ret += ' }' rescue errMsg = $!.to_s errMsg.gsub!( '<', '<' ) vis = 'vis;\"Error - ' + errMsg + '\"' ret = '{ layers;\"\", scenes;\"\",' + vis + ' }' end return ret end # getJson() # given "VI" string (see getVisibles) sets visibilities def setVisibles( vis ) sn = 0 # scene number @scenes.each do |s| ln = 0 # layer number @layers.each do |lr| vn = ln*@scenes.count + sn # vis number v_or_i = vis[vn..vn] s.set_visibility( @layers[ln], v_or_i == 'V' ) ln += 1 end # layers.each sn += 1 end # scenes.each end # of setVisibles() end # of class Model class Bitmap =begin JavaScript returns the new list of Visibles and Invisibles as a bit map encoded in a string. The first three characters encode the length of the string. Each following character encodes six bits of the map. CHR(48) == 000000; CHR(49) == 000001; CHR(50) == 000010; CHR(51) == 000011; etc. =end @@vismap_powers = [ 32, 16, 8, 4, 2, 1 ] @@base = 48 def initialize( map ) @map = map self.getLength() # sets @length self.getVis() # sets @vis end # end of initialize() def length() return @length end def vis() return @vis end def getVis() @vis = '' bits = @map[3,@map.length - 3] (0..bits.length-1).each do |i| word = bits[i] word -= @@base; @vis += self.getVisFromWord( word ) @vis = @vis[0, @length] end end def getVisFromWord( word ) v = '' @@vismap_powers.each do |p| v += ( p & word ) > 0 ? 'V' ; 'I' end return v end def getLength() @length = 64 * 64 * ( @map[0] - @@base ) @length += 64 * ( @map[1] - @@base ) @length += @map[2] - @@base; end end # of class Bitmap def self.check( len ) nlrs = $vismap_model.layers().length nsns = $vismap_model.scenes().count ok = ( len == (nlrs * nsns) ) UI;;messagebox( "Wrong number of layers and/or scenes \n" + "Click \"Get Data from Model\" and try again." ) if ( !ok ) return ok end def self.launchWebDialog wd = UI;;WebDialog.new( "Layer/Scene Visibility Map", true, 'vismap_v1.0', 400, 300, 100, 500, true ) wd.add_action_callback( "refresh" ) do |js_wd, msg| reloadModel() json = $vismap_model.getJson script = 'rubyReturned( "' + json + '" );' # puts script js_wd.execute_script( script ) end wd.add_action_callback( "newVis" ) do |js_wd, msg| reloadModel() unless msg.nil?() map = Bitmap.new( msg ) $vismap_model.setVisibles( map.vis ) if Vismap.check( map.length() ) end end # of newVis() pathname = Sketchup.find_support_file( 'vismap.html', 'Plugins/vismap/' ) wd.set_file( pathname ) wd.show() end # launchWebDialog() def self.reloadModel() $vismap_model = Vismap_Model.new end # true if any member of array matches item def self.is_in( item, array ) return self.locate( item, array ) > -1 end # index of item in array (-1 == not found) def self.locate( item, array ) i = 0 array.each do |a| return i if (a == item) i += 1 end return -1 end # convert array of names to JSON array def self.namesJson( names ) ret = '[ ' start = true names.each do |n| unless start ret += ', ' else start = false end ret += quote( n ) end ret += ' ]' return ret end # namesJson() # convert name (which may contain embedded quotes) to name in quotes def self.quote( name ) name.gsub!( '"', '\"' ) name.gsub!( "'", "\'" ) # name.gsub!( "\", "\\\\" ) # how do you do this in Ruby? return "'" + name + "'" end launchWebDialog() end # module Vismap # end of vismap.rb
-
I think this can help to understand modules. Since a few hours ago, I did not had any idea about what modules are. This gave me a clue.
I hope others will find it enlighting too.
http://www.rubyfleebie.com/an-introduction-to-modules-part-1/
Advertisement