How to limit History size in long lasting game?

Twine Version: 2.3.5
Story Format: Harlowe 3.1.0

I am working on a game which may last long when played, like e.g. 1000-2000 passage visits.
I am worrying about the memory usage, because I read in other threads that:
>>Twine stores the state of all (global) variables for every passage-visit, such copying all variables for every step.<<

My Question: How can I limit the memory usage?

My thoughts so far:

Solution 1: Tell Twine, I have no use for a complete history of variable states. I am satisfied with having something like

  • a history of visited passage names, like (history:) is described in Harlowe documentation,
  • a 1-step undo (but I don’t even need that).

Is there some way to achieve that?

Solution 2: Use only very few global variables (like $xy), but mostly local variables (like “_xyz”)

Currently I can’t do that, because local variables, defined in a passage’s gloabl space, are not visible within hooks in that passage. Such their use is so limited, that I almost never can use them.

Is there any work around for that?


An example why Solution 2 does not work for me:
— begin example —
(set: _options to (a: “Hello”, “Bye”))
(set: _choiceIndex to 0)

… some passage text and interaction …

(event: when _choiceIndex > 0)[(print: (_choiceIndex) of _options )]
— end example —

This gives two error messages, because _choiceIndex and _options are undefined within the hook.

Any help or ideas are very welcome.

The Twine 2.x application itself has no effect on how a specific story format implements its custom History system or how the state of that History System is ‘stored’, and currently each of the included story formats has their own unique method of handling History.

Harlowe’s History system is JavaScript Object Prototype (inheritance) based, and it doesn’t automatically copy/clone the current state of all known Story Variables each time a Passage Transition occurs. It does however copy/clone the entire contents of any collection based object (Array, DataMap, DataSet, etc…) each time one of that object’s elements/properties are updated.

Harlowe currently includes no means for an Author to configure/effect how the History system behaves. Your only choice is to monitor how many Story Variables you use, and their types.

I believe you mean Temporary Variables, and unfortunately Harlowe has interesting Scoping rules with regard to that type of variable.

I would strongly advise against using the (event:) macro, or at least limiting its usage, and I have explained my reasons in the Event charger not triggering? thread of the Twinegames sub-reddit.

1 Like

Thank’s for the valuable information, @Greyelf! This does help me a lot in managing memory footprint.

It does however copy/clone the entire contents of any collection based object (Array, DataMap, DataSet, etc…) each time one of that object’s elements/properties are updated.

If I understand that right:

  • Global variables with static content are not copied.
  • It makes sense to put values that change often in single value variables, instead of arrays or datamaps.

Correct?

@Greyelf

I would strongly advise against using the (event:) macro, or at least limiting its usage, and I have explained my reasons in the Event charger not triggering? thread

In that posting you write, (event:) triggers the boolean check every 20ms. However, the Harlowe documentation, https://twine2.neocities.org/#macro_event, says, it is triggered every 0.2s = 200ms.

Which one is correct?

I’ve tested it myself for about 10 times; the reaction time of (event:) was always below 20 ms.
To double-check, I replaced (event:) with (live: 300ms) and had within a few tries reaction times of over 200ms.
So, I believe the documentation is wrong.

Global variables with static content are not copied.

Two points:

  1. A Story Variable isn’t truly ‘global’.
  2. A variable’s value is only ‘static’ if you don’t assign that variable a new value.

A Story Variable that “is storing” / “being assigned” an immutable data-type value (eg. String, Number, …) does not ‘copy’ the previous stored value during the assignment process of a (set:) macro call.
eg. These types of assignments don’t result in the previous value being ‘copied’…

(set: $string to "a string")
(set: $string to it + " more")
(set: $number to 123)
(set: $number to it + 10)

…where as these types of assignments do because the data-type is mutable …

(set: $array to it + (a: "element"))
(set: $array's 1st to "some value")
(set: $map to it + (dm: "key", "value"))
(set: $map's "key" to "value")

Harlowe uses it’s own custom implementation of the Array, Data-Map (Map), and Data-Set (Set) objects, which aren’t as efficient when it comes to assignment, accessing, and looping as the JavaScript equivalents. This is the main reason we suggest limiting their usage in a Harlowe based project.

I also link to the source code that implements the ‘working’ part of the (event:) macro so that the reader can see both that source code’s comment and implementation, both of which reference the 20 value.

(set: $number to it + 10)
(set: $array to it + (a: "element"))

Is it allowed to assign one variable to differing data types? Like

(set: $any: to (a: "text")
    .... some stuff ...
(set: $any: to 0)

Of cause that’s bad style; but setting a former array variable to 0 before leaving the passage
might workaround the copying for history issue.

Yes, you can assign a new value of a different data type to an existing variable.

No, that doesn’t help here as Harlowe copies on access. Meaning, that each time you modify an array, datamap, or dataset in Harlowe you make a copy. Setting such a variable to another data type afterwards doesn’t save you from all of that copying.

1 Like