Temp Variable Scoping With <<include>>

Twine Version: 2.9.2

This is maybe less of a question than a cautionary tale / nitpick, since I understand why it happens, but I’m a little annoyed that it does.

A simple illustration: Suppose I have a caller passage with the following content:

<<set $message = []>>
<<for _i to 0; _i < 10; _i++>>
	_i:
    <<if _i == 5>>
    	<<set $message = $message.concat(["this is a test message"])>>
    <</if>>
    <<include "Called Passage">><br>
<</for>>

…with “Called Passage” having this content:

<<if $message.length > 0>>
	<<for _i to 0; _i < $message.length; _i++>>
    	$message[_i] 
    <</for>>
    <<set $message = []>>
<</if>>

The result of navigating to the caller passage is an infinite loop, as include causes the scope of the temp variable _i to be shared across both passages.

I suppose there are cases where this might be useful, but it doesn’t seem that desirable tbh. Obviously it makes sense that variables at global scope ($message, for example) are accessible and mutable across passages, but the fact that included passages share scope with temp variables mean you have to be super careful with variables whose names are usually throwaways.

Is there any sort of configuration or other choice (aside from just not using include or adopting some naming conventions in included passages with temp variables to prevent them from getting stepped on) that can change this behavior?

Temporary variables are scoped to the current active Moment of Progress History that is associated with the current Passage being visited. This Moment is also available to any Special Passage that is part of the Passage Transition process, and to any content that is included into that of the visited Passage.

While it makes sense to use the Conditional form of the <<for>> macro to create the contents of the array $message variable, when later iterating over the current contents of that array it would make more sense to use the Range form of that macro.

<<for _message range $message>>
	_message
<</for>>

note: In the use-case you describe a custom widget might be a better solution than using <<include>> to “execute” common code.

1 Like

It appears that you are using <<include>> as a way of re-using code, and that’s not really what it’s good at — it’s more for reusing text, or for large blocks of layout that are self-contained.

A better approach for reusable code is a <<widget>>, as @Greyelf mentions, but they share the same problem, in that they inherit and modify the same temporary variables.

One way to prevent this would be the following:

<<widget test>>
   <<capture _i>>
      <<set _i to 2>>
      _i
   <</capture>>
<</widget>>

Because _i is <<capture>>'d, the change to it’s value inside the widget won’t spread outside it. If _i is 10 before you call <<test>> it will 10 after.

If this is a frequent issue for you, you could consider modifying the version of <<widget>> in your game to always set up a special temporary variable like _local, similar to _args and _content that was always local to the widget itself.

Here’s a version that does this:
localised_widget.txt (4.2 KB)

2 Likes

Thanks to both of you for the explanations and ideas. I’m still a bit of a n00b to this story format, so hearing how you more experienced folks with a better idea of the guts of sugarcube would tackle these tasks is super-helpful.