I think I didn’t use Sketchup.load and Sketchup.require. I don’t know how plugin can understand run in SU for first time or second time? Is there any especial code that each time we run SU change?
I just got a new desktop, and I haven’t yet loaded SketchUp. I’m also working on a few non-Sketchup Ruby projects (and working with WSL2/Ubuntu).
Once your plugin starts, it remains loaded. So, all the classes and modules it defines still exist. If you don’t ‘hard’ reload it when you reopen the plugin, any info stored in them is maintained.
IOW, if the plugin is set up correctly, information can be maintained when the plugin appears ‘closed’…
For a value to remain in memory, it must be “owned”, i.e. referred to, by some object that also remains in memory or some variable that remains in scope. Objects with no remaining references are “garbage collected” soon after the last reference goes away, hence their value is lost. However, module and class names are constants that refer to fixed objects. Ruby does this so that all usages of, e.g., the MyTool class will refer to the same class object regardless of when they occur in the code. In effect, Ruby itself holds a reference to them without needing a separate variable. So, values referenced by module or class variables, which are owned by the corresponding fixed module or class objects, will remain in memory until the Ruby interpreter shuts down (as SketchUp exits). That’s the basis of several of the techniques given in earlier posts in this topic.
I have a variable named “@ww”. When the plugin run its value is nil. I change the value to 15 and never change it in the plugin. When I just close and reopen the plugin its value will be nil again. As you said Ruby considers it as garbage when I close the plugin. If I change its name to “$ww”, when I close and reopen the plugin its value not change. I try not to use global variables and looking for other ways to have similar results.
A variable named @ww is an instance variable. It is owned by a single instance of the class and persists only as long as that instance persists. So, the behavior you describe is exactly as expected. You should read up on class and module variables to understand the difference.
Perhaps best to go back to 1.0.1 ???
A variable named xx
applies within its method [e.g. def xxxx(); xx=123; ### ;end
] it doesn’t carry over into other methods, modules or classes.
A variable named @xx
applies within its module [e.g. module XXX; @xx='123'; ### use it in other methods etc ;end
] - it is remembered within that module during that SketchUp session, it can be changed within methods within that module and is thereafter reused during that session.
A variable named @@xx
applies within its class [e.g. module XXX; class TOOL; @@xx=123; end
] - it is accessible within any methods within that class, and it’s remembered across instances of that module’s class during that SketchUp session.
A variable named XXX
is a Constant and if it’s define within a module, then it is accessible to all methods/sub-modules/classes within that module - a Constant cannot be changed during a session without a warning - although it will change !
Using Sketchup.read_default()
and Sketchup.write_default()
can store your extension’s data for use across SketchUp’s sessions. But you must read
in these with default values in case they are not set, and after the user changes them you must write
these again, to save them…
You can also save your extension’s setting etc into a specific model using
Sketchup.active_model.set_attribute(name, key, values)
and when opening a model read any saved attributes back [with defaults]
values=Sketchup.active_model.get_attribute(name, key, defaults)
There’s lots of guidance on the Ruby guidance and the API etc…
Sketchup.load vs Sketchup.require
FYI, It currently does not matter as Sketchup::load
and Sketchup::require
are aliases.
They both work like Kernel#require
in addition to doing the RBE decryption.
We have several times discussed changing Sketchup::load
to instead work like Kernel#load
…
@tt_su said in API tracker issue 462 …
Yea, internally both
Sketchup.require
andSketchup.load
both call the same function - which uses a load guard.I’m wondering if this is a case where we can/should accept a breaking change. Over the years of doing extension reviews I don’t recall every seeing
Sketchup.load
being used. I suspect the impact would be minimal.
Dan,
Thanks. I either never knew or forgot (we both go back to at least SU8). That is kind of different…
And… where do you have initialized this variable? If you have initialized in a class instance that you again initialize over and over again, it will not be persisted…
So, an easy way to ensure single initialization is to use the Singleton Pattern: Singleton pattern - Wikipedia
A quick example in Ruby:
module MyModule
module MySubModule
class MySingleton
@@instance = nil
def self.instance()
# check if an instance extists, if not create one now
@@instance = self.new() if @@instance.nil?
@@instance
end #def
private_class_method :new # make sure you cannot make new instances from anywhere
def initialize()
# this is the initializer of every instance of this class. Butm since we are making a singleton, there will only be one instance and this will be executed just once
@the_value_that_i_want_to_initialize_just_once = "first time"
end #def
def the_value_that_i_want_to_initialize_just_once()
@the_value_that_i_want_to_initialize_just_once
end #def
def the_value_that_i_want_to_initialize_just_once=(value)
@the_value_that_i_want_to_initialize_just_once = value
end #def
end #def
end #def
end #def
You then call
MyModule::MySubModule::MySingleton.instance.the_value_that_i_want_to_initialize_just_once()
and
MyModule::MySubModule::MySingleton.instance.the_value_that_i_want_to_initialize_just_once=some other value
The first time you call it it will return "first time"
since that is the default it gets set to in the initializer/constructor, all subsequent calls it will return whatever you set it to the last time you called MyModule::MySubModule::MySingleton.instance.the_value_that_i_want_to_initialize_just_once=some_other_value
I intentionally did not include Ruby’s existing Singleton
module so that the TS can have a better understanding of what is going on.
So, there is a more simple solution, but if I would have copied that instead of the example above you would not have an overview of what the singleton pattern does:
require 'singleton'
module MyModule
module MySubModule
class MySingleton
include Singleton
def initialize()
# this is the initializer of every instance of this class. Butm since we are making a singleton, there will only be one instance and this will be executed just once
@the_value_that_i_want_to_initialize_just_once = "first time"
end #def
def the_value_that_i_want_to_initialize_just_once()
@the_value_that_i_want_to_initialize_just_once
end #def
def the_value_that_i_want_to_initialize_just_once=(value)
@the_value_that_i_want_to_initialize_just_once = value
end #def
end #def
end #def
end #def
Basically, The Singleton Pattern ensures that you can only create 1 instance of a class. It is very similar to ruby Modules, but, it limits the creation of resources to initialize only when you need them. If you need to store a lot of variables for example: in a Module they are initialized directly when code gets loaded while when using a singleton you only create the object and all data it holds when calling the instance
.
I would also suggest having a better understanding of global, module and instance variables. Just putting a @
in front of a variable does not make it suddenly different, when not being initialized in the proper place.
Is the Singleton pattern a ‘good’ pattern? Some says yes, some say no and consider it an anti pattern. I consider it an anti pattern myself and the above example is not how I would use a Singleton, but it helps to explain how it works and it will also help you solve your current problem. But, I would suggest learning some more about object oriented programming. Here are also some good resources for ruby programming: Design Patterns in Ruby
Or, even better is this book: https://www.amazon.com/Design-Patterns-Ruby-Russ-Olsen/dp/0321490452
Singleton Pattern is so interesting but as I understood in my plugin it works exactly the same as
result = Sketchup.write_default("section", "key", "my_value")
result = Sketchup.read_default("section", "variable", "default")
in reading sequence, when it is the first time, “result” will be “default” and when it is not the first time, “result” will be the last data that saved in writing sequence. When we close SU and open it again, “result” will be the last data saved in the writing sequence.
As I checked Singleton Pattern works the same. Please let me know if I misunderstood something. It is my fast review result and sure I need a longer time to understood Singleton Pattern more.
No.
Your objects are stored in memory. If you close sketchup and restart it, you will create a new instance of the singleton, and thus reset everything to default…
Sketchup defaults are written and read to and from a file. That is why it persists between sessions.
Let me check it again…
Sir, You are right. I should study more and deeply about variables. To have result for this topic lets have a simple example. In our example, when we run it for first time result will be first time and when we run it for second time or more result will be second time.
require 'extensions.rb'
require 'sketchup.rb'
require 'singleton'
module MajidMahmoudi
module MajTest
class MySingleton
include Singleton
def initialize()
@the_value_that_i_want_to_initialize_just_once = "first time"
end #def
def the_value_that_i_want_to_initialize_just_once()
@the_value_that_i_want_to_initialize_just_once = "second time"
end #def
def the_value_that_i_want_to_initialize_just_once=(value)
@the_value_that_i_want_to_initialize_just_once = value
end #def
end #def
class MajTest
def activate
test = MajidMahmoudi::MajTest::MySingleton.instance.the_value_that_i_want_to_initialize_just_once()
p test
end
end #class MAJTest
cmd1 = UI::Command.new("MAJ Test") {
Sketchup.active_model.select_tool MajTest.new
}
cmd1.set_validation_proc { true ? MF_ENABLED : MF_GRAYED }
cmd1.small_icon = "./images/small_icon_wall.png"
cmd1.large_icon = "./images/large_icon_wall.png"
cmd1.tooltip = "Wall Maker"
maj_toolbar = UI::Toolbar.new "MAJ Test"
maj_toolbar.add_item cmd1
maj_toolbar.show
end #MajWall
end #MajidMahmoudi
Would you please let me know my problem?
I think I am having trouble understanding what the real usecase is.
Anyhow, in your code above you are never setting the value to something else, thus it will always return the default value.
I assume when I call this class for the first time in SU it returns “first time” and when I call it the second time in SU it will return “second time” but it always return “second time”. Would you please change the codes that when we call it for first time, it will return “first time”.
It does just that, doesn’t it? It will return “first time” for as long you do not set it to some other value. And, if you set it to some other value, it will return that other value, for as long you do not restart sketchup.
No Sir, it always returns “second time”. not important you run it for the first time or the second time. Code is simple and complete please copy and paste in your SU and run it. Thank you in advance.
I see… because you changed this.
From this:
You are great!!! Thank you so much, codes work exactly in the way you said and I wanted. When run code first time in SU it returns “first time”. If we close and open plugin again return value. When we close and open SU and run it for the first time again it returns “first time”. Simply we can check if it returns “first time” we can use default data or write default data instead of “first time” and whenever need we can save new data on it.
require 'extensions.rb'
require 'sketchup.rb'
require 'singleton'
module MajidMahmoudi
module MajTest
class MySingleton
include Singleton
def initialize()
@the_value_that_i_want_to_initialize_just_once = "first time"
end #def
def the_value_that_i_want_to_initialize_just_once()
@the_value_that_i_want_to_initialize_just_once
end #def
def the_value_that_i_want_to_initialize_just_once=(value)
@the_value_that_i_want_to_initialize_just_once = value
end #def
end #def
class MajTest
def activate
test = MajidMahmoudi::MajTest::MySingleton.instance.the_value_that_i_want_to_initialize_just_once()
p test
MajidMahmoudi::MajTest::MySingleton.instance.the_value_that_i_want_to_initialize_just_once = "123"
test = MajidMahmoudi::MajTest::MySingleton.instance.the_value_that_i_want_to_initialize_just_once()
p test
end
end #class MAJTest
cmd1 = UI::Command.new("MAJ Test") {
Sketchup.active_model.select_tool MajTest.new
}
cmd1.set_validation_proc { true ? MF_ENABLED : MF_GRAYED }
cmd1.small_icon = "./images/small_icon_wall.png"
cmd1.large_icon = "./images/large_icon_wall.png"
cmd1.tooltip = "Wall Maker"
maj_toolbar = UI::Toolbar.new "MAJ Test"
maj_toolbar.add_item cmd1
maj_toolbar.show
end #MajWall
end #MajidMahmoudi# Default code, use or delete...h
Similar code but using Singleton…
class MySingleton
include Singleton
def initialize()
@st_value3 = "15", "300", "0", "5", "0", "0", "1", "0", "0"
end
def st_value3()
@st_value3
end
def st_value3=(value)
@st_value3 = value
end
end
when 3
loop do
prompts = ["Wall Width (0...100)", "Wall Height (10...1000)", "Wall to Zero (0...WH-10)",
"Finishing Width (0...10)", "Finiahing to WZ (0... WH-10)", "Finishing to WH (0...WH-FWZ)",
"Interior Width (0...10)","Interior to WZ (0...WH-10)", "Interior to WH (0...WH-IWZ)"]
defaults = MajidMahmoudi::MajWall::MySingleton.instance.st_value3()
wallin = UI.inputbox prompts, defaults, "Wall Information"
@ww, @wh, @wz, @fw, @fwz, @fwh, @iw, @iwz, @iwh = wallin.map(&:to_f)
if 0 <= @ww && @ww <= 100 && 10 <= @wh && @wh <= 1000 && 0 <= @wz && @wz <= @wh - 10
if 0 <= @fw && @fw <= 10 && 0 <= @fwz && @fwz <= @wh - 10 && 0 <= @fwh
if @fwh <= @wh - @fwz && 0 <= @iw && @iw <= 10 && 0 <= @iwz && @iwz <= @wh - @iwz
if 0 <= @iwh && @iwh <= @wh - @iwz
MajidMahmoudi::MajWall::MySingleton.instance.st_value3 = wallin
break
end
end
end
end
Sketchup.vcb_label = " MAJ Wall Maker :"
Sketchup.vcb_value = 'Wrong Wall info'
end #loop
SketchUp has no concept of closing a plugin. You can close a dialog and deactivate a tool but not close the plugin itself. Maybe you’ve tried using instance variables in throwaway instances of a custom class, e.g. a tool, but if you use them in something “more persistent” as a module, they’ll stay for the rest of the SketchUp session.