What is this Ace script doing?

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,111
Reaction score
13,713
First Language
English
Primarily Uses
RMMV
I'm trying to convert an Ace script to MV, and have run into something I can't get my head around.


There's a method like this in the Game_Player class.  The variable is not exposed (no attr_reader or attr_accessor) so the only way to access it is via the method, which I understood made it read-only:

Code:
class Game_Player < Game_Character
  def spare_members
    @spare_members = [] unless @spare_members
    # do some stuff here to manipulate the contents
    @spare_members
  end
end
Later there's a scene that appears to be modifying this data, but I don't see how it can, because it's accessing the method, not the underlying array:

Code:
class Scene_PartySelection < Scene_Base
  def start
    $game_party.members.each{|m|
      $game_player.spare_members << m unless
        $game_player.spare_members.include?(m)
    }
    
    super
    # other stuff here
  end
end


What's going on with this line?

Code:
$game_player.spare_members << m
If @spare_members is not exposed, and spare_members is the method, not the array, how can this scene add stuff to the array?  It's not storing the result in a variable for its own use.


Is it actually doing anything?  It seems that something IS going on, because at some point a party member who is not in @spare_members does appear to be getting added to the array.
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,269
Reaction score
1,423
First Language
English
Primarily Uses
RMXP
The variable is exposed.


Method calls evaluates to a value. Consider it like a function returning value. This is useful for functional oriented programming.


In your particular case note the last line of the spare_members method. That's the result of the method call. Personally I prefer explicitly having return @spare_members because it helps mitigate confusion that can occur, as shown by this topic. Using the return statement has no practical functional meaning here, some advocates for not having one. Depends on what you want to communicate.


Specifically for the line?


$game_player.spare_members << m

# $game_player.spare_members is evaluated first
result_object << m

# << is an infix operator, it's basically just a method call
result_object.<<(m)

# As result_object is an Array the m object will be added to the array




*hugs*


 - Zeriab
 

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,111
Reaction score
13,713
First Language
English
Primarily Uses
RMMV
I got that @spare_members was being returned, but I didn't realize that opened it to change from the outside!  I always thought an instance variable had to have an attr_accessor or a specific method that allowed change, in order to be changed by something outside of the class.


So you're saying the @spare_members line at the bottom of that method is returning the object itself and not a copy of it?
 

Hudell

Dog Lord
Veteran
Joined
Oct 2, 2014
Messages
3,546
Reaction score
3,717
First Language
Java's Crypt
Primarily Uses
RMMZ
I just tried this:


module SomeModule
def self.some_method
@list = [] unless @list
@list
end
end

SomeModule.some_method << 15
SomeModule.some_method << 20

p SomeModule.some_method


And yes, it works!


Maybe it's easier to understand if you think of it this way:


module SomeModule
def self.some_method
@list = [] unless @list
@list
end
end

list = SomeModule.some_method

list << 15
list << 20

p SomeModule.some_method


Even though you used a method to get the array, it is still the same array, so you can add objects to it from any reference to it that you may have.


The same thing works in Javascript:


function SomeModule() {
}
SomeModule.list = [];

SomeModule.someMethod = function() {
return this.list;
};

SomeModule.someMethod().push(15);
SomeModule.someMethod().push(20);

console.log(SomeModule.someMethod());




Edit: Took so long to write this that I was Ninja'ed twice.
 
Last edited by a moderator:

Zalerinian

Jack of all Errors
Veteran
Joined
Dec 17, 2012
Messages
4,696
Reaction score
935
First Language
English
Primarily Uses
N/A
One of the reasons to explain why this works, rather than how this works is because of the way Ruby (and many other scripting languages, including JavaScript) handles objects, like arrays. Ruby has no concept of what a "pointer" is, but in the interpreter that runs the Ruby code, objects are typically stored as pointers. I know you're a developer, Shaz, but I'm not sure how familiar you are with concepts like pointers.


Basically, it's a variable that, instead of holding the value itself, it holds the location in memory of where to find the value. This helps speed up a program because when working with complicated objects, instead of having to copy the complex object to bring it to a function, you just copy the address of it in memory, which is typically about 4 bytes compared to more for the object itself. This also allows functions to modify the original version of the value, rather than copies of it, which is where we're getting to with the code above.


I'd wager that Ruby stores its arrays internally as a pointer, so when the spare_members function returns the @spare_members variable, you are getting the memory address of the actual array in memory. Then when you make a change to that, it ends up going to the value that both the instance variable and the return value of the spare_members function refers to, because @spare_members is just a pointer, and therefore the return value of the function is a pointer, both of which refer to the same data. Making a change to one will affect the other (there are some exceptions, such as reassigning the variable will not change the other variable's data, as that is just saying that the variable name now points to a different position in memory).
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,269
Reaction score
1,423
First Language
English
Primarily Uses
RMXP
So you're saying the @spare_members line at the bottom of that method is returning the object itself and not a copy of it?


Neither.


A reference to the to the object is being returned.


@spare_members is simply a reference to the object. You can make @spare_members point to something else.


Try searching for The Ruby Object Model by Dave Thomas and watch the talk he gives.


There is a naming convention of destructive versus non-destructive methods, but practically you can only really count on methods ending with a ! being destructive, methods without ! are frequently destructive as well.


P.s. attr_accessor is purely syntactic sugar. You can create your own if you want. Here's two examples


http://pastebin.com/sZdibHyK


http://pastebin.com/fRwiLE04


*hugs*


 - Zeriab
 

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,111
Reaction score
13,713
First Language
English
Primarily Uses
RMMV
wow - so much for private variables.


Thanks for the explanations everyone ( @Zalerinian yeah, I'm familiar with pointers, but haven't used them for a long time).  Very informative.  And @Hudell thanks for the JS implementation ... that was going to be my next task, though I wonder if I should just put it inside the method itself, as some of the "massage the data" inside the method does as party members to the array, though under a condition that I'm not sure should ever exist.
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,269
Reaction score
1,423
First Language
English
Primarily Uses
RMXP
Privacy and Ruby?


@variable is called an instance variable rather than a private variable for a reason.


Ruby supports being intrusive if that's what you want to do.

Code:
$game_player.instance_eval('@spare_members')
 
Last edited by a moderator:

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,111
Reaction score
13,713
First Language
English
Primarily Uses
RMMV
Yeah, but you've got to go out of your way to do that.  I was always under the impression that if you wanted to change an object's variable, you needed an attr_accessor, or a specific method that allows you to change it, and outside of methods that let you read them, they weren't accessible.  I didn't realize passing a variable back from a method gave you access to change its contents.


Guess you learn something new every day.
 

Wavelength

MSD Strong
Global Mod
Joined
Jul 22, 2014
Messages
5,635
Reaction score
5,116
First Language
English
Primarily Uses
RMVXA
For whatever it's worth, until I read the answers, I interpreted the code the same exact way you did, Shaz (and was ready to suggest looking for where the array might have been changed elsewhere... LOL!) - as a request to append a value to a method (which would be nonsense) or perhaps to a (deep) copy of its return value.  It never occured to me that a shallow copy/reference would be made and then the underlying (returned) array would be permanently altered.


It's probably the easiest to follow in @Hudell's second example (where the variable "list" is explicitly defined as SomeModule.some_method), though pretty much every post in this topic improved my understanding of objects as return values.  So, just wanted to say great job to the usual suspects answering here who tend to show up and solve a lot of coding threads, and also thanks to @Shaz for airing this problem out in the open; will be great reference for a lot of scripters to have.  Awesome work, you guys!
 

Ixfuru

Veteran
Veteran
Joined
Jul 18, 2012
Messages
40
Reaction score
8
First Language
English
I use to use a lot of attr_accessors, before I got the concept of keeping the variables private.  I use very few now.  I find it best to call a method to change the variable from outside the class.  As far as the 'return' thing.  I too find it better to call use the hard 'return' in my code.  I noticed that RGSS3 doesn't do that as often as RGSS2 did.  This can be particularly confusing if you have a method with case/whens, long if/elsif branches, or for loops because there is likely to be several different ways the method will return the value on several different lines of code.  By using the 'return', you minimize the confusion. 

I've never used the '<<' either.  This example is pretty cool, because in the PartySelection start method, the code is indeed altering what appears to be a private variable.  I may start using this.  It's like performing two methods, (get and set) at the same time.  It means that the 'spare_members' (or any method which returns a resulting array) can be used in this fashion from outside the class.
 
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,269
Reaction score
1,423
First Language
English
Primarily Uses
RMXP
As a conceptual model I rather like the tentacle metaphor.
First of, say we have the following code already specified.


class Game_Members
def spare_members
@spare_members ||= []
end

def set_spare_members(members)
@spare_members = members
end
end
$game_members = Game_Members.new




Consider variable names to be tentacles that grab onto objects, so @spare_members would be a tentacle that grabs onto the array object.
We can define a new tentacle that also grabs the array object using the assignment= operator.


members = $game_members.spare_members
$game_members.spare_members << 4
members << 7

p $game_members.spare_members # => [4, 7]
p members # => [4, 7]




Using the assignment operator we can make the member tentacle grab onto a different object.


members = [2]
members << 10

p $game_members.spare_members # => [4, 7]
p members # => [2, 10]




This is just another object which we also can make the instance variable grab.


$game_members.set_spare_members(members)

p members # => [2, 10]
p $game_members.spare_members # => [2, 10]




What happens when no tentacle no longer hold onto an object? Why, then we cannot access it, and eventually it'll be destroyed by the garbage collector.
More precisely, as long as we can reach a given object through any tentacle path it won't picked up by the garbage collector.


The tentacle metaphor is the best one I've found so far in explaining and depicting how references work. It is still a metaphor though, and there is still the option of digging down into the source code itself: https://www.ruby-lang.org/en/news/2011/07/15/ruby-1-9-2-p290-is-released/


@Wavelength I'm glad you learned something as well. Best part about having discussions in a forum :D


Note that shallow copies is different from passing references around. Try and investigate the difference with this snippet.


$game_members.spare_members.clear
$game_members.spare_members << 3
members = $game_members.spare_members.dup
$game_members.spare_members << 4
members << 7

p $game_members.spare_members # => [3, 4]
p members # => [3, 7]




@Ixfuru Array#<< is practically the same as Array#push
Only difference is that Array#<< only allows one argument. I.e. arr.push(13, 42) is not supported by the << operator.


*hugs*


 - Zeriab
 
Last edited by a moderator:

Engr. Adiktuzmiko

Chemical Engineer, Game Developer, Using BlinkBoy'
Veteran
Joined
May 15, 2012
Messages
14,682
Reaction score
3,003
First Language
Tagalog
Primarily Uses
RMVXA
I think it's also good to note that instance variables are accessible by default even without attr_accessor (which just automates the get and set definition so that you dont need to write them yourself) or making a method that returns the variable manually like on the case of the script that you posted. The Object class actually has default methods for accessing the instance variables


obj.instance_variables -> returns an array of the instance variables of that object


obj.instance_variable_get(var) -> returns that instance variable


obj.instance_variable_set(var,value) -> sets the value of that instance variable to value
 

Users Who Are Viewing This Thread (Users: 0, Guests: 1)

Latest Threads

Latest Posts

Latest Profile Posts

Don't forget, aspiring writers: Personality isn't what your characters do, it is WHY they do it.
Hello! I would like to know if there are any pluggings or any way to customize how battles look?
I was thinking that when you start the battle for it to appear the eyes of your characters and opponents sorta like Ace Attorney.
Sadly I don't know how that would be possible so I would be needing help! If you can help me in any way I would really apreciate it!
The biggest debate we need to complete on which is better, Waffles or Pancakes?
rux
How is it going? :D
Day 9 of giveaways! 8 prizes today :D

Forum statistics

Threads
106,047
Messages
1,018,539
Members
137,834
Latest member
EverNoir
Top