Dumb question about call... return...

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,578
2,210
Okay. I know programming (ish). I don't know Python/RenPy especially well.

So there's something odd going on with the call / return stack in the game I'm trying to update/fix.

I think the original author is making a basic mistake, but I don't know RenPy well enough to properly diagnose it.

The author has discovered "call / return"... and seems to love it.
A completely linear story has recent chapters which feature it all over the place.

Except, some testing I just did - ends up with the game ending (normally) and then returning back to previous point in the code (a called label)... executing it a second time... and THEN ending.
Hence my assumption about a basic programming mistake.

Question #1.
My previous programming experience says it's probably a mismatched return stack - but I don't know the console/debug command to list the outstanding returns.

Looking through the code, the only thing I can see him doing that looks "off" to me is a combination of these two (coding mistakes)?:

Code:
label start:
    call lb_my_subroutine
    call lb_other_subroutine
    return

label lb_my_subroutine:
    if var1 == True:
        return

    pc "I said something clever"
    return

label lb_other_subroutine:
    if var1 == True:
        jump lb_sub2_label1

    jump lb_sub2_label2

label lb_sub2_label1:
    pc "this is subroutine 2, exit #1"
    return

label lb_sub2_label2:
    pc "this is subroutine 2, exit #2"
    return
Note, this is just my example. The real code is similar enough to get the point of the issue that may or may not be there. Obviously the original isn't quite this dumb.

Question #2.
Can you code conditional "return" statements?
and/or have multiple "return" statements within a single label?

Question #3.
Is it okay to jump from one label to another and then expect the "return" to honor the original call?
It's the sort of thing my original tutor would have had a fit at... but maybe Python/RenPy is fine with this sort of stuff...

I figure one or both of these coding fragments is responsible for my odd call/return behavior. But then again... maybe I'm completely barking up the wrong tree.
 

toolkitxx

Well-Known Member
Modder
Donor
Game Developer
May 3, 2017
1,473
1,789
Question #2: As of conditional - to the best of my knowledge that is proper and possible.

Question '3: There is 'jump' and 'call' basically in Renpy afaik - jump will not let you go back - it 'jumps' to wherever you told it to and does everything from there only - where 'return' will send you to where the 'call' came from.
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,578
2,210
Yeah.
And maybe it's as simple as that.

I just had visions of RenPy (or Python) doing something beyond being a simple interpreted language. (like how it handles default or define).
Where it pre-scans "calls" and "return" statements and does something less than obvious.
Or tries to match the current label with the return stack and ended up getting confused.

As I said, I don't know.
But I know there's a few very experienced coders on here and even if I've got completely the wrong end of the stick - maybe my overall description will hint at the real cause.

At the very least, a command to show the current list of returns queued would help. But as I say, that might not even be the root cause. I suppose I'm hoping someone will read my nonsense and go "ah, no... it's probably just [[THIS]]".
 

Rich

Old Fart
Modder
Donor
Respected User
Game Developer
Jun 25, 2017
2,487
7,020
From the console, the current call stack depth can be displayed using:
Code:
renpy.call_stack_depth()


The call stack itself can be accessed via
Code:
renpy.game.context().return_stack
(Found by perusing the code) The code documents the two significant members of the game context object as:
Code:
@ivar current: The name of the node that is currently being
executed.
@ivar return_stack: A list of names of nodes that should be
returned to when the return statement executes. (When a return
occurs, the name is looked up, and name.text is then executed.)
I have no idea what constitutes a "node" - this is internal Ren'py magic. But even just tracking the call stack depth as you work through the code will probably help you narrow down where the problem is.

In general, as you're probably aware, every "call" executed should have a corresponding "return" executed. There is one exception to this in Ren'py - if you "call" a screen, the screen doesn't necessarily have to use a Return screen action - as an alternative, it can use a "Jump" screen action to direct the call flow elsewhere, and the Jump screen action pops the "call screen" off the stack. (Having said that, I'm not 100% sure that "call screen" is actually implemented the same way that calling a script label is - the return stack may not actually be involved in a "call screen" operation. So this might be a conceptual rather than literal description.)

But there absolutely does not have to be a one-to-one correspondence between "call" statements and "return" statements - the example code you posted with conditional returns and jumps to returns is perfectly valid.
 

Catapo

Member
Jun 14, 2018
237
437
I don't know much about Ren Py but I will apply some of my programming knowledge to try to solve this:

call/return is similar to procedures in most programming languages
jump seems to just go to a different piece of code and not returning back to the place of the jump similar to a goto in other languages

with this info my answers are

Question 1:

From label start the code goes to lb_my_subroutine
There it checks the variable and returns immediately or does the text thing and then returns back to the start label
Next it goes into lb_other_subroutine and depending on the variable it jumps to lb_sub2_label1 or lb_sub2_label2
Personally I consider this jump unnecessary but you said the actual program is more complex so it could be useful there.
Will the return inside those label work and take us back to the call in start label...I think Yes...
My question would be where the return from label start takes us to ?
It should go back to somewhere that has call start but I don't know where that is so the problem might be there

Question 2:

In other languages conditional and multiple returns are normal and quite useful so it should apply to ren py as well

Question 3:

Like I said I don't know about Ren Py but I don't see why it would not if it works like I think it works.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,312
15,193
Looking through the code, the only thing I can see him doing that looks "off" to me is a combination of these two (coding mistakes)?:
They aren't a mistake as long as one of the jumped label end by a return statement.
Ren'py use a basic stack to store the return points, and it's updated only when a call statement (or equivalent) are executed. So, it doesn't matter how many times you jump after a call, as long as at the end of the path there's a return.
And the missing return must be what cause the problem you face, since the game start by a call from the start menu ; therefore it need a return at the end to go back to the start menu.


I have no idea what constitutes a "node" - this is internal Ren'py magic.
It's an entry in the Abstract Syntax Tree (AST), and its literally a node in the tree. Either it's a complex statement (like while by example), and the node lead to another branch of the AST, where you'll found the nodes contained by the statement. Or it's a simple statement (like jump by example), and the node is just an entry in the AST.
Initially it was a single statement, but starting 6.99.13 (?) PyTom made some optimizations and a node can contain more that one statement, depending of [whatever was in his mind when he decided this].
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,578
2,210
The call stack itself can be accessed via
Code:
renpy.game.context().return_stack
This was exactly what I was looking for. Cheers.

Question #2: As of conditional - to the best of my knowledge that is proper and possible.
But there absolutely does not have to be a one-to-one correspondence between "call" statements and "return" statements - the example code you posted with conditional returns and jumps to returns is perfectly valid.
They aren't a mistake as long as one of the jumped label end by a return statement.
Ren'py use a basic stack to store the return points, and it's updated only when a call statement (or equivalent) are executed.
Thanks for confirming that. RenPy doing "something clever" would have been a nice get-out-of-jail-free answer.
But knowing that it really is just a simple call/return stack (and being able to verify it as such) has been a huge help.

... to the point that I've found the mismatched code.

tbh, if I'd not got distracted by potential abstractions - I might have found it earlier. But now I'm both a little wiser and have the answer I needed.

Thanks to all three of you for the replies.