Failed to clear the links between 2 objects having instance variables referring to each other

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,790
Reaction score
943
First Language
Chinese
Primarily Uses
N/A
The script of interest is Damage Sequence.

The below parts are what I'm talking about:

class Spriteset_Battle def leds_update_actor_sprites $game_party.battle_members.each_with_index { |m,i| sprite = @actor_sprites next if sprite.nil? m.leds_sprite = sprite sprite.leds_battler = m } end end

Now I've encountered a situation where I've to make a deep copy for a battler, and the only way I know is to use Marshal.

As the battler has an instance variable which is a sprite, Marshal will try to copy that sprite as well, which will lead to crashes as sprites can't be serialized.

To solve this, I'll have to use another variable to store that sprite and clear that instance variable of that battler right before using Marshal, then restore that instance variable of both the original and copied battler via the variable storing that sprite right after using Marshal.

Attempt 1

If only that sprite has issues with Marshal, I could do something like this:

sprite = battler.leds_spritebattler.leds_sprite = nilclone = Marshal.load(Marshal.dump(battler))battler.leds_sprite = clone.leds_sprite = sprite
However, I've also encountered similar issues with some other custom scripts as well. Using the above solution can lead to an exceptionally long list of instance variables to be cleared and restored due to the potentially ever increasing number of such custom scripts. Also, if any of those instance variables changes, my compatibility fix will have to change too.

Attempt 2

This motivates me to write a general solution that will automatically get rid of all instance variables that can't be serialized:

1. Use DoubleX RMVXA Object Trace to find all objects that can't be serialized and are linked to the object to be serialized -

Related parts of the configuration -

# * Object manipulations |# 1. trace_obj(cond, label) |# - Traces all objects meeting cond method linked to this object |# - Labels all traced objects using label method |# - cond and label are method symbols in Object Trace Condition Method |# and Object Trace Label Method respectively |# 2. obj_trace[cond] |# - Returns all traced objects meeting cond method linked to this object |# - cond is a method symbol in Object Trace Condition Method |
Code:
    #--------------------------------------------------------------------------|    #  Object Trace Condition Method                                           |    #  - Setups cond used by trace_obj(cond, label)                            |    #--------------------------------------------------------------------------|    # cond must be the symbol of a method taking the currently traced object as    # the only arguement    # The below examples are added to help you setup your own cond methods    # Checks if the currently traced object belongs to klass    def self.cond_klass(obj)      obj.is_a?(Sprite)    end # cond_klass
Code:
    #--------------------------------------------------------------------------|    #  Object Trace Label Method                                               |    #  - Setups label used by trace_obj(cond, label)                           |    #--------------------------------------------------------------------------|    # label must be the symbol of a method taking the currently traced object as    # the only arguement    # The below examples are added to help you setup your own label methods    # Labels all traced objects using their class symbol    def self.label_klass(obj)      obj    end # label_klass
Related parts of the implementation -

class Object # Edit #----------------------------------------------------------------------------| # New public instance variable | #----------------------------------------------------------------------------| attr_reader :obj_trace # The traces of all objects linked to this object # (v1.01a+)The list of symbols of all instance variables added by this script OBJ_TRACE_IVAR = [:"@obj_trace"] # cond: The object trace condition method symbol taking the object as argument # label: The object trace label method symbol taking the object as argument def trace_obj(cond, label) # New # Stop tracing the object if the object trace path would be cyclic (@obj_trace ||= {})[cond] ? return : @obj_trace[cond] = {} # trace_instance_obj(cond, label) return trace_array_obj(cond, label) if is_a?(Array) return trace_hash_obj(cond, label) if is_a?(Hash) return trace_range_obj(cond, label) if is_a?(Range) trace_struct_obj(cond, label) if is_a?(Struct) end # trace_obj # cond: The object trace condition method symbol taking the object as argument # label: The object trace label method symbol taking the object as argument def trace_instance_obj(cond, label) # New (instance_variables - OBJ_TRACE_IVAR).each { |ivar| trace_all_obj(cond, label, ivar, instance_variable_get(ivar)) } end # trace_instance_obj # cond: The object trace condition method symbol taking the object as argument # label: The object trace label method symbol taking the object as argument def trace_array_obj(cond, label) # New each_with_index { |val, index| trace_all_obj(cond, label, index, val) } end # trace_array_obj # cond: The object trace condition method symbol taking the object as argument # label: The object trace label method symbol taking the object as argument def trace_hash_obj(cond, label) # New each { |key, val| trace_all_obj(cond, label, key, val) } end # trace_hash_obj # cond: The object trace condition method symbol taking the object as argument # label: The object trace label method symbol taking the object as argument def trace_range_obj(cond, label) # v1.00b+; New # Embeds the klass traces of all ranges linking to this object index = -1 each { |val| trace_all_obj(cond, label, index += 1, val) } # end # trace_range_obj # cond: The object trace condition method symbol taking the object as argument # label: The object trace label method symbol taking the object as argument def trace_struct_obj(cond, label) # v1.00b+; New each_pair { |key, val| trace_all_obj(cond, label, key, val) } end # trace_struct_obj #----------------------------------------------------------------------------| # Label and use all nonempty subtrees to form the original object trace tree| #----------------------------------------------------------------------------| # cond: The object trace condition method symbol taking the object as argument # label: The object trace label method symbol taking the object as argument # iks: The index/key/symbol of the object trace # val: The object to be traced def trace_all_obj(cond, label, iks, val) # v1.01a+; New # Recursively traverse the object trace tree using Depth First Search ot = DoubleX_RMVXA::obj_Trace @obj_trace[cond][iks] = [ot.send(label, val)] if ot.send(cond, val) val.trace_obj(cond, label) return if (trace = val.obj_trace[cond]).empty? (@obj_trace[cond][iks] ||= []) << trace # end # trace_all_objend # Object
2. Use the below snippet to clear all those traced objects from the object to be serialized:

Original algorithm implementation -

class Object  #----------------------------------------------------------------------------|  #  Clears all traced objects meeting cond method linked to this object       |  #----------------------------------------------------------------------------|  # tree: All traced objects meeting cond method linked to this object  def clear_obj(tree) # New    @clear_obj ? return : @clear_obj = true    # Recursively traverse the object trace tree using Depth First Search    tree.each { |iks, val|      if iks.is_a?(Symbol) && instance_variable_get(iks)        instance_variable_get(iks).clear_obj(val[-1])        instance_variable_set(iks, nil) if val.size > 1      elsif is_a?(Array) || is_a?(Hash) || is_a?(Struct)        self[iks].clear_obj(val[-1])        self[iks] = nil if val.size > 1      end    }    #    @clear_obj = nil  end # clear_obj  #----------------------------------------------------------------------------|  #  Restores all traced objects meeting cond method linked to this object     |  #----------------------------------------------------------------------------|  # tree: All traced objects meeting cond method linked to this object  def restore_obj(tree) # New    @restore_obj ? return : @restore_obj = true    # Recursively traverse the object trace tree using Depth First Search    tree.each { |iks, val|      if iks.is_a?(Symbol) && instance_variable_get(iks)        instance_variable_set(iks, val[0]) if val.size > 1        instance_variable_get(iks).restore_obj(val[-1])      elsif is_a?(Array) || is_a?(Hash) || is_a?(Struct)        self[iks] = val[0] if val.size > 1        self[iks].restore_obj(val[-1])      end    }    #    @restore_obj = nil  end # restore_objend # Object
Test Case Version -

class Objectdef clear_obj(tree) # NewFile.open("diagnose.txt", "a") { |file|file.print("\npre clear_obj - tree: ", tree, " @clear_obj: ", @clear_obj, "\n")}@clear_obj ? return : @clear_obj = truetree.each { |iks, val|File.open("diagnose.txt", "a") { |file|file.print("clear_obj - iks: ", iks, " val: ", val, "\n")}if iks.is_a?(Symbol) && instance_variable_get(iks)File.open("diagnose.txt", "a") { |file|file.print("pre clear_obj - iks: ", iks, " instance_variable_get(iks): ", instance_variable_get(iks), "\n")}instance_variable_get(iks).clear_obj(val[-1])next if val.size <= 1instance_variable_set(iks, nil)File.open("diagnose.txt", "a") { |file|file.print("post clear_obj - iks: ", iks, " instance_variable_get(iks):", instance_variable_get(iks), "\n")}elsif is_a?(Array) || is_a?(Hash) || is_a?(Struct)File.open("diagnose.txt", "a") { |file|file.print("pre clear_obj - iks: ", iks, " self[iks]: ", self[iks], "\n")}self[iks].clear_obj(val[-1])next if val.size <= 1self[iks] = nilFile.open("diagnose.txt", "a") { |file|file.print("post clear_obj - iks: ", iks, " self[iks]: ", self[iks], "\n")}end}File.open("diagnose.txt", "a") { |file|file.print("post clear_obj - tree: ", tree, " @clear_obj: ", @clear_obj, "\n")}@clear_obj = nilend # clear_objdef restore_obj(tree) # NewFile.open("diagnose.txt", "a") { |file|file.print("\npre restore_obj - tree: ", tree, " @restore_obj: ", @restore_obj, "\n")}@restore_obj ? return : @restore_obj = truetree.each { |iks, val|File.open("diagnose.txt", "a") { |file|file.print("restore_obj - iks: ", iks, " val: ", val, "\n")}if iks.is_a?(Symbol) && instance_variable_get(iks)instance_variable_set(iks, val[0]) if val.size > 1File.open("diagnose.txt", "a") { |file|file.print("restore_obj - iks: ", iks, " instance_variable_get(iks): ", instance_variable_get(iks), "\n")}instance_variable_get(iks).restore_obj(val[-1])elsif is_a?(Array) || is_a?(Hash) || is_a?(Struct)self[iks] = val[0] if val.size > 1File.open("diagnose.txt", "a") { |file|file.print("restore_obj - iks: ", iks, " self[iks]: ", self[iks], "\n")}self[iks].restore_obj(val[-1])end}File.open("diagnose.txt", "a") { |file|file.print("post restore_obj - tree: ", tree, " @restore_obj: ", @restore_obj, "\n")}@restore_obj = nilend # restore_objend # Object

I used the below simplified test case:

Test Case Situation Replication -

test_battler = Game_Battler.newtest_sprite = Sprite_Battler.new(Viewport.new)test_battler.leds_sprite = test_spritetest_sprite.leds_battler = test_battler
Test Case 1 Specification -

test_battler.leds_sprite = nilFile.open("diagnose.txt", "a") { |file| file.print("Test Case 2:\n") file.print("test_battler.leds_sprite = nil\n") file.print("test_battler.leds_sprite: ", test_battler.leds_sprite, "\n") file.print("test_sprite.leds_battler.leds_sprite: ", test_sprite.leds_battler.leds_sprite, "\n")}test_battler.trace_obj:)cond_klass, :label_klass)File.open("diagnose.txt", "a") { |file| file.print("test_battler.obj_trace[:cond_klass]: ", test_battler.obj_trace[:cond_klass])}
Test Case 1 Result -

Test Case 1:test_battler.leds_sprite = niltest_battler.leds_sprite:test_sprite.leds_battler.leds_sprite:test_battler.obj_trace[:cond_klass]: {}
Test Case 2 Specification -

test_battler.trace_obj:)cond_klass, :label_klass)File.open("diagnose.txt", "a") { |file| file.print("Test Case 1:\n") file.print("test_battler.obj_trace[:cond_klass]: ", test_battler.obj_trace[:cond_klass], "\n") file.print("================================================================================")}test_battler.clear_obj(test_battler.obj_trace[:cond_klass])File.open("diagnose.txt", "a") { |file| file.print("================================================================================\n") file.print("test_battler.leds_sprite: ", test_battler.leds_sprite, "\n") file.print("test_sprite.leds_battler.leds_sprite: ", test_sprite.leds_battler.leds_sprite, "\n")}test_battler.obj_trace.cleartest_battler.trace_obj:)cond_klass, :label_klass)File.open("diagnose.txt", "a") { |file| file.print("\ntest_battler.obj_trace.clear\n") file.print("test_battler.obj_trace[:cond_klass]: ", test_battler.obj_trace[:cond_klass])}
Test Case 2 Result -

Test Case 2:test_battler.obj_trace[:cond_klass]: {:mad:leds_sprite=>[#<Sprite_Battler:0x4e093c8>, {:mad:leds_battler=>[{...}]}], :mad:result=>[{:mad:battler=>[{...}]}]}================================================================================pre clear_obj - tree: {:mad:leds_sprite=>[#<Sprite_Battler:0x4e093c8>, {:mad:leds_battler=>[{...}]}], :mad:result=>[{:mad:battler=>[{...}]}]} @clear_obj: clear_obj - iks: @leds_sprite val: [#<Sprite_Battler:0x4e093c8>, {:mad:leds_battler=>[{:mad:leds_sprite=>[...], :mad:result=>[{:mad:battler=>[{...}]}]}]}]pre clear_obj - iks: @leds_sprite instance_variable_get(iks): #<Sprite_Battler:0x4e093c8>pre clear_obj - tree: {:mad:leds_battler=>[{:mad:leds_sprite=>[#<Sprite_Battler:0x4e093c8>, {...}], :mad:result=>[{:mad:battler=>[{...}]}]}]} @clear_obj: clear_obj - iks: @leds_battler val: [{:mad:leds_sprite=>[#<Sprite_Battler:0x4e093c8>, {:mad:leds_battler=>[...]}], :mad:result=>[{:mad:battler=>[{...}]}]}]pre clear_obj - iks: @leds_battler instance_variable_get(iks): #<Game_Battler:0x4e09814>pre clear_obj - tree: {:mad:leds_sprite=>[#<Sprite_Battler:0x4e093c8>, {:mad:leds_battler=>[{...}]}], :mad:result=>[{:mad:battler=>[{...}]}]} @clear_obj: truepost clear_obj - tree: {:mad:leds_battler=>[{:mad:leds_sprite=>[#<Sprite_Battler:0x4e093c8>, {...}], :mad:result=>[{:mad:battler=>[{...}]}]}]} @clear_obj: truepost clear_obj - iks: @leds_sprite instance_variable_get(iks):clear_obj - iks: @result val: [{:mad:battler=>[{:mad:leds_sprite=>[#<Sprite_Battler:0x4e093c8>, {:mad:leds_battler=>[{...}]}], :mad:result=>[...]}]}]pre clear_obj - iks: @result instance_variable_get(iks): #<Game_ActionResult:0x4e09738>pre clear_obj - tree: {:mad:battler=>[{:mad:leds_sprite=>[#<Sprite_Battler:0x4e093c8>, {:mad:leds_battler=>[{...}]}], :mad:result=>[{...}]}]} @clear_obj: clear_obj - iks: @battler val: [{:mad:leds_sprite=>[#<Sprite_Battler:0x4e093c8>, {:mad:leds_battler=>[{...}]}], :mad:result=>[{:mad:battler=>[...]}]}]pre clear_obj - iks: @battler instance_variable_get(iks): #<Game_Battler:0x4e09814>pre clear_obj - tree: {:mad:leds_sprite=>[#<Sprite_Battler:0x4e093c8>, {:mad:leds_battler=>[{...}]}], :mad:result=>[{:mad:battler=>[{...}]}]} @clear_obj: truepost clear_obj - tree: {:mad:battler=>[{:mad:leds_sprite=>[#<Sprite_Battler:0x4e093c8>, {:mad:leds_battler=>[{...}]}], :mad:result=>[{...}]}]} @clear_obj: truepost clear_obj - tree: {:mad:leds_sprite=>[#<Sprite_Battler:0x4e093c8>, {:mad:leds_battler=>[{...}]}], :mad:result=>[{:mad:battler=>[{...}]}]} @clear_obj: true================================================================================test_battler.leds_sprite: test_sprite.leds_battler.leds_sprite: test_battler.obj_trace.cleartest_battler.obj_trace[:cond_klass]: {:mad:result=>[{:mad:battler=>[{:mad:leds_sprite=>[#<Sprite_Battler:0x4e093c8>, {:mad:leds_battler=>[{...}]}], :mad:result=>[{...}]}]}]}


Test Case 1 indicates that Attempt 1 worked while Attempt 2 failed.

While I know why the former worked, I don't know why the latter failed, nor any remote difference between them when it comes to clearing the test_battler instance variable @leds_sprite.

I tried several hours but I still have absolutely no clue at all, so I think I'll need help for this one(it seems to me that it's the hardest algorithm I've ever tried to solve)
 
Last edited by a moderator:

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,790
Reaction score
943
First Language
Chinese
Primarily Uses
N/A
Rather than trying to make Marshaling work, just look at some other deep cloning solutions. Here's some links I found by Googling 'Ruby Deep Clone':

http://stackoverflow.com/questions/8206523/how-to-create-a-deep-copy-of-an-object-in-ruby

https://github.com/moiristo/deep_cloneable

https://github.com/balmma/ruby-deepclone
Thanks, I'll check that.

However, I'm still interested in the result of Attempt 2:

p(battler) # #<Game_Battler:0x4e09814>p(battler.leds_sprite) # nilp(battler.result.instance_variable_get:)"@battler")) # #<Game_Battler:0x4e09814>p(battler.result.instance_variable_get:)"@battler").leds_sprite) # #<Sprite_Battler:0x4e093c8>According to my understanding, if battler == battler.result.battler(both refering to the same battler #<Game_Battler:0x4e09814>), then battler.leds_sprite == battler.result.battler.leds_sprite, but my above test result just show the opposite to be true(the former referring to nil while the latter referring to #<Sprite_Battler:0x4e093c8>).

What's even more strange to me is that, such cases won't come if I use Attempt 1 but will if I use Attempt 2. i.e.:

# Attempt 1battler.leds_sprite = nilp(battler) # #<Game_Battler:0x4e09814>p(battler.leds_sprite) # nilp(battler.result.instance_variable_get:)"@battler")) # #<Game_Battler:0x4e09814>p(battler.result.instance_variable_get:)"@battler").leds_sprite) # nil# Using instance_variable_set insteadbattler.instance_variable_set:)"@leds_sprite", nil)p(battler) # #<Game_Battler:0x4e09814>p(battler.leds_sprite) # nilp(battler.result.instance_variable_get:)"@battler")) # #<Game_Battler:0x4e09814>p(battler.result.instance_variable_get:)"@battler").leds_sprite) # nilI think I've some fundamental misconception on either how Ruby instance_variable_set works, how Ruby variable reference works, or both, as my current understanding just keep telling me that it should be impossible(i.e., there's no way Attempt 1 works while Attempt 2 fails but that's just how it's) :)

P.S.: In the default RMVXA codebase, Window_EquipItem has this method:

Code:
  def update_help    super    if @actor && @status_window      temp_actor = Marshal.load(Marshal.dump(@actor))      temp_actor.force_change_equip(@slot_id, item)      @status_window.set_temp_actor(temp_actor)    end  end
 
Last edited by a moderator:

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

Latest Threads

Latest Posts

Latest Profile Posts

Just beat the last of us 2 last night and starting jedi: fallen order right now, both use unreal engine & when I say i knew 80% of jedi's buttons right away because they were the same buttons as TLOU2 its ridiculous, even the same narrow hallway crawl and barely-made-it jump they do. Unreal Engine is just big budget RPG Maker the way they make games nearly identical at its core lol.
Can someone recommend some fun story-heavy RPGs to me? Coming up with good gameplay is a nightmare! I was thinking of making some gameplay platforming-based, but that doesn't work well in RPG form*. I also was thinking of removing battles, but that would be too much like OneShot. I don't even know how to make good puzzles!
one bad plugin combo later and one of my followers is moonwalking off the screen on his own... I didn't even more yet on the new map lol.
time for a new avatar :)

Forum statistics

Threads
106,018
Messages
1,018,357
Members
137,803
Latest member
andrewcole
Top