Page 1 of 1

call_in_new_context?

Posted: Thu Dec 26, 2019 2:42 pm
by rames44
Trying to make sure I understand something.

I was trying to write something vaguely like this:

Script:

Code: Select all

    someone "says something"
    $ an_object.a_method()
    someone "says something"
Inside "an_object":

Code: Select all

    def a_method(self):
        do some calculations
        
        renpy.call(my_label)
        
        do some more calculations
 
and

Code: Select all

label my_label:
    show screen notification_popup
    pause
    hide screen notification_popup
    return
So, a method call from my main script calls a Ren'py label which does an interaction with the user, then I want to resume my Python method, and eventually return back to the original script. (If I was designing this from scratch, I probably wouldn't do it this way, but I have a bunch of existing game code, and I'm trying to migrate some of the operations while not breaking a bunch of existing stuff.)

What I'm finding is that the Python code immediately after "renpy.call(my_label") never gets executed. Instead, I end up back it my main script, without "do some more calculations" being executed.

Is this a case where I should be using "renpy.call_in_new_context" instead of just "renpy.call"? Is that the reason my Python function doesn't resume? Or is this something that you just can't do? (script -> Python -> script and back again)

Re: call_in_new_context?

Posted: Thu Dec 26, 2019 6:29 pm
by strayerror
The answer very much depends on the behaviour you need from your called label. You're correct that call_in_new_context will return control to your python code (along with any value returned by the label), the potential gotcha is that any recording of rollback within that label will be lost when it returns (this includes saving, any save made while in the call will load prior to it). This also means that after rolling back over the interaction calling your function, when attempting to roll-forward over it, the call_in_new_context will repeat (as expected) but due to the missing rollback state rolling forward through it will not be possible.

Given your example this means that you would be unable to roll-forward through the pause statement in my_label, as it's an interaction.

Generally, assuming you wish to retain functional rollback behaviour for your players, I'd recommend not triggering any interactions with your called label. If however, as in this case, this is not possible and you need interactions within it, you have several options:

1) If the return value doesn't matter (as it appears not to in your case) and you're happy to forgo the label call during roll-forward, you could simply add a "if not in rollback" condition to the call_in_new_context skipping the entire thing when rolling-forward.

2) If you're happy to skip the content of the label on roll-forward but you do need the return value, you could capture the return value the first time, and store it in the rollback log manually so that when rolling-forward the label isn't called, but instead the value it would have returned is read out of rollback (look into the checkpoint and rollforward_info functions for this).

3) Invert your current python-based design - instead of calling a function to abstract out python calculations and doing an additional label call from python, instead call a label to abstract out a renpy call and do some python calculations via python calls (which, now that I read it isn't terribly clear, so here's a potential example):

Code: Select all

label a_method(self):
    # Scope the rv variable to this label call so that it doesn't leak out to the global scope.
    $ renpy.dynamic('rv')
    
    $ self.a_method_pre()
    
    # You could call my_label here, or alternatively in-line its contents if it makes sense to do so.
    call my_label
    
    $ rv = self.a_method_post()
    return rv
Hope this helps explains the limitations of using call_in_new_context with a game (it's more designed for menus from what I can gather), and maybe has a solution that will work for you, good luck!

Re: call_in_new_context?

Posted: Fri Dec 27, 2019 12:16 pm
by rames44
Thank you for the detailed explanation. The save and rollback/roll-forward are issues that would never have occurred to me.

So, just to make sure I'm understanding -

1. If someone creates a save during the "pause" in "my_label," when the save is loaded, Ren'py will restart things by calling "an_object.a_method()" again, since it doesn't consider that statement to have been completed.

2. To handle the roll-forward case (because skipping the popup in roll-forward would be benign) I should code as:

Code: Select all

    def a_method(self):
        do some calculations
        
        if not renpy.in_rollback():
            renpy.call_in_new_context(my_label)
        
        do some more calculations
3. If I don't include the rollback test, then during roll-forward, the game will still stop at the pause during roll-forward?

The idea of inverting the Python/Renpy logic is actually a good one - it would take some restructuring, since some of the "do some calculations" involves "can you really do this, if not bail out" logic, but, as you indicate, that could be achieved by breaking the current method up into two, etc., etc., etc. I had the Python method already, so was thinking in terms of "ok, now let's make it do more for me," rather than going back to the drawing board a bit. Functional fixedness. :) I'll certainly have to remember that approach for my next project - whether this is all worth it for the current one is an open question.

Re: call_in_new_context?

Posted: Fri Dec 27, 2019 12:32 pm
by strayerror
1. I believe the save would restore the game to the last interaction previous to the call_in_new_context occurring. i.e. the next click by the player would trigger the an_object.a_method() call.
2. Correct, that should prevent players "sticking" when rolling forward.
3. Exactly!

Glad it helped! :)