0 votes
by (180 points)
Hi! I just had a couple questions for anyone close to how Harlowe works.

My story is starting to experience some slowdowns, and I'm wondering if you have any suggestions. (Many thanks to those who helped earlier, I replaced a lot of my (click:?hook)[do this] code with (link:)s and it has sped things up a good bit.)

Still not perfect, though! There's still this little, annoying lag when moving between passages, like less than a second, but I feel like it could probably be fixed. I have a list of potential improvements/restructuring I could make, but I'm not sure how much benefit they would really be. I also get this horrifying "stack" errors every once in a while that Twine ruefully declares is a Harlowe bug, I still feel like they're my fault!  

I heard through the grapevine that Harlowe stores a complete history of variables or something. I would *love* to turn that off somehow, or figure out how to minimize it's impact.

I wish I could use temporary variables, but most of my passages are stitched together from sub-passages that they (display:), and temporary variables seem to be only accessible when you're solely using them in the highest-level, currently-displayed passage.

1. I use a lot of $variables for things like character stats / dispositions / etc. Does it make more sense to group these into datamaps? Are those more performant for data that should be logically grouped together? Do they have less of an impact on Harlowe's history function?  

2. I use a significant amount of (either:"really long string","another really long string","another fairly long string") statements. What is the java that underlies the (either:) statement? Would it be better to use a different method for selecting one of many long strings?

3. Do (else-if:)'s make a big difference as opposed to just a bunch of (if:)'s?

4. Should I... take the plunge and convert to another story format to get a more performant story? I would really hate to do this because I've done quite a few things in Harlowe that I consider kind of technically complex and would take some time to figure out how to mirror them. But definitely willing to do it if it's the best thing in the long run.

5. I probably won't complete my story for another 2-3 years. Do you think in that time, Harlowe will have improved / gotten fast to the point where tuning at this point won't make a huge difference?

Thanks so, so much for any advice!! :)

2 Answers

+1 vote
by (159k points)

general notes:
1. You need use the question tags to state the name and full version number of the story format, as answers can vary for each one. Based on the reference to temporary variables I will assume you are using Harlowe v2.0.1

2. It is very difficult to discus what might be causing potential performance issues without seeing the actual source code of your story project, and almost impossible to diagnose errors without knowing what the error message actually stated and what was happening code wise at the time the error occurred.

3. The History systems of both Harlowe and SugarCube (and those of all Twine 1.x related story formats) track what Passages the user has traversed/visited during their play-through, they also track the state of all the known story variables for each each moment/Passage in that history. Unlike SugarCube, Harlowe's History system isn't configurable so you can't control how many moments are stored within history.

4. The value of a Harlowe 2.x temporary variable declared within a Parent Passage can be access within a Child Passage referenced via a (display:) macro, and it can even be modified/changed in the Child Passage however due to how temporary variable scoping** works in Harlowe 2.x that modification will not be available within the Parent Passage.
** Each Child Passage is given its own copy of all the currently known temporary variables, and by known we mean those that were assigned a value before the Child Passage was referenced.

The follow TWEE notation example can be used to demonstrate this behaviour:

:: Parent Passage
(set: _number to 1)
(set: _list to (datamap: "first", "value"))
number: parent before: _number
list first: parent before: (print: _list's first)

(display: "Child Passage")

(set: _text to "Hello")
number: parent after: _number
list first: parent after: (print: _list's first)

:: Child Passage
number: child before: _number
list first: child before: (print: _list's first)
text: child: _text

(set: _number to it + 1)
(set: _list's first to "other value")

number: child after: _number
list first: child after: (print: _list's first)

... if you run the above you will notice that the "text: child: _text" line in the Child Passage will produce a "There isn't a temp variable named _text in this place." error due to the fact that that temporary variable wasn't created until after the reference to the Child Passage.

5. Objects like a datamap (Javascript Map) or an Array contain extra 'data' besides that which you place within them and as such they are considered a 'heavy' data type, which is basically just another way to say that they take up more resources (like memory) than a 'light weight' primitive data type like an integer. Also due to how Harlowe creates a new Map/Array each time you add or modify a value to/in one of these data types means that using them is also a more expensive process in terms of resources like time/memory.

6. You are using Javascript, which isn't the unrelated but unfortunately similar named Java programming language.

7. Using else-if's is generally more efficient when dealing with multiple mutually exclusive conditions because the evaluating process will stop once the first 'true' condition is found, where as the evaluating process of a "bunch of if's" doesn't stop until all the ifs in the bunch have been checked.

8. I can't advise you if you should switch story formats or not  because I believe that an Author should choose the story format they feel most comfortable using and whichever one suits their needs best, and only the Author can know the answers to these things.
I will however state that Harlowe is deliberately designed to suit those that want to build a less programmatically complex story and has limited support for using Javascript to extend the existing functionally of it's built-in engine. SugarCube on the other hand is more feature rich for those that both want to have a more programmatically complex story and who want to extend (using either TwineScript and/or Javascript) the existing functionally of it's built-in engine.

9. Only the Harlowe Developer can state what changes they have planned for their story format in that sort of time period, and whether future versions of the story format will even be backward compatible with the current versions. (2-3 real years is a long time in software development years. lol)

0 votes
by (63.1k points)
edited by

1. I use a lot of $variables for things like character stats / dispositions / etc. Does it make more sense to group these into datamaps? Are those more performant for data that should be logically grouped together? Do they have less of an impact on Harlowe's history function?

This shouldn't have a major effect on performance one way or another, though datamaps are actually harder on the history system than normal variables.  Story variables are already stored as properties on an object anyway, though.  The syntax for accessing parts of a datamap is probably very, very slightly less performance friendly than a standard variable, but this is unlikely to be causing the issue.  I suspect variables are the problem, if anything, but not really because of this.  I'll explain more in a bit.

2. I use a significant amount of (either:"really long string","another really long string","another fairly long string") statements. What is the java that underlies the (either:) statement? Would it be better to use a different method for selecting one of many long strings? 

First of all, it's JavaScript, not Java.  Other than unfortunately similar names, they have little in common and are completely separate languages.  You shouldn't be using (either:) for program control, assuming that's what you're doing; use (if:)s instead.  For example:

(set: _var to (either: 1, 2, 3))\
(if: _var is 1)[\
    Really long string...\
](elseif: _var is 2)[\
    etc....

 Depending on what your code actually entails, you might get some small boosts from this, but I doubt it'll be anything to write home about.

3. Do (else-if:)'s make a big difference as opposed to just a bunch of (if:)'s? 

A bunch of (if:)s is probably marginally slower than using (elseif:)s.  Ultimately, again, we're talking unnoticeable performance gains here, if anything.  It would take potentially 100s of (if:)s in the same passage being processed to have a noticeable impact. 

4. Should I... take the plunge and convert to another story format to get a more performant story? I would really hate to do this because I've done quite a few things in Harlowe that I consider kind of technically complex and would take some time to figure out how to mirror them. But definitely willing to do it if it's the best thing in the long run. 

Maybe, but that depends on the problem.  I'll address this more directly below.

5. I probably won't complete my story for another 2-3 years. Do you think in that time, Harlowe will have improved / gotten fast to the point where tuning at this point won't make a huge difference?

Browsers might be faster in the future, and I'm sure there's room for improvement in Harlowe's code.  No one can say for sure though. 

I heard through the grapevine that Harlowe stores a complete history of variables or something. I would *love* to turn that off somehow, or figure out how to minimize it's impact.

It stores a complete history of variables, at each moment.  If you have 1000 variables and click through 1000 passages, then it's storing 1,000,000 variables by the end.  Obviously, that's a lot, and it will cause noticeable slowdown.  You can't turn off Harlowe's history system.  SugarCube's history system can be shut off, and if this is really the issue you're facing, it might be worth making the switch.  I suspect this is probably the culprit.  If it is indeed the problem, then the performance issues should be getting worse as play goes on.  If that's what's happening, then this is the problem.

I don't know that you need to switch formats though.  It's probably the case that you could do a better job managing the variables you have now; using more temporary variables and using less variables overall, or reusing variables.  Many Twine creators create pointless variables, and pay for it as their stories grow.  For example, an author might create a variable that checks if a player has been to a passage before rather than using the (history:) macro.  Or, an author might make a variable that tests if the user is out of health instead of just checking if the health variable itself is 0 or not:

-> BAD
(if: $health <= 0)[
    (set: $noHealth to true)
]
(if: $noHealth)[
    You DEAD!
]

-> GOOD
(if: $health <= 0)[
    (set: $health to 0)
]
(if: $health is 0)[
    You DEAD!
]

Check your code for pointless variables, and cut down on the clutter as much as you can.

It's also possible that you have headers or footers running every round that are eating up performance.  This is especially noticeable when you're running loops in Harlowe.  Sometimes you need a loop, but loops are not really well optimized in my experience.

Look at the code you have running on every turn and try to trim the fat.  Look at any loops you've got running and make sure you aren't better served using another method.  Here's an example of such a situation.

There are other reasons your performance might be struggling, too.  Try your game in different browsers to make sure it's not your browser's JavaScript engine.  It might also be related to saved games--if you're loading old saves to test from, it's possible that the saved data includes junk variables or data that you aren't using anymore.

Some complicated JavaScript or poorly-written CSS could also be hindering your game, so look at what you have running in those places too.

In conclusion, I would be surprised if it wasn't possible for you to fix the performance issues, and that needing to change story formats or wait for improved versions of Harlowe was actually your only option.  The issues you mention specifically might net you smaller performance gains, but I think you should focus on:

  • Cleaning up your variable store by using less story variables wherever possible.
  • Looking at headers/footers/other repeated code and trying to make that code more efficient (assuming you are using this type of code). 
...