Ren'Py Data storage in renpy

dustbeen43

Newbie
Feb 19, 2023
71
38
I have issues in my game because data (variables who are declare with 'default' and change while the player progress) are not saved correctly. Yesterday someone introduce to me the concept of 'renpy.retain_after_load()' but calling it right before calling a screen or after cause the same issue. I've made a test project to illustrate how I currently code my game and how to reproduce the issue. Apparently renpy is not able to store data after the first level of an object or perhaps there is something that I've missed ^^

Here is the code to reproduce :
Code:
init python:
    class MySubCustomObject:
        subvalue1 = 1
        subvalue2 = "init sub value"
        def changeSubValue(self):
            self.subvalue2 = "new sub value"

    class MyCustomObject:
        value1 = 1
        value2 = "init value"
        subValue = MySubCustomObject()
        def changeValue(self):
            self.value2 = "new value"

    def preventScreenPredictionAction1():
        MyCustomObjectPointer.value1 = 2
        MyCustomObjectPointer.changeValue()
        MyCustomObjectPointer.subValue.subvalue1 = 2
        MyCustomObjectPointer.subValue.changeSubValue()
        renpy.hide_screen("QuickMenuActionScreen")
        renpy.jump("step2")

default MyCustomObjectPointer = MyCustomObject()
define e = Character("Eileen")
label start:
    scene bg room
    show eileen happy
    $ renpy.retain_after_load()
    call screen QuickMenuActionScreen

label step2:
    e "When you're here save and reload"
    e "Now load the previous state and from console what's the value of my object ?"
    e "'MyCustomObjectPointer.value1' = 2 (OK)"
    e "'MyCustomObjectPointer.value2' = new value (OK)"
    e "'MyCustomObjectPointer.subValue.subvalue1' = 1 (KO)"
    e "'MyCustomObjectPointer.subValue.subvalue2' = init sub value (KO)"
    return

screen QuickMenuActionScreen:
    frame:
        xsize 200
        ysize 200
        xpos 200
        ypos 200
        vbox:
            xalign 1.0
            ypos 45
            textbutton "action1" action Function(preventScreenPredictionAction1)
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,958
16,192
Yesterday someone introduce to me the concept of 'renpy.retain_after_load()'
This function is designed to not update a variable when you load a save file, therefore at the exact opposite of your save issue. Before using a function someone pointed out, take at least the time to read what the documentation say about it, to be sure that it effectively correspond to your need:
" "


Code:
[...]
Okay, what is your real issue and how do you really want the objects to behave ?

Because your "step2" label is really confusing. The values you give as references correspond to the values that are effectively saved, but not to the values of the object when you save it.
And there's a reason for this difference, but since you used the designed leading to this, and are saying that the effectively saved values are correct, I don't know where's the error ; yet, as usual I have a strong guess.

Is it that the values aren't correct when you do a "live load" (loading the save without quitting Ren'Py) (what a test show), or is it that the saved values are incorrect (what you are saying).
And depending on the answer, the solution isn't the same.


Now, as I said, I have a strong guess, so, does this solve your issue:
Python:
    class MySubCustomObject( renpy.python.RevertableObject):
        def __init__( self ):
            self.subvalue1 = 1
            self.subvalue2 = "init sub value"
        def changeSubValue(self):
            self.subvalue2 = "new sub value"

    class MyCustomObject( renpy.python.RevertableObject):
        def __init__( self ):
            self.value1 = 1
            self.value2 = "init value"
            self.subValue = MySubCustomObject()
        def changeValue(self):
            self.value2 = "new value"
or is it this that solve it:
Python:
    class MySubCustomObject( renpy.python.RevertableObject):

        def __init__( self ):
            self.subvalue1 = 1
            self.subvalue2 = "init sub value"

        def changeSubValue(self):
            self.subvalue2 = "new sub value"

    class MyCustomObject( renpy.python.RevertableObject):

        def __init__( self ):
            self.value1 = 1
            self.value2 = "init value"
            self.subValue = MySubCustomObject()

        def __setstate__( self, state ):
            self.__dict__ = state
            self.subValue = MySubCustomObject()

        def changeValue(self):
            self.value2 = "new value"
Note: my guess goes for the first since it correspond to what you say, while the second correspond to what "step2" values are saying.



Now, all this being said, I recommend you to learn how before you continue to works on your project. Doing this before trying to use them would have saved you a lot of time, especially if the solution is effectively solved by the sole use of the __init__ meta method.
 

dustbeen43

Newbie
Feb 19, 2023
71
38
Thanks for your answer.

Ar first, concerning the 'renpy.retain_after_load()', I've read the official documentation and I've just misread it. English is not my natural language so I make mistake when writing and reading it. But you're right about RTFDM but in my case, I did it.

Secondly, what I'm trying to do is when I develop the game, I test it constantly. But with the way I declared the object, the modification made between the declaration and the save are different. So when I reload the game, I loose the progression and it make me loose a lot of time.

The __init __ doesn't solved the issue by the way. I'm OK with class in python but I'm learning Ren'py for 1 month now so it's kinda new to me.

Now the question for me is what does the "renpy.python.RevertableObject" inside your class declaration means ?

Python:
    class MySubCustomObject( renpy.python.RevertableObject):
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,958
16,192
But with the way I declared the object, the modification made between the declaration and the save are different. So when I reload the game, I loose the progression and it make me loose a lot of time.
It's were having an answer to my question regarding how you want the objects to behave would have been useful.

Here's what you get when you quit Ren'Py, then restart and load the object:
You don't have permission to view the spoiler content. Log in or register now.
As you can see, the values are all corrects accordingly to what your "step2" label is saying.
And to reach this result without needing to quit Ren'Py first, you need the second code I provided ; therefore both the __init__ and __setstate__ meta methods.

And here's are the value you get when you load without quitting:
You don't have permission to view the spoiler content. Log in or register now.
Those values also are the ones of the object when you save. Therefore if you don't load right after the save, but continue through the "step2" label, it's what you'll see.

And here lie the contraction. You are saying that the values aren't correctly saved, but also expecting values that don't correspond to the values when the object is saved.

So, once again, what behavior are you expecting ?



I'm OK with class in python
I don't want to be rude, but no you aren't.

When you declare a class like you did:
Python:
   class MyCustomObject:
        value1 = 1
        value2 = "init value"
        subValue = MySubCustomObject()
Both "value1", "value2" and "subValue" are class attributes.


For "value1" and "value2", it's not a problem. When you later use either objectName.value1 or self.value1 to change their value, you create an object attribute that will override the class one.

But like "subValue" is an object, you don't address it directly. Therefore:
  • Every single object of the "MyCustomObject" class will share the exact same values for "subValue". Change the value for one object, the value will change for all the object.
  • When pickled (therefore stored into a file), "subValue" will be ignored because related to the class, and not to the object.
This is Python behavior, and to it have to be added Ren'Py behavior:
  • When loading with a living game, "subValue" will always have the current values, since it's the values hosted by the class.
  • When loading with a fresh game, "subValue" will always have the default values, once again because it's the values hosted by the class.
Be noted that "Ren'Py behavior" is a way to speak. Any Python script that would use an external file to store/read the values would have exactly the same behavior.

You can take a look by yourself.
Instead of looking what is the value of MyCustomObjectPointer.subValue.subvalue2, look what is the value for the class itself, therefore for MyCustomObject.subValue.subvalue2. You'll see that they are the same.


That you missed the storing/reading specificity, I can get it. But the classes you designed clearly show that you are not "OK with class in Python".
One don't declare class attributes to later use them exclusively as object attributes. And one don't wonder why the class attributes have a strange behavior when they act as expected from them.

The one of the first thing, if not the very first, you learn about Python classes is that you MUST use the __init__ meta method to declare your attributes:
" "
" "
" "
" "
And so on. Some are more or less correct in their terms (Python have attributes not properties, a method isn't a function, and Python class constructor is __new__, __init__ is the initializer), but all say the same.


but I'm learning Ren'py for 1 month now so it's kinda new to me.
And despite what it can looks like, the errors are not due to Ren'Py, but strictly to Python. As I said, any Python script using your two classes as they are designed, and storing/reading their value would have the exact same behavior.
And this, anyone who learned about class attributes (because it's rarely taught) also know about it.


Now the question for me is what does the "renpy.python.RevertableObject" inside your class declaration means ?
That the class inherit from the renpy.python.RevertableObject class ; a class that add rollback compliance to the default object class.
Please, tell me that your "OK with class in Python" at least include the notion of inheritance...





I try, I really try, but sometimes it's really challenging...
 
  • Like
Reactions: Richday

dustbeen43

Newbie
Feb 19, 2023
71
38
There is a lots of thing to process here and I will look into it.

That the class inherit from the renpy.python.RevertableObject class ; a class that add rollback compliance to the default object class.
Please, tell me that your "OK with class in Python" at least include the notion of inheritance...
Ok probably I assume a lots of things because I don't use python often and my main coding language is C# so I just clone my behavior from C# to Python but inheritance and polymorphism are principes that I'm familiar with.


I try, I really try, but sometimes it's really challenging...
You did great and I will look into all you've given me, you've not wasted your time and I appreciate your help and guidance.