Global Variables

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’…

3 Likes

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.

1 Like

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.

1 Like

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…

3 Likes
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 and Sketchup.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.

1 Like

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

1 Like

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.

1 Like

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.

1 Like

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.

1 Like

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:

2 Likes

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.

2 Likes