User-defined functions and save/load

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
Odysseus
Newbie
Posts: 17
Joined: Sun May 03, 2020 12:52 pm
Contact:

User-defined functions and save/load

#1 Post by Odysseus » Tue May 11, 2021 2:55 pm

Hey guys! Been a long time since I hit a wall. Long story short, I've made an educational VN teaching Python. In it, players write Python code, which Ren'Py then executes.

So far it's working swell, but I hit the following bump I can't get my head around:

One challenge involves writing a simple function using def, called alter_signal:

Code: Select all

def alter_signal(sig):
    return sig*7/5
Now, that's pretty straightforward and simple. But...

...after passing this challenge, if saving, the game won't load!

Code: Select all

I'm sorry, but an uncaught exception occurred.

While running game code:
  File "renpy/common/00action_file.rpy", line 452, in __call__
    renpy.load(fn)
AttributeError: 'StoreModule' object has no attribute 'alter_signal'

-- Full Traceback ------------------------------------------------------------

Full traceback:
  File "renpy/common/_layout/screen_main_menu.rpym", line 28, in script
    python hide:
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\ast.py", line 914, in execute
    renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\python.py", line 2028, in py_exec_bytecode
    exec bytecode in globals, locals
  File "renpy/common/_layout/screen_main_menu.rpym", line 28, in <module>
    python hide:
  File "renpy/common/_layout/screen_main_menu.rpym", line 35, in _execute_python_hide
    ui.interact()
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\ui.py", line 297, in interact
    rv = renpy.game.interface.interact(roll_forward=roll_forward, **kwargs)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\core.py", line 2702, in interact
    repeat, rv = self.interact_core(preloads=preloads, trans_pause=trans_pause, **kwargs)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\core.py", line 3518, in interact_core
    rv = root_widget.event(ev, x, y, 0)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\layout.py", line 998, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\transition.py", line 47, in event
    return self.new_widget.event(ev, x, y, st)  # E1101
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\layout.py", line 998, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\layout.py", line 998, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\screen.py", line 714, in event
    rv = self.child.event(ev, x, y, st)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\layout.py", line 998, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\layout.py", line 244, in event
    rv = d.event(ev, x - xo, y - yo, st)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\layout.py", line 998, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\layout.py", line 244, in event
    rv = d.event(ev, x - xo, y - yo, st)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\layout.py", line 998, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\layout.py", line 244, in event
    rv = d.event(ev, x - xo, y - yo, st)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\behavior.py", line 962, in event
    return handle_click(self.clicked)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\behavior.py", line 897, in handle_click
    rv = run(action)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\behavior.py", line 320, in run
    return action(*args, **kwargs)
  File "renpy/common/00action_file.rpy", line 452, in __call__
    renpy.load(fn)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\loadsave.py", line 769, in load
    roots, log = loads(location.load(filename))
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\loadsave.py", line 63, in loads
    return cPickle.loads(s)
AttributeError: 'StoreModule' object has no attribute 'alter_signal'

Windows-8-6.2.9200
Ren'Py 7.3.5.606
DigiWorld 1.0
Tue May 11 21:48:11 2021
Of course, I define it before, whether in init or right before, I can't save, since it doesn't know which version!

Code: Select all

I'm sorry, but an uncaught exception occurred.

While running game code:
  File "renpy/common/00action_file.rpy", line 372, in __call__
    renpy.save(fn, extra_info=save_name)
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

-- Full Traceback ------------------------------------------------------------

Full traceback:
  File "renpy/common/_layout/screen_load_save.rpym", line 35, in script
    $ ui.interact()
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\ast.py", line 914, in execute
    renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\python.py", line 2028, in py_exec_bytecode
    exec bytecode in globals, locals
  File "renpy/common/_layout/screen_load_save.rpym", line 35, in <module>
    $ ui.interact()
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\ui.py", line 297, in interact
    rv = renpy.game.interface.interact(roll_forward=roll_forward, **kwargs)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\core.py", line 2702, in interact
    repeat, rv = self.interact_core(preloads=preloads, trans_pause=trans_pause, **kwargs)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\core.py", line 3518, in interact_core
    rv = root_widget.event(ev, x, y, 0)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\layout.py", line 998, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\transition.py", line 47, in event
    return self.new_widget.event(ev, x, y, st)  # E1101
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\layout.py", line 998, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\layout.py", line 998, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\screen.py", line 714, in event
    rv = self.child.event(ev, x, y, st)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\layout.py", line 998, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\layout.py", line 244, in event
    rv = d.event(ev, x - xo, y - yo, st)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\layout.py", line 998, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\layout.py", line 998, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\behavior.py", line 962, in event
    return handle_click(self.clicked)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\behavior.py", line 897, in handle_click
    rv = run(action)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\behavior.py", line 313, in run
    new_rv = run(i, *args, **kwargs)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\display\behavior.py", line 320, in run
    return action(*args, **kwargs)
  File "renpy/common/00action_file.rpy", line 372, in __call__
    renpy.save(fn, extra_info=save_name)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\loadsave.py", line 419, in save
    six.reraise(t, e, tb)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\loadsave.py", line 405, in save
    dump((roots, renpy.game.log), logf)
  File "C:\Users\Odysseas\Documents\renpy-7.3.5-sdk.7z\renpy-7.3.5-sdk\renpy\loadsave.py", line 49, in dump
    cPickle.dump(o, f, cPickle.HIGHEST_PROTOCOL)
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

Windows-8-6.2.9200
Ren'Py 7.3.5.606
DigiWorld 1.0
Tue May 11 21:54:01 2021
Anything I can do to work around this "damned if I do, damned if I don't" solution?
Please, it's really important to keep the challenge as-is (the player defining the alter_signal function), it's part of the gameplay and a requirement for my thesis (the game is part of my thesis).

Thank you!

User avatar
Ocelot
Eileen-Class Veteran
Posts: 1151
Joined: Tue Aug 23, 2016 10:35 am
Skype: miinipaa
Contact:

Re: User-defined functions and save/load

#2 Post by Ocelot » Tue May 11, 2021 3:12 pm

RenPy (or, rather, cPickle) cannot save or load code. If you want saving and loading the game, you must make sure, that there are no references to alter_signal function in any variable. You must clear them all. You can store function code itself in a string and reevaluate it after loading, but make sure that game does not try to save your function.

You can try to store it in variable, which would not get saved, as described here: https://www.renpy.org/doc/html/save_load_rollback.html
< < insert Rick Cook quote here > >

Odysseus
Newbie
Posts: 17
Joined: Sun May 03, 2020 12:52 pm
Contact:

Re: User-defined functions and save/load

#3 Post by Odysseus » Thu May 13, 2021 8:26 am

Normally, with no other reference of the function except the player writing it, if I load without exiting it's fine, but if I load after opening the game after exiting then I get the load error (the first one).

I'm not looking for something extremely elegant as a solution, even a crude workaround would be awesome. I wouldn't mind if it was the last challenge, but there's a few more after that.

User avatar
Ocelot
Eileen-Class Veteran
Posts: 1151
Joined: Tue Aug 23, 2016 10:35 am
Skype: miinipaa
Contact:

Re: User-defined functions and save/load

#4 Post by Ocelot » Thu May 13, 2021 9:13 am

The problem is following: if you do something like:

Code: Select all

init python:
    def foo():
        bar()

label start:
    "Start"
    python:
        def bar():
            narrator("Hi")
    $ foo()
    "End"
    return
function definition outside of the init phase makes the name of that function be included to the list of variables to save. And RenPy cannot save or load functions properly. Only way to avoid this is to use anonymous functions which are not part of saved state. One way to do that is something like:

Code: Select all

init python:
    def foo():
        stuff.bar()
define stuff = object()
label start:
    "Start"
    python:
        stuff.bar = lambda : narrator("Hi")
    $ foo()
    "End"
    return
This will not interfere with saving and loading, but any changes to stuff.bar would be lost. Another approach would be simply disable saving and loading after you introduce this puzzle.
< < insert Rick Cook quote here > >

Post Reply

Who is online

Users browsing this forum: Ocelot