RMRK is retiring.
Registration is disabled. The site will remain online, but eventually become a read-only archive. More information.

RMRK.net has nothing to do with Blockchains, Cryptocurrency or NFTs. We have been around since the early 2000s, but there is a new group using the RMRK name that deals with those things. We have nothing to do with them.
NFTs are a scam, and if somebody is trying to persuade you to buy or invest in crypto/blockchain/NFT content, please turn them down and save your money. See this video for more information.
Ruby and RGSS3: The More You Know

0 Members and 1 Guest are viewing this topic.

*
Rep:
Level 82
About

This thread will cover many different aspects of Ruby as a programming language, with examples being used through RGSS3 to ensure that the information provided is useful for those who write scripts for VX Ace.

It assumes that you have at least some understanding of the basics of Ruby and can happily read words like 'objects', 'arguments' and 'attributes' and have an idea of what I mean.

Whilst not everything is guaranteed to be new to every scripter, as this depends on their own experience, there may just be some parts that are useful nonetheless. I would appreciate feedback from any and all users, especially if there are any parts that I may have misunderstood myself. Part of the reason for doing this is to find out if my own understanding is correct.

Questions are also encouraged, and a post will be reserved following each entry to allow for ease of access to questions and answers relevant to the topic.

It should be noted that RGSS3 uses a version of Ruby that is different to both VX and XP, and so the information may not be appropriate for those. I will not be checking whether the information is in fact useful for RGSS and RGSS2, but do feel free to find out for yourself. I will gladly update posts to reflect which version the information is applicable to, if this is provided.


Topics

Ruby

Structs: Introduction and Custom Behaviour
Objects and Variables Part 1: Objects and Object IDs
Objects and Variables Part 2: Fixed Object IDs
Objects and Variables Part 3: Using Variables and Copying Objects
Aliasing: 'alias' keyword vs 'alias_method'
Code Presentation: {} vs do...end
Clone and Dup
Debugging: Using 'caller'

RGSS3

Empty
Links and topics will be updated as they are posted and are subject to change in name
« Last Edit: March 28, 2013, 05:10:54 PM by LoganF »
(Why do I always feel like it's the end of the world and I'm the last man standing?)

*
Rep:
Level 82
Structs: Introduction and Custom Behaviour

A Struct is a simple way of creating a container than holds a specific set of data, without having to create an explicit class for it.

Creating them is done in the same way as creating a new Class object; by calling the '.new' method on the Class. In the case of Struct, this would be Struct.new().

There are two ways that we can create a Struct object. For the purposes of this post, we will be using a Struct called Point, which will hold an x and y value which refers to a pixel location on the screen.

Code: [Select]
#Way #1:
Struct.new("Point", :x, :y)

#Way #2:
Point = Struct.new(:x, :y)

Now, Structs are a little different to normal classes in that they don't return instances of the Struct class. Instead, they return a new Class object whose name is based on the name given. In Way #1, the identifier (the string) that was given as the first argument is used as the Class name. In Way #2, it is the assigned to the variable on the left hand side of the '='. Do note that using variables to store the Class object will result in that object being bound by scope.

It is important to note that using Way #1 requires that the identifier is a Constant (it must begin with a capital letter). Struct.new("point", :attr) will not compile. This is because the identifier is added as a constant value in the Struct class itself. For this reason, any Constant name given must also be unique, which will severely limit the choice of names should you use a lot of Structs this way.

The symbols (and they must be symbols) that are provided as arguments to Struct are the names of the attributes that we want our data structure to contain. They are created as accessors, and thus can be both written to and read from.

With our Struct Class objects defined, we can now make instances of these Classes.

Depending on which way we have defined them, we will have to change how we are to create these instances:

Code: [Select]
#Way 1:
p1 = Struct::Point.new(0, 0) #p1 is a Point in the top left corner at x = 0, y = 0

#Way 2:
p2 = Point.new(50, 50) #p2 is a Point in at x = 50, y = 50)

The only difference between these two methods is that in Way #1 we have to go through the Struct Class to get to our defined Point Class object.

In the two examples above, we have created our two Points in two different locations by providing two arguments. If you recall, our first defined attribute was :x and the second was :y. When we create our objects in the above manner, the attributes are assigned the values in the same order that they were defined.

Note: For the remainder of the post we will assume that we have used Way #2.

Now, when we create an instace of our Struct Class object, Point, we don't have to assign to all of the attributes we defined at once. All attributes are optional, and our passed in arguments will be assigned from left to right, as is standard in Ruby when passing in arguments.

Code: [Select]
p1 = Point.new(5)
puts p1 # #<struct Point :x = 5, :y = nil>

p2 = Point.new(5, 10)
puts p2 # #<struct Point :x = 5, :y = 10>

This does mean that for a Struct object which contains many attributes, we will need to assign all of the preceding attributes or at least provide some argument to them. But because our attributes are created as accessors, we can access individual attributes directly.

Code: [Select]
p1 = Point.new # an instance where all attributes are nil
p1.x = 10
puts p1 # #<struct Point :x = 10, :y = nil>

p2 = Point.new
p2.y = 15
puts p2 # #<struct Point :x = nil, :y = 15>

What is interesting about Structs, outside of their simple implementation of a data class object, is the methods that are inherent to the Struct class and through that are accessible by our Class objects that we create.

The Struct class provides us with a variety of methods that can be found in both Arrays and Hashes. Namely, the following:

Spoiler for:
Code: [Select]
== 
eql? 
hash 
to_s 
inspect 
to_a 
values 
size 
length
each
each_pair
[] 
[]= 
select 
values_at 
members

Because of these, we can use Structs in a similar way to Arrays and Hashes.

For example, we can access and assign our attributes like we would an Array or Hash with the []= method:

Code: [Select]
p1 = Point.new

#accessing :x as an element in an Array
p1[0] = 5  # #<struct Point :x = 5, :y = nil>

#accessing :x as a key in a Hash
p1[:y] = 10 # #<struct Point :x = 5, :y = 10>

What is interesting is that whilst we define our attributes using symbols, we can access that attribute with a string format:

Code: [Select]
p1["x"] # 5
p1["y"] # 10

We can also write out the members ('keys' in Hashes), or the values that correspoind to those members.

Code: [Select]
p p1.members   # [:x, :y]
p p1.values    # [10, 15]

We can also iterate through each pair of members and values in the same way we would with Hashes:

Code: [Select]
p1.each_pair { |m, v| p [m, v] }
# [:x, 10]
# [:y, 15]

With this additional functionality already built it, Structs make for a good alternative to an explicit class definition when it comes to containing data. They also come with some of the usefulness of a Hash with the addition of being able to hardcode attribute names unlike with Hash objects, which can result in less errors in your program.

But wait, there is still another very interesting aspect to Structs that differs from Hashes, and that is the ability to define additional methods within a particular Struct Class object that we have created.

Let's say that we want a way to determine the distance between two given points. Because we might know that we want this functionality ahead of creating our objects, we may decide to write an expicit class that implements a method for this purpose. But that's all that we want from our class. We could easily have a class definition that looks like this:

Code: [Select]
class Point
 
  attr_accessor :x, :y
 
  def distance_to(dest_point)
    Math.sqrt((self.x - dest_point.x) ** 2 + (self.y - dest_point.y) ** 2)
  end
 
end

This would allow use to take a Point object and find the distance_to another specified Point object. But by defining our own class, we lose all of that free functionality that comes with Struct.

One possibility is to inherit from a Struct Class object in a custom class:

Code: [Select]
class Point < Struct.new(:x, :y)
 
  def distance_to(dest_point)
    Math.sqrt((self.x - dest_point.x) ** 2 + (self.y - dest_point.y) ** 2)
  end
 
end

But doing something like is can be a potential waste of resources that offers very little in terms of advantages.

What we would like is a way to add that one method but still have a Struct without having to resort to the above definition. Luckily, Structs can do that with little problem. Though this is undocumented, Structs are capable of taking a block of code that will be used and added to the Class that is subsequently created.

Code: [Select]
Point = Struct.new(:x, :y) {
  def distance_to(dest_point)
    Math.sqrt((self.x - dest_point.x) ** 2 + (self.y - dest_point.y) ** 2)
  end
}

You'll note that I use curly braces for my blocks, but you can also use do...end which for some is more preferable. There is actually some story behind whether to use { } or do...end, and that is something I will touch on in a later post.

Back on track, we can now call the method distance_to from a Point instance:

Code: [Select]
p1 = Point.new(0, 0)
p2 = Point.new(10, 0)

p p1.distance_to(p2) # 10.0  (which is what we would expect)

And because this is still based on a Struct object, we can continue to use methods like #members and #each_pair without having to implement our own versions in a custom defined class.
(Why do I always feel like it's the end of the world and I'm the last man standing?)

*
Rep:
Level 82
{Reserved for Q&A, if required}
(Why do I always feel like it's the end of the world and I'm the last man standing?)

***
Rep:
Level 70
RMRK Junior
This should be stickied because Knowing is half the Battle!  +Rep

Heretic's Vehicles XP (Boat and Magic Carpet)

Heretic's Collection XP Ver 2.3 - Updated to include Dynamic Lighting, Moving Platforms, Vehicles, and much much more!

pokeball TDSOffline
***
Rep:
Level 84
-T D S-
Silver - GIAW 11 (Hard)Silver - Game In A Week VII
This is pretty good Logan. Something like this could have saved me quite a lot of time when I was first experimenting with Structs. So I am sure it will help a lot of scripters.

*
RMRK's dad
Rep:
Level 86
You know, I think its all gonna be okay.
For going the distance for a balanced breakfast.Project of the Month winner for June 2009For being a noted contributor to the RMRK Wiki2013 Best WriterSilver Writing ReviewerSecret Santa 2013 Participant
I feel confident that this is my best hope for finally figuring out RGSS for myself, at least enough to tinker with the majority of scripts I use.

Well done and Thank You!
:tinysmile:

*
Rep:
Level 82
Objects and Variables Part 1

I'm going to be splitting this particular topic up into a number of parts for ease of following and understanding. Whilst we are only looking at a small part of handling objects and variables, it's often easier if the topic is built up slowly rather than all at once.

The overall goal is to build an awareness to how objects are stored and how variables work to provide access to these objects. Sometimes it's quite easy to think you know what is going to happen when you write out some code, but are then surprised when it does something you didn't quite expect. There were times when I fell into this kind of trap and it wasn't until I learned more that I understood why.


Notice: For this topic, and indeed any topic, should you wish to execute the code provided you should have the "Show Console" option checked on. It is very uncommon that I will print out text to the message box, and so to use this code directly will require the console window is shown during Play Test.



So, the first thing we are going to look at is objects. We aren't going to be using any variables of our own and are relying simply on the program itself. This particular topic doesn't feature much code and should be considered as more of an information session. We will start to look at code when we come to Part 3 and variables.

As you should hopefully already know, everything in Ruby is based around the concept of objects. No matter how primitive or complex, there is an object at the heart of it. Things like strings and numbers, whether integers or float, are objects. Even the concept of true and false have their own specific objects.

It is one thing about Ruby that makes it a relatively simple language to come in to. Everything is a thing and, for most of us, our lives involve "things" that we interact with. And that's what we have with Ruby. A language where everything is a thing that can be interacted with.

Now, I wanted to make that analogy because it will be useful later on. Whilst these topics aren't aimed at teaching the basics, and I pretty much expect readers to know these basics beforehand, they are designed to give understanding and that may require a little step back to make sure it is as clear as it can be.

Moving on, what I want to introduce is something that every object (and thus everything in Ruby) has and that is an object ID number. For all intents and purposes, this is the number that Ruby uses to know where, in memory, that particular object is so that we can make use of it. And because Ruby code is (ideally) designed to be quite intuitive to use, we can hazard a good guess at how we would find this number. We will use the string, "hello", to check this out:

Code: [Select]
puts "hello".object_id

With .object_id we can find what ID number has been given to this particular string instance. With these basic types, Ruby gives us the ability to create them without having to explicitly call .new on our required class. With strings, we can simply declare what characters are in our string and Ruby will automagically create us a String object with the value that we declared. This is a fairly basic standard in most programming languages and Ruby is no different. The same occurs with types such as numbers.

If you run that one line in the script editor in RGSS3, you will get an integer value printed in the console window. These object IDs are always integers, and are (and I say this slightly loosely as I will explain later) unique to each object instance. If they weren't unique, Ruby wouldn't be able to retrieve the correct object instance that we want.

So our string, "hello", is a unique instance of the String class. We can write that code on two lines, the second an exact copy, and we will get a different result for the object_id of each string.

Note: These values will change each time you run your code, and by reading the Extra Knowledge section that you will come across shortly you will understand why. As such, the values that are used in this post are based on those that are given to me. They will not be the same (except in extremely rare cases).

Code: [Select]
puts "hello".object_id  #70544290
puts "hello".object_id  #70544170

Whilst almost the same number they are different and thus proves that, despite the string being given the same content value, it has a different instance id. And that's to be expected, and is a really good thing. There's no reason why we can't have two different strings that have the same characters in them. And if we wanted to change one of those strings to something else, we definitely wouldn't want it to change the other. So it's good that our strings, and objects, have unique numbers.

This same thing happens if we make instances of our own custom objects.

Code: [Select]
class A
  #empty class for demonstration
end

#.new creates a new instance of A, and object_id is called on that instance immediately. Thus no variable is required.
puts A.new.object_id  #69922610

So, all of our object instances all have a unique object_id and you can see that for yourself if you mess around with the code a little. You could write "puts A.new.object_id" 10 times and check the results. You will not come across a number twice.


This marks the end of Part 1. Part 2 can be found by clicking here.

Extra Knowledge

Whilst this topic will continue in subsequent parts, I want to include the first Extra Knowledge section here. These are designed to provide a little extra insight into what is going on. They aren't necessarily useful to know, but just tidbits that I, at least, find interesting. Feel free to take a read, and then we will look at object IDs for some special objects in Part 2.

If you wish to experiment on your own until Part 2 is uploaded, we will be looking at Integers, True, False and Nil. We will then look at Symbols and why we should use these values whenever appropriate.

Spoiler for Extra Knowledge:
You may recall that I said that, for all intents and purposes object_id gave an integer that Ruby uses to know where in memory that instance was located. The reason I say this as such, is that the number we get isn't strictly and directly the location in memory. Locations in memory are recorded as hexadecimal values. Our ID, which is an integer (which itself is a decimal value) is not one of. But indirectly, this value is the location in memory, and it's Ruby that takes converts the value to the other (memory to object_id and vice versa).

Let's use a bit of code to help us understand this conversion.

Code: [Select]
class A
  #an empty class for demonstration purposes
end

a = A.new #create a new instance of the class, A
puts a.object_id  #outputs the instance's id number
puts a    #outputs the object type and memory location

If you use that code, you will likely get different values and that's is ok and is expected. For this, I will use the values that I get when I run this code.

So, I get the following:
object_id : 70413110
memory loc: 0x864d66c

If we remove the 0x part, which tells us that the value is hexadecimal, we get the value 864d66c.

I'm going to show this in an easier way first and then, before the end of post, I will tell you what is actually going on underneath.

Simple Way

Let's first convert that number into decimal:

864d66c in decimal is 140826220.

This still isn't the same number as our object_id. But we can almost see instantly the difference between the two.

The value for object_id is half of the value we just converted.

140826220 / 2 = 70413110

Is that really all that object_id is? Half of the decimal value of the memory location. Just to be sure, let's run the code again.

object_id  = 70449680
memory loc = 0x865f420

memory loc as decimal = 140899360

140899360 / 2 = 70449680

It sure seems so. So, whilst I can't say for certain, it looks like our object_id really is a memory location. It just requires a little bit of conversion with an algorithm and that's perfectly acceptable. When it comes to creating a unique value, it often is good to use an algorithm to essentially encode our values based on some other input. In this case, the actual memory location is used to generate an object_id.

As I mentioned I wanted to say what was most likely going on with this algorithm. As you may know, computers tend to work in binary rather than decimal. Whilst the above, simple, method appears to be the way that the object_id is found, this isn't likely to be the case. It wouldn't make sense. It's faster for a computer to operate on binary than it is converting to a format it doesn't even work with. Looking at it from a decimal point of view makes it easier for us to understand, but this is not the case for your computer. More likely it does something far simpler.

This method is called bit shifting. In this case, bit shifting to the right. By shifting all of the bits in a binary number we will create the effect of dividing by half. On a similar not, shifting to the left has the effect of doubling it and we will see that in Part 2.

Let's use an example. We will begin with decimal and then look at it from a binary point of view.

We will take the number 1234. If we divide that by 2, we will get the result 617. Now let's look at it from a binary point.

1234, in binary, is 0100 1101 0010. 1234 is the same as 1024 + 128 + 64 + 16 + 2, which is really what that binary number says.

If we bit shift this 1 bit to the right, then we are essentially moving all of the binary digits to the right 1 position: 0010 0110 1001. This new number is 512 + 64 + 32 + 8 + 1, or 617.

So what is likely to be happening when turning a memory address into an object ID is that the hexadecimal memory address is converted to binary which is then bit shifted once to the right. What you will likely notice is that no memory address will be an odd number, which is a sensible assumption if you know anything about the way that memory is used and allocated. In the event that you did bit shift an odd number to the right however, you will simply lose the 1's bit. Thus, 123 will become 61.
« Last Edit: March 30, 2013, 12:21:10 PM by LoganF »
(Why do I always feel like it's the end of the world and I'm the last man standing?)

*
Rep:
Level 82
{ Reserved for potential Q&A }
(Why do I always feel like it's the end of the world and I'm the last man standing?)

*
*crack*
Rep:
Level 64
2012 Most Unsung Member2012 Best NewbieFor frequently finding and reporting spam and spam bots
Definitely knew what New was doing, never thought about things this way though. Didn't realise there was an object_id attr either.
Keep 'em coming Logan :)
All of my scripts are totally free to use for commercial use. You don't need to ask me for permission. I'm too lazy to update every single script post I ever made with this addendum. So ignore whatever "rule" I posted there. :)

All scripts can be found at: https://pastebin.com/u/diamondandplatinum3

*
Rep:
Level 97
2014 Most Unsung Member2014 Best RPG Maker User - Engine2013 Best RPG Maker User (Scripting)2012 Most Mature Member2012 Favorite Staff Member2012 Best RPG Maker User (Scripting)2012 Best MemberSecret Santa 2012 ParticipantProject of the Month winner for July 20092011 Best Veteran2011 Favourite Staff Member2011 Most Mature Member2011 Best RPG Maker User (Scripting)2011 Best Use of Avatar and Signature Space2010 Most Mature Member2010 Favourite Staff Member
Nice job Logan.

*
Rep:
Level 82
Updated the Extra Knowledge section of Objects and Variables Part 1.

The reason is fairly simple. It wasn't really finished. I realised whilst I was getting ready to sleep that it was more appropriate to demonstrate the memory address to object ID conversion through bit shifting than through decimal arithmetic, which is a far more likely event considering how computers actually work. Additionally, I need to bring up bit shifting in Part 2 and felt it relevant to introduce the concept earlier.

Also, thanks for the support guys. Hopefully it's interesting enough to read. I know I tend to write a lot in my posts. Any suggestions on topics or presentation, do feel free to leave a comment.
« Last Edit: January 30, 2013, 02:35:18 PM by LoganF »
(Why do I always feel like it's the end of the world and I'm the last man standing?)

*
Rep:
Level 82
Objects and Variables Part 2

The previous part of this particular topic demonstrated object_id on objects of String as well as custom classes that we may define. Whilst I didn't cover them, even objects of Bitmap, Sprite, Game_Character and almost all of the other classes you can find in the script editor will follow a similar procedure.

I left the last post stating that we would look at some special objects in this next part. By special, I am referring to integers (specifically Fixnums), FalseClass, TrueClass and NilClass.

Those last 3 might seem a little strange but they do exist. In scripting, we tend to get these classes through the use of the respective keywords: true, false and nil. What these keywords do simply point to the object of the same type. Using code such as:

Code: [Select]
result = true

makes result point to an object of type TrueClass.

Note: I will use variables briefly to illustrate these points but we will not be looking at variables themselves just yet.

Now, I call these special because, unlike the objects we looked at previously, these only exist once in the whole program. Even if we do something like:

Code: [Select]
result_one = true
result_two = true
result_three = true

we only ever have 1 instance of TrueClass. If we call object_id on each of those variables:

Code: [Select]
puts result_one.object_id    # 2
puts result_two.object_id    # 2
puts result_three.object_id  # 2

Each of those variables, and thus each 'true' is actually the same object. The same is the case for FalseClass, NilClass and all Integers, albeit with different object_id between them. These are what we would consider immutable objects.

Let's get to looking into showing some of these qualities.

Firstly, we can only ever have 1 instance, and this has already been done for us when we first run our program. Because of this restriction, we cannot do this:

Code: [Select]
FalseClass.new
TrueClass.new
NilClass.new
Integer.new
Fixnum.new
Bignum.new
Float.new

Trying any of those will result in an undefined method error.

Floats and Bignums are stored in allocated memory like any other object type and will have similar object_ids to the Strings we saw earlier. However, Fixnums are special with regards to their object_ids just like true, false and nil.


Fun Fact: Integers in Ruby
Spoiler for Fun Fact:
In most cases, a standard integer value is a 32-bit value that may or may not be signed (can be positive or negative with the first Most Significant Bit representing which sign). For a signed and unsigned integer this gives us the range of usable values:

Signed: ?2,147,483,648 to 2,147,483,647
Unsinged: 0 to 4,294,967,295

With Ruby, however, whilst Fixnums are 32-bit values, only 31-bits are used, in which you get no ability to change between signed or unsigned. This limits our Fixnum range to:

-1,073,741,824 to 1,073,741,823

Half of that of a 32-bit value.

Any integer value outside of that range will automatically be converted into a Bignum by Ruby (and will be allocated its own memory spot).


So let's take a look at our first 3 class types, FalseClass, TrueClass and NilClass or false, true and nil respectively as keywords. Because of the way that Ruby works, we can use these keywords like objects:

Code: [Select]
puts false.object_id   # 0
puts true.object_id    # 2
puts nil.object_id     # 4

Those are very different IDs to our Strings in the previous part. They aren't memory address either, even if we convert them. These are what makes these particular objects a little different and more special.

The reason why these objects have such strange IDs is actually quite simple: optimization. Because these types are very commonly used, Ruby stores these specially to improve performance. If you think about it, there really isn't a need for true or false to behave differently depending on the object instance and so it makes sense to allow for only one instance that is easy to find and get access to.

In the same respect, integers of Fixnum type are also commonly used (or at least far more likely than those which you would see in Bignums) and are, as such, stored in a similar fashion.

Code: [Select]
#Quickly output the object IDs for each value from 0 to 10
for i in 0..10
  puts i.object_id
end

# 1      is the object ID of 0
# 3      is the object ID of 1
# 5      is the object ID of 2
# 7      is the object ID of 3
# 9      is the object ID of 4
# 11     is the object ID of 5
# 13     is the object ID of 6
# 15     is the object ID of 7
# 17     is the object ID of 8
# 19     is the object ID of 9
# 21     is the object ID of 10

You'll quickly notice a pattern here. Each ID output is an odd number. You should also notice that the pattern is (Fixnum * 2) + 1.

Another reason for these particular objects being special is that, when you do assign them to variables it works a little differently. We will look into that in Part 3. Following the Extra Knowledge section below, we will look at symbols and their object IDs and why we should use them over strings or other objects.


Extra Knowledge
Spoiler for Extra Knowledge:
If you read the Extra Knowledge section in Part 1, you may notice that multiplying by 2 is the opposite of dividing by 2. In this case, bit shifting to the left has the effect of doubling the binary value. After shifting, we simply add 1 - or change the last bit into a 1.

As an example, let's take the Fixnum 5.

In binary, the Fixnum 5 is 1001 (4 + 1) preceded by 27 0s:
000 0000 0000 0000 0000 0000 0000 0101.

If we first shift to the left (ie. double it):
0000 0000 0000 0000 0000 0000 0000 1010

We get 10 (8 + 2). The space that opened as a result of moving the last 1 to the left is filled with 0s as necessary.

It's object id is (5*2)+1 or 11. In binary (as a 32-bit value):
0000 0000 0000 0000 0000 0000 0001 0011

As you can see, we moved the 1's from the first binary number to the left by 1 bit each. Then we added 1 (or changed the very last bit (the Least Significant Bit) which was a 0 after shifting.

Continuing On...

Symbols are a very useful tool in Ruby, and for a very good reason. They help improve performance and reduce the need for allocating extra memory when we use the same symbol name twice, even for different purposes.

Let's take a look at symbols with our object ids.

Code: [Select]
:symbol_one.object_id  #358408
:symbol_two.object_id  #358488

Even after restarting and rerunning the program, these numbers are the same. They are also noticeably smaller than the Strings we used in Part 1. This is because the name of the symbol is converted into an ID. Symbols, like our earlier types are also immutable objects. This is why they help to improve on runtime speed, and why symbols are incredibly important to use as keys in a Hash, rather than Strings. It has the same effect as using integers, but with the added bonus of being more meaningful because we can use characters instead of only digits.

To further demonstrate that these IDs are the same can be seen below.

We will use Strings to perform a case/switch operation. I will use some variables again, but as before I won't be covering much on them just yet.

Code: [Select]
first_array = ["Today", "Yesterday", "Tomorrow"]
second_array = ["Today", "Yesterday", "Tomorrow"]

first_array.each do |item|
  #Output the string value
  puts item
  #output object_id of item
  puts item.object_id
  #find matching item in second_array and output the object_id of that match
  case item
  when second_array[0]
    puts second_array[0].object_id
  when second_array[1]
    puts second_array[1].object_id
  when second_array[2]
    puts second_array[2].object_id
  end
end

#Output

# Today
# 69980460    <- first_array
# 69980420    <- second_array
# Yesterday 
# 69980450
# 69980410
# Tomorrow
# 69980440
# 69980400

What we have here are two arrays that contain exactly the same string values within them. But as we will see from the output, each of those 6 strings are their own object instance despite having the same characters (this we saw in Part 1).

This time, we can do the same thing but with symbols.

Code: [Select]
first_array = [:Today, :Yesterday, :Tomorrow]
second_array = [:Today, :Yesterday, :Tomorrow]

first_array.each do |item|
  puts item
  #output object_id of item
  puts item.object_id
  #find matching item in second_array and output the object_id of that match
  case item
  when second_array[0]
    puts second_array[0].object_id
  when second_array[1]
    puts second_array[1].object_id
  when second_array[2]
    puts second_array[2].object_id
  end
end

# Output

# Today
# 358538
# 358538
# Yesterday
# 358618
# 358618
# Tomorrow
# 358698
# 358698

Even if we were to use those same symbol names in another script, the value of the object_id will remain the same. Thus, comparing these two is simply a matter of comparing their object_id values.

We can also format our symbols to look like strings:

Code: [Select]
:"Today"
:"Yesterday"
:"Tomorrow"

and they will behave in exactly the same way. They even have the same object_id as their not string format counterparts. Thus :"Today" is the same as :Today.


This marks the end of Part 2. Part 3 can be found by clicking here.

« Last Edit: March 30, 2013, 12:25:32 PM by LoganF »
(Why do I always feel like it's the end of the world and I'm the last man standing?)

*
RMRK's dad
Rep:
Level 86
You know, I think its all gonna be okay.
For going the distance for a balanced breakfast.Project of the Month winner for June 2009For being a noted contributor to the RMRK Wiki2013 Best WriterSilver Writing ReviewerSecret Santa 2013 Participant
Brain is 'splode. I like it.
:tinysmile:

*****
Rep:
Level 84
This text is way too personal.
Bronze - GIAW 11 (Hard)Silver - GIAW Halloween
Very interesting Logan. A few conclusions can be brought out of this, and though I might be wrong, I think its safe to assume that the object_id of a regular class is always an even number, and that Ruby caps the integer value at 31 bits before converting to Bignum so the object_id can be stored as a standard, simple, 32-bit integer via the bit-shifting procedure, rather than an ugly 64-bit integer of whatever.

The information about symbols is also very interesting. I always knew that symbols were more efficient than strings, but I never understood why. It's nice to finally know.

Anyways, I'm enjoying these series of articles and I'm looking forward to more!

*
Rep:
Level 82
Very interesting Logan. A few conclusions can be brought out of this, and though I might be wrong, I think its safe to assume that the object_id of a regular class is always an even number, and that Ruby caps the integer value at 31 bits before converting to Bignum so the object_id can be stored as a standard, simple, 32-bit integer via the bit-shifting procedure, rather than an ugly 64-bit integer of whatever.

This is what I understand to be the case. Because of the "bit shift to the left and add one" that occurs for the Fixnum IDs, these all become odd integer values. This leaves the even number IDs available for all of the other particular symbols.

One thing that you can do, and is something I will add to the next post is that you can reverse the process. For example, try this in RGSS3:

Code: [Select]
puts ObjectSpace._id2ref(0)

where the argument is the object_id. If you try to this with an odd integer ID, like 357, you will get the corresponding Fixnum. Similarly, if you do something like this:

Code: [Select]
puts ObjectSpace._id2ref("Hello".object_id)

the output is the particular object who has that object_id.

Trying to do this with a value that doesn't have a corresponding object, for example the object_id 200 will produce an interesting error: "0x0000c8 is not id value". What is going on at the low level, I can't be certain. But it is a goal to try to find material that will help to understand it, if any really exists. With Ruby being commonly seen as a high level language, it might be difficult to find.

For more on ObjectSpace, you can check the ruby-doc page.

Quote
The information about symbols is also very interesting. I always knew that symbols were more efficient than strings, but I never understood why. It's nice to finally know.

Anyways, I'm enjoying these series of articles and I'm looking forward to more!

Thanks for the support, Cozzie. As I noted earlier, if you do have any interesting questions about Ruby or RGSS3 that you aren't sure about, let me know. I'll do some research and see what I can come up with. Chances are it will either be or will lead to something interesting to discuss.
(Why do I always feel like it's the end of the world and I'm the last man standing?)

*
Rep:
Level 97
2014 Most Unsung Member2014 Best RPG Maker User - Engine2013 Best RPG Maker User (Scripting)2012 Most Mature Member2012 Favorite Staff Member2012 Best RPG Maker User (Scripting)2012 Best MemberSecret Santa 2012 ParticipantProject of the Month winner for July 20092011 Best Veteran2011 Favourite Staff Member2011 Most Mature Member2011 Best RPG Maker User (Scripting)2011 Best Use of Avatar and Signature Space2010 Most Mature Member2010 Favourite Staff Member
Hey, I wanted to thank you again for doing this. I had not previously known about the []= method of Struct, and I just had an opportunity to use it in a recent script. So thanks!

*
Rep:
Level 82
Hey, I wanted to thank you again for doing this. I had not previously known about the []= method of Struct, and I just had an opportunity to use it in a recent script. So thanks!

It's a shame that Structs aren't well documented, even officially as of 1.9.3. They are quite powerful tools for data objects. Added with the fact that you can define new methods, overwrite existing ones and even alias them, there's a lot of potential for them.

It's been fun learning about these things. I plan to continue with the next part this weekend.

Feel free to share your experiences, too. I can only imagine them being incredibly useful and may influence future articles.

Thanks again for the commendation.
(Why do I always feel like it's the end of the world and I'm the last man standing?)

****
Rep:
Level 57
Miiiiaf =^+^=
GIAW 14: ParticipantParticipant - GIAW 11
I really think you are a great teacher for scripting.. I, once more, repped you :)

Gimme more infoooooossss :yuyu:


As blonde as can be!!!
Doesn't mean I'm the dumb one...
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Spoiler for supporting!:


*
Rep:
Level 82
Objects and Variables Part 3

A quick recap before we continue on.

In Part 1, we looked at #object_id and learned something about how objects are handled in memory (at least in a very basic way in terms of referencing).

Part 2 examined this a little closer with certain types of objects, and we found out that types such as FixNums, Nil and True/False weren't objects as such, but rather that they have unique IDs and only exist as singleton instances. We also learned that symbols are a very handy thing to use because they behave in much the same way, thus preventing us from taking up new, unreserved memory addresses.

Part 3 is going to introduce variables to this understanding, and we will be able to see how they are passed around and referred to by other sections of code.



Note: Due to the length of this post, I have split it up into sections.

Spoiler for Section 1: Object Referencing:
Let's create us a simple bit of code to begin with.

Code: [Select]
hello = "hello"
number_array = [1, 2, 3, 4]
letter_array = ["a", "b", "c"]

five = 5
symbol = :symbol

Quickly, we will use #object_id and get those IDs for our reference:

Code: [Select]
p hello.object_id           # 69693730
p number_array.object_id    # 69693710
p letter_array.object_id    # 69693670

p five.object_id            # 11
p symbol.object_id          # 312408

I've split these up into two groups. There is a reason for that which I will explain. For now, let's write some more code.

Code: [Select]
copy_hello = hello
copy_number_array = number_array
copy_letter_array = letter_array
copy_five = five
copy_symbol = symbol

All we are doing here is creating another variable and assigning them to the value stored in the variables made earlier. Again, let's call our #object_id:

Code: [Select]
p copy_hello.object_id          # 69693730
p copy_number_array.object_id   # 69693710
p copy_letter_array.object_id   # 69693670

p copy_five.object_id           # 11
p copy_symbol.object_id         # 312408

You'll notice that the object_ids are the same for the new variables. What this tells us is that only one instance of each object exists. These were created, only once, in the first code block. When we ask to assign the value of an existing variable to a new variable we aren't asking to create a new object with the same value. What we really have in our variables is the address location to the object that was created, or more likely, the object_id of the object.

Whilst we can't see it, the variables hello and number_array contain references to the address that their associated object exists in. We can express this concept in the form of an image (excuse the crappy Paint images).

Spoiler for image:


The image is representative, but do not take it as a literal thing. What it shows is a way to think about variables and the objects they are referring to. If you imagine a variable is a tag or label with a string connecting the label to a box that contains the actual object itself, you have a good concept of how I think about the way the interaction is working.

The image shows the state of the program after we have run all of the above code: the initial object initialization, the assignment to the original variable hello and the assignment of the original variable to the copy variable copy_hello.

In this way, when we assign to a variable, we are connecting the name to the object, or a label to a thing.

Spoiler for Section 2: Passing as Parameters:
Of the many things we can do with variables, one of the biggest uses lies in passing them to methods. Let's take a look at what happens here.

Code: [Select]
def my_method(in_variable)
  p in_variable.object_id
end

my_method(hello)

Very simple. What happens here, is that the parameter variable in_variable is assigned the value of the passed in variable (the argument) hello. As we know from our image representation, the id of the object is (more than likely) passed over rather than the actual object.

The result of the printed message is 69693730, which tells us that the object referred to by both parameter and argument is the same.

Let's try the following code:

Code: [Select]
def my_method(in_variable)
  p in_variable
end

my_method(hello)
p hello

This time, we are going to working with the actual object being referred to, rather than the reference ID. This code outputs the following:

Code: [Select]
"hello"
"hello"

Very simple. The first "hello" is from p in_variable, and the second is from p hello.

If we decided to do something like this:

Code: [Select]
def my_method(in_variable)
  in_variable = "hello world"
  p in_variable
end

my_method(hello)
p hello

we get a different result. Here, the output will be:

Code: [Select]
"hello world"
"hello"

While we did pass in the object referred to by the argument variable, we assigned in_variable to a new string object, "hello world". This doesn't change the object that hello is referring to. This is because we gave in_variable a new assignment or a new object to refer to. In an image, it would look like this:

Spoiler for image2:


However, something like this:

Code: [Select]
def my_method(in_variable)
  in_variable.upcase!
end

my_method(hello)
p hello

will give the output:

Code: [Select]
"HELLO"

The difference is that we aren't working with just the label, but we are working on the object that is being referred to by in_variable directly.

This is something important to be mindful off when handling variables. Whilst we are just using the names, like we have in every other code snippet, we are operating only on the name and what it is referring to.

In the code above, we are going beyond just the name of the variable and we are now treating the name as an object by calling the #upcase! method. Because we have two variables both referring to the same "hello" object, operating on one of those variables as an object will cause the changes to be reflected in any other variable that is referring to that particular object.

Another example, a little more complex:

Code: [Select]
def my_method(in_variable)
  in_variable.each_with_index { |n, i| in_variable[i] = n + 1 }
end

p number_array
p number_array.object_id

my_method(number_array)

p number_array
p number_array.object_id

In my_method, we are taking each element of number_array and increasing each of integers by 1. The outputs of the 'p's is:

Code: [Select]
[1, 2, 3, 4]
69693710

[2, 3, 4, 5]
69693710

The #object_ids tell us that the object is the same one before and after my_method is called. Whilst we may not have referenced the object by the variable it was initially assigned to, when we pass the argument in and create the parameter "in_variable", we are creating another way to reference the array object. By acting upon a variable, we are actually acting upon the object that it references.

Spoiler for Section 3: Copying objects:
It is important to keep a check of what ties you have to any object, in the event that you unintentionally change it. This is even more true when you have complex code as you may not immediately see the reason.

But what if we, for some reason, want to modify the object we are passing as an argument but we don't want to permanently change the original object. Well, there is a way of doing this.

Code: [Select]
def my_method(in_variable)
  in_variable[0] = 10
  p in_variable
end

p number_array

my_method(number_array.dup)

p number_array

The outputs are:

Code: [Select]
[1, 2, 3, 4]
[10, 2, 3, 4]
[1, 2, 3, 4]

The object that number_array points to is the original array, [1, 2, 3, 4]. When we pass our argument, we are passing a duplicate version of number_array:

Code: [Select]
my_method(number_array.dup)

The paramter, in_variable, is given a reference to the duplicated object, not the original. If we had a picture, it might look like this:

Spoiler for image3:


If we check the object_ids behind these two variables:

Code: [Select]
p number_array.object_id  # 69693710
p in_variable.object_id   # 69693800

We will see two different IDs, meaning each object is its own unique instance. In the event that we want to pass in a version of the object which we can modify temporarily and not affect the original, we can make use of the #dup method. This is a method which comes with objects without having to explicitly write one. Should we want to we can overwrite the behaviour of #dup and #clone, and in some cases we will need to.

I will cover that aspect in a future post.


Extra Knowledge
Spoiler for Extra Knowledge:

There is also another method we can use: #clone. This, too, comes with classes by default, and this, too, will have a similar effect.

Code: [Select]
def my_method(in_variable)
  in_variable[0] = 10
  p in_variable
  p in_variable.object_id
end

p number_array
p number_array.object_id

my_method(number_array.clone)

p number_array

With the same code, the outputs on the 'p's will be the same.

Code: [Select]
[1, 2, 3, 4]  #number_array
69693710 #number_array.object_id
[10, 2, 3, 4] #in_variable
69693820 #in_variable.object_id
[1, 2, 3, 4] #number_array

So what exactly is the difference between #dup and #clone? On the face of it, they appear to do the same thing, and that's almost true. But there are differences.

Considering the code:

Code: [Select]
obj = Object.new

def obj.foo
  42
end

p obj.foo

obj is a very simple instance of Object. We have extended it to include the #foo method, which simply returns 42. obj is now referencing a singleton object. If I was to make another Object object, it would not have the additional method, #foo.

Running this code will output:

Code: [Select]
42 # obj.foo

Let's try this out with #clone and #dup, and see what happens.

Code: [Select]
cloned = obj.clone
duped = obj.dup

p cloned.foo
p duped.foo

Ouputs:

Code: [Select]
42  #cloned.foo
NoMethodError   #duped.foo

Whilst we may have expected to get the same result, what we ended up with was an error when calling #foo on the object being referred to by duped.

This is because of one big difference between #clone and #dup.

#dup creates a new instance of the object and copies to it the data values that dup has.

If we were to have the following code:

Code: [Select]
class MyObject
  attr_accessor :bar
end

myobj = MyObject.new
myobj.bar = 5

myduped = myobj.dup

p myduped.bar

The output is:

Code: [Select]
5  # myduped.bar

myobj and myduped are variabled which reference two different instances of MyObject objects, with the instance behind myduped being given the same values (as of calling obj.dup) that the instance behind myobj has.

The same thing happens with #clone with regards to values being copied.

The difference lies in the fact that clone also copies singleton methods that belong to an object. In the earlier code, the singleton method #foo also gets copied. If we were to examine the instance methods of duped and cloned, we would be able to see this difference.

Code: [Select]
obj = Object.new
def obj.foo
  42
end

cloned = obj.clone
duped = obj.dup

p cloned.singleton_methods
p duped.singleton_methods

Output:

Code: [Select]
[:foo]  #cloned
[] #dup

There is also another difference between #dup and #clone.

Objects also come with internal states. In particular, we have the 'tainted' state and the 'frozen' state. We can set these states to true or false, and can check to see the current value of these states in our code. For example, objects which are 'frozen' cannot be modified. When using the two copy methods, #dup only copies the 'tainted' state of an object, whereas #clone will copy both 'tainted' and 'frozen' states.

Code: [Select]
class MyObject
  attr_accessor :bar 
end

myobj = MyObject.new
myobj.bar = 5
myobj.freeze

myobj.bar = 10

The #freeze method sets the frozen state of the myobj instance to true, preventing any values within the instance to be modified. When we try to change myobj.bar to 10, we will get an error: "can't modify frozen object". This is handy when we are passing around objects and don't want code to modify code without us explicitly knowing and agreeing (by unfreezing the state).

If we expand this code to include #dup and #clone, we can see the other difference.

Code: [Select]
class MyObject
  attr_accessor :bar 
end

myobj = MyObject.new
myobj.bar = 5
myobj.freeze

duped = myobj.dup
cloned = myobj.clone

duped.bar = 10
p duped.bar

cloned.bar = 15
p cloned.bar

The outputs here:

Code: [Select]
10 #duped.bar
Error: Can't modify frozen object #cloned.bar = 15

Thus, any object that has been #dup'ed will not copy the frozen state of the object being copied, whereas #clone will preserve this state. This is important to keep in mind should you wish to pass frozen objects to methods which may attempt to change internal data without your knowing or permission. Here, #dup will not maintain the frozen state and your data may just get changed unknowingly.
(Why do I always feel like it's the end of the world and I'm the last man standing?)

*
*crack*
Rep:
Level 64
2012 Most Unsung Member2012 Best NewbieFor frequently finding and reporting spam and spam bots
Whoa, that was a big read :|
I can now see the difference between dup and clone, always thought it was strange for there to be two methods that do the exact same thing.

Good job :yuyu:
All of my scripts are totally free to use for commercial use. You don't need to ask me for permission. I'm too lazy to update every single script post I ever made with this addendum. So ignore whatever "rule" I posted there. :)

All scripts can be found at: https://pastebin.com/u/diamondandplatinum3

*
Rep:
Level 82
Whoa, that was a big read :|
I can now see the difference between dup and clone, always thought it was strange for there to be two methods that do the exact same thing.

Good job :yuyu:

Chances are, if two different things appear to do the same thing they don't. This is something that comes up in other areas of Ruby as well. For example, alias and alias_method. Aside from syntax, they don't look to do anything different. But in the next post, I'll be able to show that they do, in fact, work differently in the end (at least from what I've seen).

One problem with learning Ruby through RGSS is that most of the basic classes are not documented, and even when it comes to the official documentation, it can be a pain to find and then understand what is going on. I came across this snippet of code which helped to see the difference.

Code: [Select]
  # Generic shallow copy of object.
  #
  # Copies instance variables, but does not recursively copy the
  # objects they reference. Copies taintedness.
  #
  # In contrast to .clone, .dup can be considered as creating a
  # new object of the same class and populating it with data from
  # the object.
  #
  # If class-specific behaviour is desired, the class should
  # define #initialize_copy and implement the behaviour there.
  # #initialize_copy will automatically be called on the new
  # object - the copy - with the original object as argument
  # if defined.
  #
  def dup
    copy = Rubinius::Type.object_class(self).allocate

    Rubinius.invoke_primitive :object_copy_object, copy, self

    Rubinius::Type.object_initialize_dup self, copy
    copy
  end

  # Direct shallow copy of object.
  #
  # Copies instance variables, but does not recursively copy the
  # objects they reference. Copies taintedness and frozenness.
  #
  # In contrast to .dup, .clone can be considered to actually
  # clone the existing object, including its internal state
  # and any singleton methods.
  #
  # If class-specific behaviour is desired, the class should
  # define #initialize_copy and implement the behaviour there.
  # #initialize_copy will automatically be called on the new
  # object - the copy - with the original object as argument
  # if defined.
  #
  def clone
    # Do not implement in terms of dup. It breaks rails.
    #
    copy = Rubinius::Type.object_class(self).allocate

    Rubinius.invoke_primitive :object_copy_object, copy, self
    Rubinius.invoke_primitive :object_copy_singleton_class, copy, self

    Rubinius::Type.object_initialize_clone self, copy

    copy.freeze if frozen?
    copy
  end

Which comes from the Rubinius implementation of Ruby. Where possible, this version is written in Ruby, and when it's not possible it's written in C++.

http://rubini.us/

Could be worth a look through the source documentation for some of those, more, hard to figure out methods.
(Why do I always feel like it's the end of the world and I'm the last man standing?)