Problems loading an incomplete savefile

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Poru

Hi! I’m having trouble with my loading system, it works fine most of the time but every time I add a new variable to save the game will freeze when it tries to load that new variable since the save doesn’t have it.

This isn’t a really big problem since I just need to delete the savefile and it will work perfectly again, but I was wondering how games with updates that add new things to add to the save file like new achievements get around this, is there a way to just set a non-existing variable to null or do they add a new file?

Edit: The error I get is “Invalid index ‘variableName’ (on base: ‘Dictionary’).”

:bust_in_silhouette: Reply From: jgodfrey

This is certainly not unique to your situation, but is a rather general problem of “data migration” throughout the lifecycle of a software-based product.

There are lots of ways to solve it, though the specifics will depend on the details of your persistence system. Basically, you’ll want to be able to provide DEFAULT values for any items that could be missing in your save file.

It could be as simple as verifying your existingDictionary has a specific key (using has) before trying to access it. If it does, get the value. If it doesn’t, use a default value.

Or, you could…

  • Load the save file.
  • Pre-process the resulting Dictionary to ensure it has all expected values. If it doesn’t, add some additional items with appropriate key/value pairs.

The advantage here is that, once the loaded dictionary has been pre-processed to insert any missing values, you shouldn’t have to worry about other areas of the code running into missing keys…

Thanks a lot!!! I’ll look into assigning the Dictionary default values!

Poru | 2022-12-13 21:46

It’s working perfectly now, thanks!

but I was wondering something, the documentation on dictionaries says you should do it like this:

var variable = value
var dictionary = {
"variable" : variable }

But I tried declaring the variables in the dictionary itself and it works that way too:

var dictionary = {
variable = value }

is there any reason I should avoid using the latter?

Poru | 2022-12-16 13:15

There are some potential nuances there that could get lost in your above “pseudo-code”. Can you post an actual, working example of the above? With that, I’m happy to try and answer your questions…

jgodfrey | 2022-12-16 14:05

Here’s the way I have it right now (I omitted most of the variables in this example so the reply doesn’t fill the entire page):

class_name Progress
extends Reference

const saveFilePath = "user://save.json"

var save = {
	runs = 0,
	scoreTotal = 0,
	scoreBest = 0}

var saveBackup = save.duplicate() #For returning savefile to original values

And here’s how I used to do it(I’m glad I left it commented out in my code just in case):

var runs = 0
var scoreTotal = 0
var scoreBest = 0

var save = {
	"runs" : runs,
	"scoreTotal" : scoreTotal,
	"scoreBest" : scoreBest}

And here’s a couple of examples of how how I’m using it

Loading:

	var data = JSON.parse(content).result

for key in save:
	if not key in data:
		print("The variable " + key + " hasn't been found in the savefile data and has been skiped in the loading process.")
		continue
	
	print(key + ": " + str(data[key]))
	save[key] = data[key]

Using values:

progress.save.runs += 1
progress.save.scoreTotal += $Player.score
if $Player.score > progress.save.scoreBest:
	progress.save.scoreBest = $Player.score
progress.SaveData()

Saving:

func SaveData():
   var jsonString = JSON.print(save)
   file.store_string(jsonString)
   file.close()

This is all I’m using the dictionary for, thanks again for all the help!

Poru | 2022-12-16 14:59

Yeah, that provides some context…

So, back to the question of how to define a dictionary… Really, both ways you show will work (as you know).

With this definition:

var runs = 0

var save = {
    "runs" : runs}

You’ve created both an external variable and a dictionary.

With this definition:

var save = {
    runs = 0}

You’ve created only the dictionary. Both methods create exactly the same dictionary, so unless you want / need the external variable (the first way) for something else in your code, there’s no reason to create it just for the sake of dictionary creation.

Regarding the way you’re handling the “default values”. It looks like you create an in-memory dictionary that contains all keys (and default values) for the current version of the game. Then, when you load the save file, you overlay that dict with the stored values having matching keys - leaving you with either a loaded value from the save file or the default value (when the save didn’t include it).

Well done - that’s what I had in mind with my earlier input.

jgodfrey | 2022-12-16 15:43

I have one last question about my loading system, right now if the save for some reason had the key saved as something like a string or a boolean the game would probably crash, it’s something that shouldn’t happen at any point in the saving process but I would love to know if there’s a way to check if the value from the save is “compatible” with the default value (something like if key.type == fileKey.type) just to make sure a corrupted savefile doesn’t prevent the game from working.

Sorry for taking so much of your time and thank you for all the help you’ve been giving me so far!

Poru | 2022-12-16 15:59

For the type checking, you can use is to test for data types. You may want to add additional type checks (and generally season to taste) but here’s something to get you started…

func check_matching_types(a, b):
	return (a is bool and b is bool) or \
	   (a is int and b is int) or \
	   (a is float and b is float) or \
	   (a is String and b is String)

func _ready() -> void:
	print(check_matching_types(1, 2))
	print(check_matching_types(1, "true"))
	print(check_matching_types("test", "true"))
	print(check_matching_types(true, false))
	print(check_matching_types(1, 2.6))

jgodfrey | 2022-12-16 16:20

thanks! I’ll also add a chek for the array’s length! thanks for all this help!

Poru | 2022-12-16 18:37