Any tips on optimization?

I’ve noticed that some games, as they become larger and more complex, become slow. Either the passage changes are slow, the saving/loading of games, etc. (Obviously, this does vary significantly depending on what computer I’m playing on.)

I assume this is the result of scripts and variable data. Anyone have any advice on either diagnosing these problems, or avoiding running into them altogether?

On a sugarcube work with large variables we noticed severe slowdowns after some passages. Setting Config.history.maxStates to 1 resolved the issue completely (the story did not require back/forward control).

1 Like

Most of the slowdown comes from storing more and more data in the game history as time goes on. Anything which reduces the amount of data stored should help. Besides what n-n suggested, with reducing the number of states stored in the game history, here are a few other tips:

1.) Try to use temporary variables whenever possible.

2.) Once you’re done with a story variable, you should use the <<unset>> macro to get rid of that variable.

3.) Instead of storing any “static” data (data which won’t change during the entire game, e.g. an array holding the names of the days of the week) on a story variable, it should be stored on the setup object variable instead. Data stored there won’t take up space in the game history, but will be accessible both from Twine and JavaScript. (See an example of this in #6 below.)

4.) Instead of using story variables to track certain events, you can use the hasVisited() function to determine if a certain passage where that event occurs has been visited or not.

5.) Instead of using strings, try to store data using number or Boolean variables whenever possible. You can use code or static variables (see #3 above) to translate those values to strings, if needed.

6.) If you want to get really fancy, you can store up to 32 flags as bits on a single number variable using bitwise operators. So you could have something like this in your StoryInit passage:

<<set $flags = 0>>
<<set setup.Flag1 = Math.pow(2, 0)>>
<<set setup.Flag2 = Math.pow(2, 1)>>
<<set setup.Flag3 = Math.pow(2, 2)>>
<<set setup.Flag4 = Math.pow(2, 3)>>
<<set setup.Flag5 = Math.pow(2, 4)>>
...
<<set setup.Flag32 = Math.pow(2, 31)>>

You can use whatever names you want (as long as it only uses A-Z, a-z, 0-9, _, and/or $ in the name and doesn’t start with a number) instead of “Flag#” to make it easier to remember what each flag stands for. (Each flag goes up by a power of 2, since that number represents a single bit.)

You could then set a flag by doing:

<<set $flags |= setup.Flag4>>

or unset a flag by doing:

<<set $flags -= $flags & setup.Flag4>>

Then you can check to see if a flag is set by doing:

<<if $flags & setup.Flag4>>
	Flag 4 is set.
<<else>>
	Flag 4 is not set.
<</if>>

And while saving up to 31 Boolean values (4 bytes each) may not seem like much, keep in mind that that’s multiplied by the maximum number of states in your game history, so that could be over 12 kB less data for 100 states in the history.

Hope that helps! :slight_smile:

3 Likes

Wow setting Config.history.maxStates to 1 on a particular slow game really had an impact - massively reduced save file size. (Had no idea the history went as far back as 100.) Thanks n-n.

HiEv, I don’t remotely understand your bitwise operator flag system at the moment, but might once I’ve taken time to play around with it. The setup thing is very interesting as well.

A question about StoryInit: SC doc describes it as “Used for pre-story-start initialization tasks, like variable initialization (happens at the beginning of story initialization).” I don’t actually know what ‘story initialization’ means. Is this the stage during which the browser is ‘loading’ a Twine game (spinning loading indicator), such as when you open the game in a new tab, or hit refresh on a game that’s already running?

(I actually wanted to show an experiment I’m working on that is running slightly slower than I’d like and get input on ways to optimize it, but laughably part of the experiment was doing exactly what you’re advocating against: Every item, creature, spell and its properties is just stored as a string, eg “[item armor] Steel Plate Armor [DEF:20 DEX:-2]”, the properties being hidden from the player by a string function. I think you’d probably advocate I store item info in a database in setup?)

Thank you for your help.

1 Like

In computer languages, a number is actually represented internally by a series of bits. In JavaScript, the bitwise operators treat a number as a signed (+/-) 32-bit value. For example:
0000 0000 0000 0000 0000 0000 0000 0100 = 4 = setup.Flag3
(Each group of eight bits above represents one byte of data, so a 32-bit number takes up 4 bytes.)

Using the code I gave above, you can treat each bit within a number variable as a separate “flag”, allowing you to store 32 true/false “flags” within a single number variable.

Correct. Whenever you open the game, load a save, or restart, the code within the JavaScript section and the StoryInit passage are executed first. Then, if you’re loading a save, the data from the save is loaded after that. Any text output in the StoryInit passage will not normally be displayed anywhere.

Thus, you should normally do any setup work which needs to be done initially within the JavaScript section and/or the StoryInit passage.

Essentially, yes. Any data which will never change throughout the story, especially larger chunks of data like strings, should either be gotten by calling a function, which returns the data you need from something other than story variables, or by getting it from data which was stored on the setup object variable when the game first starts.

The former is what my Universal Inventory System (UInv) for Twine/SugarCube does to help reduce the size of the data recorded in the game history. If your game has an inventory system, you might want to take a look at that, since it makes working with inventories a lot easier.

Hope that helps! :slight_smile:

3 Likes

There was a topic on this back on the original Twine forums and I recall the TL:DR being that the main optimization is reducing history states because they use exponential amounts of space/processing (i.e, every additional state is doubling the amount of variables). With a single state having lots of variables should not be an issue for performance, at least that’s how I recall it. Moment to moment performance, its lots of logic processing (i.e, if then, cases, etc, especially nested) that can slow things down, but even then on a 10 year old computer you’re looking at pauses of like half a second at most.

I’m no expert, but I think for most basic uses of variables performance should not require extra-creative data storage if you use 1 state.

To clarify. It’s strictly linear, not exponential.

Interesting, I was taught that there are 8 bits in a Byte (and that 4 bits is a Nibble).
Does JavaScript have some other definition for that unit of measure?

Whoops. I was thinking each hex character was a byte. You’re correct, 8 bits to a byte.

I’ll fix that above.

1 Like

I can’t even begin to describe how useful this knowledge of StoryInit will be to me, well beyond the scope of this topic. I knew about setup already but without knowing what StoryInit was, setup did not seem that compelling. Now it’s my favorite thing. Thanks!

Yeah, that’s literally true. I just think when you say “linear”, people don’t quite get that going from 1 states to 2 states is doubling it. A lot of people are mathematically illiterate. Which includes me, I guess, except when I think about it.

I didn’t explain myself very well.

I meant that it is not doubling—not beyond going from one to two moments anyway. You said “every additional state is doubling the amount of variables”, which would be a progression something like: 1, 2, 4, 8, 16, 32….

The actual progression is a simple incremental increase: 1, 2, 3, 4, 5, 6…. The number of story variable store clones in existence is directly proportional to the number of moments (states) within the history because each state contains exactly one copy of the store.