Thread Rating:
  • 1 Vote(s) - 5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
 Ruby Scripting
#21
To Encapsulate? Or Not to Encapsulate?
That is the... Controversy! Shocked

On another board I wound up talking about breaking encapsulation.

Happy with a sweat What is encapsulation?

Thinking Is it a bad thing at all?

The term itself is defined as:

Wikipedia Wrote:Encapsulation is used to refer to one of two related but distinct notions, and sometimes to the combination thereof:

A language mechanism for restricting direct access to some of the object’s components.

A language construct that facilitates the bundling of data with the methods (or other functions) operating on that data.

That is limiting how you call and manipulate a given portion of code belonging to the object. Keep in mind that an object can be either a class or a module in Ruby Ruby. Even numbers are objects because they do own several methods.

The second part handles the way you implement your internal and external calls inside blocks called methods in Ruby and functions in C, C++ and Javascript. This is some sort of task management. A single method is NOT supposed to take care of EVERYTHING.

A bad example that is linked to the lack of modularity is any main method in scene scripts found in RMXP. main contains almost everything. (The usual exception would be its update method.) That changed in VX and VX ACE because they do feature many helper methods like pre_start and start or terminate there. Now every method has a task or a group of related tasks to perform inside every method.

How do we know we are breaking the encapsulation?

Happy with a sweat Have you asked a scripter to give you at least a hint on how to access any of the current scene's methods from the Game_Party or the Spriteset_Map or vice versa?

Indifferent Guess what? You broke it! Judge Police 

You are only supposed to access the methods already provided by the script and that's it!

Happy with a sweat Oh yes, I know that it would make custom patches very difficult to be crafted under certain circumstances. Even so, you could accomplish the same goal in a totally indirect way instead.

And it is totally true that we got the attr_reader and attr_writer or attr_accessor methods to quickly make its variables accessible for free. But there's a catch! Shocked

Thinking What happens if you call the variable directly?

In order to make a point here, I will need to come up with some mockup code to better illustrate it.

Code:
class Game_System
  alias :gm_sys_init :initialize
  def initialize
    gm_sys_init
    @traps = []
  end
  attr_reader :traps
end

There I created a new traps method belonging to the Game_System class that is accessible via the $game_system global variable.

Am I breaking the encapsulation principle here? Confused
Apparently not... but I could be in no time. Shocked

Who Knows? How could you do it?

Well, it could be as EASY as making the following call.

Code:
$game_system.traps[10] = BearTrap.new

There I have already altered the actual contents of the traps Array without caring about checks and controls. Confused

Such cases demand from us the creation of custom methods to ensure we won't be leaving blank spaces or any foreign object that should not be found there like a number or a boolean. Happy with a sweat

A good but quite limiting example of this could be the $game_party.add_actor method that will limit the number of heroes that can join our party in RMXP or RMVX.

Happy with a sweat Yes, that means you got to create more and more wrapper methods to keep access to $game_system or $game_party methods safe. Laughing

A scripter's life is hard, guys! Tongue sticking out
"For God has not destined us for wrath, but for obtaining salvation through our Lord Jesus Christ," 1 Thessalonians 5:9

Maranatha!

The Internet might be either your friend or enemy. It just depends on whether or not she has a bad hair day.

[Image: SP1-Scripter.png]
[Image: SP1-Writer.png]
[Image: SP1-Poet.png]
[Image: SP1-PixelArtist.png]
[Image: SP1-Reporter.png]

My Original Stories (available in English and Spanish)

List of Compiled Binary Executables I have published...
HiddenChest & Roole

Give me a free copy of your completed game if you include at least 3 of my scripts! Laughing + Tongue sticking out

Just some scripts I've already published on the board...
KyoGemBoost XP VX & ACE, RandomEnkounters XP, KSkillShop XP, Kolloseum States XP, KEvents XP, KScenario XP & Gosu, KyoPrizeShop XP Mangostan, Kuests XP, KyoDiscounts XP VX, ACE & MV, KChest XP VX & ACE 2016, KTelePort XP, KSkillMax XP & VX & ACE, Gem Roulette XP VX & VX Ace, KRespawnPoint XP, VX & VX Ace, GiveAway XP VX & ACE, Klearance XP VX & ACE, KUnits XP VX, ACE & Gosu 2017, KLevel XP, KRumors XP & ACE, KMonsterPals XP VX & ACE, KStatsRefill XP VX & ACE, KLotto XP VX & ACE, KItemDesc XP & VX, KPocket XP & VX, OpenChest XP VX & ACE
Reply }
#22
This is something everybody should know about Patches and Bug Fixes.

Patches could also be named bug fixes depending on the issues that might have appeared while working on modifications of existing scripts. In other cases they might just alter or even replace the way the "original" script was supposed to work.

ALWAYS treat them as separate scripts. You'll thank me later on. Especially true if any patch you have included in the script editor ends up giving you headaches because it's not compatible with any other script there. Also true if it has some bugs that need some fixing.

If the Patch includes any mention of class SomeName or module AnotherName, it NEVER belongs to a script call event command. Create a new section in the script editor and paste it there.

Patches and Bug Fixes should always be placed in any free section BELOW their "parent" scripts, the scripts they partially modify or extend with new features.
"For God has not destined us for wrath, but for obtaining salvation through our Lord Jesus Christ," 1 Thessalonians 5:9

Maranatha!

The Internet might be either your friend or enemy. It just depends on whether or not she has a bad hair day.

[Image: SP1-Scripter.png]
[Image: SP1-Writer.png]
[Image: SP1-Poet.png]
[Image: SP1-PixelArtist.png]
[Image: SP1-Reporter.png]

My Original Stories (available in English and Spanish)

List of Compiled Binary Executables I have published...
HiddenChest & Roole

Give me a free copy of your completed game if you include at least 3 of my scripts! Laughing + Tongue sticking out

Just some scripts I've already published on the board...
KyoGemBoost XP VX & ACE, RandomEnkounters XP, KSkillShop XP, Kolloseum States XP, KEvents XP, KScenario XP & Gosu, KyoPrizeShop XP Mangostan, Kuests XP, KyoDiscounts XP VX, ACE & MV, KChest XP VX & ACE 2016, KTelePort XP, KSkillMax XP & VX & ACE, Gem Roulette XP VX & VX Ace, KRespawnPoint XP, VX & VX Ace, GiveAway XP VX & ACE, Klearance XP VX & ACE, KUnits XP VX, ACE & Gosu 2017, KLevel XP, KRumors XP & ACE, KMonsterPals XP VX & ACE, KStatsRefill XP VX & ACE, KLotto XP VX & ACE, KItemDesc XP & VX, KPocket XP & VX, OpenChest XP VX & ACE
Reply }
#23
self && @instance_variables

In RGSSx scripts it is not impossible to find calls that begin with a self reserved word.

What is self? Thinking

As we had learned long time ago, self simply means the same class or module.

Why is it there? Detective

In modules like Kernel or RPG::Cache alias Cache and the File class, you cannot call certain methods UNLESS you include the actual module or class name somehow.

While defining stuff pertaining to your module or class, you can avoid using its actual name and simply replace it with self. It is way more practical than typing the same name over and over again. Tongue sticking out

Why can't you call them in any other way? Happy with a sweat

That is because the method has become what we call a singleton method. Only a single instance will get access to the data it contains at any given moment. What instance? The module or class itself!

HEY! That's not true! Shocked

Huh? Huh? What do you mean by that?

I've seen it used in other circumstances and it's not referring to the module or class at all. Angry

Oops! Oh, you mean those instances like self.contents and self.bitmap found in window scripts.

For starters, yeah... Sarcasm

Grinning Don't worry about it! That is easy to explain indeed.

What happened there is that some of those calls are seeking hidden methods, those written in C or C++, and there is no other way to access them or else they could be treated as simple local variables.

Code:
self.contents.font.size = 32

In this example, the use of self is optional. Just use contents and it will work fine.

Confused Is there any difference between using one option or the other?

Happy with a sweat You got me here. Yes, there is one!
What takes place there is that contents alone forces Ruby Ruby to look for any instance of either a local variable OR a method called contents. While self.contents already informs the interpreter that you are looking for a method, be it a class or instance method. (Class methods are singletons by default.)

Sarcasm + Confused But there is a catch! There is always a catch! Confused

Code:
contents = Bitmap.new(32, 32)
self.contents = Bitmap.new(32, 32)

The first call would just create a local variable that will cease to exist the moment that method finishes its execution. The latter is ye old and trustworthy self.contents assignment method and it will keep the Bitmap object stored there for future references.

This is What Actually Made Me Write This Review!

There is a caveat that we scripters should know about just in case we still ignored it for any reason.

What happens very often in RGSS1, namely on RMXP, during battle is a very unusual phenomenon in Ruby Ruby scripting. This is one of the very few places where this has ever happened in decades.

classes like Game_Battler make several references to methods like self.damage instead of using @damage directly.

Why does this ever happen? Detective 

Well, Happy with a sweat I guess that scripters were not fully aware of how @instance variables were supposed to be used in a script. Thus, what I mean here is that they did something totally weird by calling self.damage instead of simply typing @damage whenever they want to calculate the actual damage or healing any given Game_Actor or Game_Enemy is about to receive at any moment.

Look Up But is it something really bad at all?

Hrm, I suppose Thinking that depends on what you want to accomplish there. Laughing

If you are planning to pre-calculate some basic damage, that might be helpful indeed. Shocked
Indifferent Otherwise using a plain @damage instance variable would suffice there, letting you to hit that bad mobster Ghost as hard as you can. Shocked Go for it, Arshes!

Other implementations like RGSS3 actually provides us with calls to methods in place of variables to let you calculate base prices, for instance. Winking And nope, they do not use the self pseudo variable at all.
Indifferent They may or may not have the same name as the actual variable that is going to be used there.
Happy with a sweat You see, once you choose to use a method there, you are free to name it they way you prefer to your heart's content. Winking
And anybody can later alias the method at will. Grinning

The Conclusion

Headache This is one of those cases where there is no way to get quite conclusive about what you are supposed to do now because people can tell you at a certain point that it is a matter of style or taste. Eating Hamburger
And they might be right about that Tongue sticking out no matter how weird that might sound like. Laughing
"For God has not destined us for wrath, but for obtaining salvation through our Lord Jesus Christ," 1 Thessalonians 5:9

Maranatha!

The Internet might be either your friend or enemy. It just depends on whether or not she has a bad hair day.

[Image: SP1-Scripter.png]
[Image: SP1-Writer.png]
[Image: SP1-Poet.png]
[Image: SP1-PixelArtist.png]
[Image: SP1-Reporter.png]

My Original Stories (available in English and Spanish)

List of Compiled Binary Executables I have published...
HiddenChest & Roole

Give me a free copy of your completed game if you include at least 3 of my scripts! Laughing + Tongue sticking out

Just some scripts I've already published on the board...
KyoGemBoost XP VX & ACE, RandomEnkounters XP, KSkillShop XP, Kolloseum States XP, KEvents XP, KScenario XP & Gosu, KyoPrizeShop XP Mangostan, Kuests XP, KyoDiscounts XP VX, ACE & MV, KChest XP VX & ACE 2016, KTelePort XP, KSkillMax XP & VX & ACE, Gem Roulette XP VX & VX Ace, KRespawnPoint XP, VX & VX Ace, GiveAway XP VX & ACE, Klearance XP VX & ACE, KUnits XP VX, ACE & Gosu 2017, KLevel XP, KRumors XP & ACE, KMonsterPals XP VX & ACE, KStatsRefill XP VX & ACE, KLotto XP VX & ACE, KItemDesc XP & VX, KPocket XP & VX, OpenChest XP VX & ACE
Reply }
#24
Moronic Design: Ruby & The Embedded Methods

Months ago there was a discussion on a topic that made some people not used to Ruby's nuances pretty mad indeed. The topic at hand was defining a method inside another method, pretty much embedding it without caring about the consequences of such a poor design. I heavily critized it because it will never ever make any sense in Ruby Ruby nor RGSS any version at all.

Now I am going to cover that here to let you know what actually happens when you try to imitate one of the effects, namely treating Ruby's methods as JavaScript's functions thinking they both are first class citizens in their respective programming languages.

The results might heavily disappoint some of you for not reflecting your wishes or fears as accurately as it was expected. Actually, the outcome is quite predictable while still retaining its nonsensical nature intact. Happy with a sweat

Conclusions:

  1. Ruby has NEVER treated its methods as objects that can be easily and perfectly defined pretty much anywhere in your code like JavaScript functions do in later iterations of the RM engine.
  2. The contents of embedded methods will not be accessible from within the "parent" method. They will just get defined as usual and will belong to that instance of the class, aka the current object, only and not to the fake parent method.
  3. Ruby will parse and (re)define that weird internal method will take place every single time you call the so called parent method. (Ruby will only keep a reference to the last definition of a method. Here it will only replace the previous definition over and over again.)
  4. There is no way you can make Ruby get in trouble by making a large chain of methods the way it allows you to chain constants in a weird mix of module / class & constant list kept together as a single but large script call that has no theoretical end as far as we scripters know.
  5. There is no way we can ever get something meaningful and useful out of making this terrible mistake.

The Proof

We need to come up with some test code to make sure we can prove what I have exposed above as the only valid conclusions I could come up with after running it several times in a row.

So here it is!

Sample Code

Code:
class A
  def hello
    print "Hello!"
    def bye
      print "Bye!"
    end
  end
end

Now let us run it by pasting the following snippet right on a Editor script section located before the Main script and lets launch the game after saving our changes to the script editor.

Test Code

Code:
print a = A.new
print a.hello
begin
  print a.hello.bye
rescue
  print "Failed to execute a.hello.bye"
end
a.bye
a.hello
a.bye
a.hello


Side Note:

If you print both the a.hello or a.bye calls, you will not get any error messages. Even so, removing the print call like I did in the code above can make you throw an error for relying on a rescue call inside an IRB binding. What this means is that a console window running the Ruby interpreter is not supposed to execute such a rescue statement. Confused This is true for the 2.7 release at least.

Inline rescue calls work normally even while running the IRB on a console window aka shell.


What we are going to do now is test it on vanilla RMXP (or VX or ACE if you prefer) and also on HiddenChest game engine and just see what happens then. Thinking Will both engines return the same results?

Ruby 1.8's Output - vanilla RGSS1

Code:
#<A:0x41a3481>
Hello!
nil
Hello!
Failed to execute a.hello.bye
Bye!
nil
Hello!
nil
Bye!
nil
Hello!
nil

What we should notice after reading the output above is that whenever a method gets defined, meaning being parsed and evaluated first, it will always return a nil object as its normal outcome.

Ruby 2.7's Output - HiddenChest

Code:
#<A:0x00007f1d4cf54810>
Hello!
bye
Hello!
Failed to execute a.hello.bye
Bye!

Hello!
bye
Bye!

Hello!
bye

OK, this is very similar to the first output, except that Ruby 2.7 returns a :bye symbol after defining any given method. For some reason still unknown to your servitor, it doesn't return anything readable after printing the bye method's return value.

Note:
After other people also ran the code and reviewing the default implementation of Ruby called either MRI or YARV depending on the version, using two consecutive print calls like I originally did will return a nil value or a whitespace accordingly.

In both cases you can either get a bye string representing the method's name by calling the usual print function or the actual :bye "symbol" (actually a string that keeps its typical colon at the beginning) if you call the p function instead.
"For God has not destined us for wrath, but for obtaining salvation through our Lord Jesus Christ," 1 Thessalonians 5:9

Maranatha!

The Internet might be either your friend or enemy. It just depends on whether or not she has a bad hair day.

[Image: SP1-Scripter.png]
[Image: SP1-Writer.png]
[Image: SP1-Poet.png]
[Image: SP1-PixelArtist.png]
[Image: SP1-Reporter.png]

My Original Stories (available in English and Spanish)

List of Compiled Binary Executables I have published...
HiddenChest & Roole

Give me a free copy of your completed game if you include at least 3 of my scripts! Laughing + Tongue sticking out

Just some scripts I've already published on the board...
KyoGemBoost XP VX & ACE, RandomEnkounters XP, KSkillShop XP, Kolloseum States XP, KEvents XP, KScenario XP & Gosu, KyoPrizeShop XP Mangostan, Kuests XP, KyoDiscounts XP VX, ACE & MV, KChest XP VX & ACE 2016, KTelePort XP, KSkillMax XP & VX & ACE, Gem Roulette XP VX & VX Ace, KRespawnPoint XP, VX & VX Ace, GiveAway XP VX & ACE, Klearance XP VX & ACE, KUnits XP VX, ACE & Gosu 2017, KLevel XP, KRumors XP & ACE, KMonsterPals XP VX & ACE, KStatsRefill XP VX & ACE, KLotto XP VX & ACE, KItemDesc XP & VX, KPocket XP & VX, OpenChest XP VX & ACE
Reply }


Possibly Related Threads…
Thread Author Replies Views Last Post
   Ruby - Behind the Scenes DerVVulfman 0 543 07-15-2023, 05:52 PM
Last Post: DerVVulfman
Information  Assorted Ruby chm documents. hanetzer 10 32,852 08-17-2020, 04:19 AM
Last Post: kyonides
Brick  Learn Ruby! greenraven 2 6,918 05-16-2014, 12:25 PM
Last Post: greenraven
   Creating Your Own Scripting System DerVVulfman 3 6,654 10-12-2009, 03:37 PM
Last Post: Alpha-Mad
   How to make interesting NPC's without scripting Third333Strike 0 3,442 12-06-2008, 04:59 PM
Last Post: Third333Strike



Users browsing this thread: