[SOLVED] Any way to access store variables from CDS function?

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
boundsoul
Newbie
Posts: 11
Joined: Wed Feb 02, 2022 5:38 pm
Contact:

[SOLVED] Any way to access store variables from CDS function?

#1 Post by boundsoul »

Is there a way to access a variable that has been initialized with "default" or "define" inside a CDS (which itself required "python early")?

I'd like to have something like this (simplified example):

Code: Select all

# File: 01_my_cds_parser.rpy

default my_complex_system_object = SomeComplexClass()
# define my_complex_system_object = SomeComplexClass()  # Could also be "define"-d, honestly, it's going to be static state

python early:
    def parse_my_cds(lexer):
        # ...
        
    def execute_my_cds(parsed_object):
        my_complex_system_object.do_some_complex_stuff(parsed_object)  # <-- Use the store variable here
        
    def lint_my_cds(parsed_object):
        # ...
        
    renpy.register(
        "my_cds",
        parse=parse_my_cds,
        execute_init=execute_my_cds,  # I want to use my CDS to initialize some complex state
        init_priority=10,  # I thought using this would delay its execution until *after* the store was initialized, but no luck
        lint=lint_my_cds
    )
Afterwards, I'd like to use the object in some screens:

Code: Select all

# File: my_screen.rpy

screen my_screen:
    if my_complex_system_object.flag:
        ...
    else:
        ...
Whenever I try to access the variable from the CDS handler code, I get:

Code: Select all

NameError: name 'my_complex_system_object' is not defined
If I try with "store.my_complex_system_object" I get:

Code: Select all

AttributeError: 'StoreModule' object has no attribute 'my_complex_system_object'
My understanding is that Ren'Py will run the "python early" block first, which means the variable hasn't been defined yet, but I need the complex object to be accessible to the rest of the game code. I could initialize the variable inside the "python early" block, but it's my understanding that doing this can cause issues with rollback and serialization (unless I'm mistaken?), so I'm wondering if this is even possible.

Maybe I could create a store itself programmatically instead? Not even sure that's possible, though...

Any help would be greatly appreciated.
Last edited by boundsoul on Sun Apr 07, 2024 11:42 am, edited 1 time in total.

jeffster
Veteran
Posts: 409
Joined: Wed Feb 03, 2021 9:55 pm
Contact:

Re: [HELP] Any way to access store variables from CDS function?

#2 Post by jeffster »

boundsoul wrote: Sat Apr 06, 2024 7:32 pm Is there a way to access a variable that has been initialized with "default" or "define" inside a CDS (which itself required "python early")?
Instead of addressing store variables by name, we can use functions like

Code: Select all

setattr(store, "my_complex_system_object", value)
getattr(store, "my_complex_system_object")
If the object is constant, no need to define it with "default" or "define".
Initializing with "init" or "early" should be sufficient.
"default" is for non-constants that should be saved.

And we probably could use constant objects with properties pointing at variables that would be changed and saved:

Code: Select all

python early:
    class SomeComplexClass():
        def __init__(self):
            self.my_flags = []
    my_complex_system_object = SomeComplexClass()

default my_flags = [False]
init 1 python:
    my_complex_system_object.my_flags = my_flags

screen my_screen:
    if my_complex_system_object.my_flags[0]:
        ...
Though I didn't test how it works.

PS. The idea is that my_flags list would be introduced with "default", so it would be changed and saved.
(Internally in Ren'Py that list would be of type "RevertableSet" or something).

And my_complex_system_object.my_flags would become a pointer to that list. The object itself would be initialized in "python early", so it wouldn't participate in save/load operations.

Not sure that it wouldn't have side effects. (Like maybe "dirty" state of the game when restarted from Main Menu? Or when "my_flags" would be assigned a different object). But that's something that could be taken care of, probably.

boundsoul
Newbie
Posts: 11
Joined: Wed Feb 02, 2022 5:38 pm
Contact:

Re: [HELP] Any way to access store variables from CDS function?

#3 Post by boundsoul »

Code: Select all

def execute_my_cds(parsed_object):
    getattr(store, "my_complex_system_object").do_some_complex_stuff(parsed_object)
Still fails for the same reason that using "store.my_complex_system_object" fails: the CDS code runs when the "python early" block initializes it (though why it runs immediately instead of waiting on the init delay, I have no idea).

I could probably add it via the "setattr()" method, but I'm not sure how safe that is to do, since that's not going through the actual Ren'Py API.
If the object is constant, no need to define it with "default" or "define".
Initializing with "init" or "early" should be sufficient.
I've seen a few posts refer to doing that as the "old style" and that it can lead to issues with saving/loading and rollbacks down the line, though...

jeffster
Veteran
Posts: 409
Joined: Wed Feb 03, 2021 9:55 pm
Contact:

Re: [HELP] Any way to access store variables from CDS function?

#4 Post by jeffster »

boundsoul wrote: Sat Apr 06, 2024 8:34 pm

Code: Select all

def execute_my_cds(parsed_object):
    getattr(store, "my_complex_system_object").do_some_complex_stuff(parsed_object)
Still fails for the same reason that using "store.my_complex_system_object" fails: the CDS code runs when the "python early" block initializes it (though why it runs immediately instead of waiting on the init delay, I have no idea).
Could it be trying to resolve the identifier .do_some_complex_stuff?

There should be ways to abstract the variable part from the functional stuff defined "early". Like maybe

Code: Select all

def execute_my_cds(parsed_object):
    vars(getattr(store, "my_complex_system_object"))["do_some_complex_stuff"](parsed_object)
or

Code: Select all

def execute_my_cds(parsed_object):
    getattr(getattr(store, "my_complex_system_object"), "do_some_complex_stuff")(parsed_object)
?

boundsoul
Newbie
Posts: 11
Joined: Wed Feb 02, 2022 5:38 pm
Contact:

Re: [HELP] Any way to access store variables from CDS function?

#5 Post by boundsoul »

I found the problem. For future reference, here's the fix:

Basically, if you need to access the store, you can't use "execute_init". Per the docs:
execute_init
This is a function that is called at init time, at priority 0. It is passed a single argument, the object returned from parse.
(Emphasis mine.)

In this case, "init time" refers to before the store has actually been fully instantiated. Instead, you can use "execute_default", which will run after an "init" block.

The following code works for me:

Code: Select all

# File script.rpy

init:  # <-- Init blocks have a default "0" priority
    default my_global = "Hello, world!"
    
    my_cds foo  # <-- Custom CDS statement here
    

Code: Select all

# File 01_my_cds_parser.rpy

python early:
    def parse_cds(lexer):
        return None
        
    def execute_cds(parsed):
        print(store.my_global)
        
    def lint_cds(parsed):
        pass
        
    renpy.register_statement(
        "my_cds",
        parse=parse_cds,
        execute_default=execute_cds,
        lint=lint_cds
    )
NOTE: The above code runs in an "init" block, which means it runs before you get to the main menu screen (which is fine for me, since I want it for static data).

boundsoul
Newbie
Posts: 11
Joined: Wed Feb 02, 2022 5:38 pm
Contact:

Re: [HELP] Any way to access store variables from CDS function?

#6 Post by boundsoul »

Worth noting that adding an "init_priority" can be used to use other variables that were initialized with "default" or "define" outside of an "init" block (though it needs to be higher than the default initialization priority). Adding "init=True" can also allow you to forgo the "init" block as well. So this:

Code: Select all

renpy.register_statement(
    "my_cds",
    parse=parse_cds,
    execute_default=execute_cds,
    init=True,  # <-- Implicitly adds an "init" block for you
    init_priority=999,  # <-- 999 just to illustrate the point
    lint=lint_cds
)
Will work with the following code:

Code: Select all

default my_global = "Hello, world!"

my_cds foo  # <-- Parser can use "my_global" internally

Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Google [Bot]