Wavelength

MSD Strong
Global Mod
Joined
Jul 22, 2014
Messages
5,957
Reaction score
5,583
First Language
English
Primarily Uses
RMVXA
I've come across some very strange behavior while modifying one of my scripts.  The script creates a Hash called $lim_shops which tracks inventories for each individual store using the following format: $lim_shops.shops[shop_id][database_object] = number 


This works fine in-game, but when I used pretty standard save/load aliasing to preserve the inventory values when the player comes back to the game, all of the shops' inventories are gone!  After some debugging, it seems that the number for each shop/object combination somehow becomes nil during or after a Load, and I have no idea why this is happening.  The Save itself doesn't seem to trigger this issue.


Here is the DataManager section of my script:


module DataManager

class << self

#--------------------------------------------------------------------------
# * Create Game Objects, including Shop Array
#--------------------------------------------------------------------------
alias :create_game_objects_with_shop_array :create_game_objects
def create_game_objects
create_game_objects_with_shop_array
$lim_shops = Limited_Shops.new
end

#--------------------------------------------------------------------------
# * Create Save Contents, including Shop Array
#--------------------------------------------------------------------------
alias :make_save_contents_with_shop_array :make_save_contents
def make_save_contents
contents = make_save_contents_with_shop_array
contents[:lim_shops] = $lim_shops
$game_variables[8] = contents[:lim_shops].shops[3][$data_weapons[1]] # TEST TEST
contents
end
#--------------------------------------------------------------------------
# * Extract Save Contents, including Shop Array
#--------------------------------------------------------------------------
alias :extract_save_contents_with_shop_array :extract_save_contents
def extract_save_contents(contents)
extract_save_contents_with_shop_array(contents)
$lim_shops = contents[:lim_shops]
$game_variables[6] = contents[:lim_shops].shops[3].size # TEST TEST
$game_variables[7] = contents[:lim_shops].shops[3][$data_weapons[1]] # TEST TEST
# TEST TEST: If this value is nil (it shouldn't be), play the SE
if (contents[:lim_shops].shops[3][$data_weapons[1]] == nil)
Audio.se_play('Audio/SE/Disappointment')
end
end

end

end


As you can see, I added several lines of "debug" code to see what's going on.  At the start of my game, Shop #3 has 200 Hand Axes.  After I save and check the game's variables, Variable 8 is 200, meaning that contents[:lim_shops] seems to be holding the valid, populated Hash.  When I quit and load a game, then check the game's variables, I see that Variable 6 is set to 6, meaning that contents[:lim_shops].shops[3] is correctly holding 6 key-value pairs.  But Variable 7 is still set to 0, and the "Disappointment" SE plays upon Loading to indicate that the value for contents[:lim_shops].shops[3][$data_weapons[1]] is inexplicably nil!


Testing was done in a completely clean project.  I can provide the clean project with the full script if you think it would assist in solving this problem.


Please help me!!  I am at a complete loss as to why the number is still fine after a Save, but is lost after a Load!


Thanks a million for your time.
 

Sarlecc

Veteran
Veteran
Joined
Sep 16, 2012
Messages
453
Reaction score
211
First Language
English
Primarily Uses
RMMV
Instead of checking the size of contents[:lim_shops].shops[3].size I would just do contents[:lim_shops].shops[3].


This should show you the data that is being held in shop[3] without needing additional checks.
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,286
Reaction score
1,459
First Language
English
Primarily Uses
RMXP
It's caused by the serialization followed by the deserialization.


Here's some code illustrating the issue:


# Data object
class Dat
end
dat = Dat.new

# Hash mapping data object to value
test = {}
test[dat] = 10

# Serialize and then deserialize
s = Marshal.dump(test)
t2 = Marshal.load(s)

# Test
p test[dat]
p s
p t2[dat]






Not specifically related to your issue, consider attaching your Limited_Shops object to the Game_System object. Then you get saving/loading automatically.


Using http://pastebin.com/sZdibHyK you can do it with this code


class Game_System
attr_sec_accessor :lim_shops, 'Limited_Shops.new'
end


Note: This does assume that for older saves starting with a new shop is ok.


*hugs*


 - Zeriab
 

Shaz

Global Moderators
Global Mod
Joined
Mar 2, 2012
Messages
41,875
Reaction score
14,525
First Language
English
Primarily Uses
RMMV
Any reason you're storing the actual weapon object instead of just the id (or a code for weapon and the id)?


When you quit, are you actually quitting the game entirely?  Is it possible that the $data_weapons[1] object that's created when you restart the game is not the same object as the $data_weapons[1] object that existed when you saved?  They might both be weapons and have the same id, but they may not be the same object.


For the sake of space, I would not use the weapon object as the key - I'd use a ['w', id] key instead.  That will reduce your file size and possibly get rid of your issue.
 

Wavelength

MSD Strong
Global Mod
Joined
Jul 22, 2014
Messages
5,957
Reaction score
5,583
First Language
English
Primarily Uses
RMVXA
@Sarlecc @Zeriab @Shaz Thank you so much for your kind advice!!  I really appreciate you taking the time to help, as this behavior by the game was beyond my understanding and really, really frustrating me!

Instead of checking the size of contents[:lim_shops].shops[3].size I would just do contents[:lim_shops].shops[3].


This should show you the data that is being held in shop[3] without needing additional checks.



The data in shops[3] is a hash (with objects as keys and integers as values).  This would be useful to check in the console in MV, but in Ace I don't know of any way to print a human-readable version of a complex, non-array object.  I did some reading about Yaml and using JSON with Ruby... surely there's got to be an easier way, though? :)

It's caused by the serialization followed by the deserialization.


Here's some code illustrating the issue:


...



Very interesting!!  I would have never figured this out myself and probably would have needed to give up; this catch of yours was invaluable.


When I tried that code in a browser-based Ruby console:


test[dat] was 10 as expected


s was either a bunch of Bits or some form of object - I'm not sure


t2[dat] was nil, just like the "problem" I was encountering!


This is really good to know.

Not specifically related to your issue, consider attaching your Limited_Shops object to the Game_System object. Then you get saving/loading automatically.


Using http://pastebin.com/sZdibHyK you can do it with this code



class Game_System
attr_sec_accessor :lim_shops, 'Limited_Shops.new'
end



I tried adding your pastebin code and then this Game_System extension to my script - it does indeed stop the inventories from all reverting to nil upon Loading, which is great, but in some scenarios (I think when the window is completely shut down), the values revert to what their original values were at the start of the game.  I believe this is happening because the lim_shops object is being initialized multiple times when maybe we're not expecting it to be (which is not a bug in your solution, but rather a mere incompatability with the rest of my script).


I feel like it would be asking way too much to request that you look through my code and adjust your script to meet my personal needs, so instead let me please ask you a couple questions about the serialization problem.  I think I could probably correct my mistakes if I understood this better.

  • What type of object was I not able to properly de/serialize?  The Hash, the Database Object as a Key, or something different?
  • If I got rid of the key:value hash structure and instead stored the whole $lim_shops object as a multi-level array, would I be able to safely save and load it?
  • Alternatively, if instead of $lim_shops I created an accessible property of $game_party (or some other singleton class) called :limshops and changed every instance of $lim_shops in the script to $game_party.limshops, would that allow me to safely use the hash structure through saves and loads?

Thanks so much!

Any reason you're storing the actual weapon object instead of just the id (or a code for weapon and the id)?


When you quit, are you actually quitting the game entirely?  Is it possible that the $data_weapons[1] object that's created when you restart the game is not the same object as the $data_weapons[1] object that existed when you saved?  They might both be weapons and have the same id, but they may not be the same object.


For the sake of space, I would not use the weapon object as the key - I'd use a ['w', id] key instead.  That will reduce your file size and possibly get rid of your issue.



My understanding was that the database object is always the same but it sounds like my understanding might be wrong, in which case you've caught the reason this is happening. :)   The reason I am using the actual object in most places, rather than a string, is because a lot of the places where I needed to check the inventory, such as do_buy in Scene_Shop, already process a database object, so it's very easy to check against the $lim_shops hash and see what the inventory is for this object in a given store.  However, there is certainly no reason I can't use a code instead if the object is problematic - I already have methods for parsing codes into database objects, since I figured it would be much easier for designers using my script to remember a command like 'stock("i4")' rather than one like 'stock(data_items[4])'.
 

Sarlecc

Veteran
Veteran
Joined
Sep 16, 2012
Messages
453
Reaction score
211
First Language
English
Primarily Uses
RMMV
Its been awhile since I have done much Ruby programming (like several months to a year), so I am not sure if I can help you in making your current hashes more readable.


However I think the easiest way to make your hash more readable would be to simply simplify your hash. The way I would have done the hash myself would have been something like this:

Code:
{stores: [ {storeID: 1, stockItems: {1=> 4, 2=> 7, 8=> 1} }, {storeID: 2, stockArmor: {5=> 4, 91=> 2} } ] }

The stock keys would be the ids of whatever it is selling with the values being the amount that the store has. This has the advantage of only using data you actually need to save versus saving things like item descriptions, stats, buffs , etc. The shop can retrieve all other data from the database just by knowing the id of the item and the type of item.


Take a look at this code for an example on two objects not matching:

Code:
class Dat
  
end

d1 = Dat.new
d2 = Dat.new

p d1.inspect #note that the memory addresses are different
p d2.inspect

if d1 == d2
  p true
else
  p false
end



why does the name of this thread make me think of Hash Browns instead of programming?? :p
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,286
Reaction score
1,459
First Language
English
Primarily Uses
RMXP
It's not the serialization itself that's the issue, but rather that you create two distinct object which has different hash values.


Check out the documentation on hash keys: http://ruby-doc.org/core-2.3.3/Hash.html#class-Hash-label-Hash+Keys


Here's another example for you to play around with:


# Data object
class Dat
attr_accessor :a

def hash
a.hash
end

def eql?(other)
other.is_a?(self.class) && a == other.a
end
end
dat = Dat.new
dat.a = 42

# Hash mapping data object to value
test = {}
test[dat] = 10

# Serialize and then deserialize
s = Marshal.dump(test)
t2 = Marshal.load(s)

# Test
p test[dat]
p s
p t2[dat]




To solve you problem you can either go around implementing adequate eql? and hash methods for the data objects, or you can use identifiers that points to your data objects. [:weapon, 3] could be a key, for example.


@Sarlecc Better than you thinking about hashish, right?


*hugs*
 
Last edited by a moderator:

Latest Threads

Latest Posts

Latest Profile Posts

RPG Maker News #12 | Control Zooming, Customize Bush Effect, Causality & Happy Bones on Steam
Episode 16 let's go! We're making an RPG in RPG Maker MZ! This week... even more mapping, we're almost done with all the mapping for our first dungeon!

I have big news. I just got hired at a Mexican restaurant! They're like Chipotle but a bit different... and my job will be taking orders some days, delivering food other days, and of course, making customers happy. My orientation is on Tuesday. Once I start, I might disappear from here sometimes, but I'll always try to come back!
Batch converting .mp3 to .ogg files is making my computer soooo slow!
When you spend hours working on an optional segment that players will only be in for 2 minutes in a single playthrough. That's the life right there.

Forum statistics

Threads
110,443
Messages
1,053,355
Members
143,518
Latest member
JuneyBug
Top