Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

[SugarCube 2.7.0] Performance Degradation With Excessive Variables

Hello,
My game is likely outside the scope of what Twine/SugarCube were designed for. However, I'm planning to work on a turn-based RPG which makes use of many variables (hundreds, largely related to NPCs, and stored in JavaScript objects). I've been doing some stress-testing to see what Twine/SugarCube can handle, and I'm noticing that the time it takes to load a passage varies greatly depending on how many variables it has to process.

For those more familiar with JavaScript than myself, is there something I can do to improve performance? Ideally, I think performance would improve greatly if the game were only processing variables used in the current passage, but using the CheckVars macro, it appears that the game keeps all variables loaded at all times.

Thank you for your time.

Comments

  • Have you tried limiting the number of moments kept within the story history? If not, see the configuration object property Config.history.maxStates.

    Beyond that, without knowing exactly what you're doing, there's not a lot anyone can do to help.
  • If the state of a variable does not change for the life of your story you may want to replace that variable with a reference to a manually created property on the setup object.
    /% Setup variable in StoryInit passage. %/
    <<set setup.shopkeeperName to "Bob">>
    
    /% Use variable in story passage. %/
    The shopkeeper's name is <<print setup.shopkeeperName>>
    
  • Thank you both for the replies.
    Have you tried limiting the number of moments kept within the story history? If not, see the configuration object property Config.history.maxStates.

    Beyond that, without knowing exactly what you're doing, there's not a lot anyone can do to help.

    Thank you for that. That property may actually be what I'm looking for; I'll have to play around with it.

    It's hard to give a specific example, as I haven't written anything for the game yet, and am just testing the feasibility of Twine. But, as a random example, let's propose that I have an NPC named Bob.

    Since Bob lives in our starting town, he is initialized fairly early-on in our game, like so:
    /% A non-exhaustive look at Bob's properties. %/
    <<set $bob to {
        main : {
            firstName: "Bob",
            health : 100,
            maxHealth : 100
        },
        misc : {
            disposition: 50
        }
    }>>
    

    While interacting with Bob, the player raises Bob's disposition to 70, and then the player leaves town. Is there a way to store the change to Bob's disposition, but at the same time, 'unload' him from the game so that Bob (and dozens of other NPCs) aren't being persistently tracked while the player is nowhere near them (or is this what Config.history.maxStates helps with)?

    Another, somewhat related, question is how would I unset a specific property? Using:
    /% Something that wouldn't happen, but as an example. %/
    <<unset $bob.misc.disposition>>
    

    removes the entire object Bob. As a workaround, I would use:
    <<script>>delete (variables().bob.misc.disposition)<</script>>
    

    But that seems like an improper way to do it.
  • At what point are you noticing a slowdown? I'm using several hundred myself, and not encountering any problems.
  • Claretta wrote: »
    At what point are you noticing a slowdown? I'm using several hundred myself, and not encountering any problems.

    Primarily when running for loops or conditional statements on them. Granted, these are stress tests, and I don't foresee having to run those on several hundred variables at once very often.

    I'm just not very experienced with JavaScript, and am wondering if there's something fundamentally different I should be doing when setting up these data structures to ensure that they scale well as the game continues to grow.
  • Kokonoe wrote: »
    Primarily when running for loops or conditional statements on them. Granted, these are stress tests, and I don't foresee having to run those on several hundred variables at once very often.

    I'm just not very experienced with JavaScript, and am wondering if there's something fundamentally different I should be doing when setting up these data structures to ensure that they scale well as the game continues to grow.
    Do you have examples of these tests you care to share? It may be that you're doing something pathological which is causing the issue you are seeing.

    Kokonoe wrote: »
    Is there a way to store the change to Bob's disposition, but at the same time, 'unload' him from the game so that Bob (and dozens of other NPCs) aren't being persistently tracked while the player is nowhere near them (or is this what Config.history.maxStates helps with)?
    You could keep the disposition data separate from the rest of Bob's data, however, you'd run into the same issue with any piece of NPC data which wasn't immutable. As soon as you mutate something, you have to keep track of it and the way story formats do that is via whatever passes for their variable store. In summary, there's no easy way built-in to unload Bob without losing persistence. Can it be done? Sure, but you'd have to write your own subsystem for it.

    The Config.history.maxStates property controls the maximum allowed number of moments which can be stored within the story history. Each moment is chiefly a snapshot of the variable store for a particular "turn". The more moments the story history contains, larger its resource requirements—both in-memory and within the serializations. I usually recommend setting Config.history.maxStates to 1 for projects which are intend to be grand in scope—like an RPG.

    As a suggestion, ditch any object hierarchy that you don't actually need—e.g. main and misc don't seem like useful categorizations/sub-objects.

    Kokonoe wrote: »
    Another, somewhat related, question is how would I unset a specific property? Using:
    /% Something that wouldn't happen, but as an example. %/
    <<unset $bob.misc.disposition>>
    
    removes the entire object Bob. As a workaround, I would use:
    <<script>>delete (variables().bob.misc.disposition)<</script>>
    
    But that seems like an improper way to do it.
    The <<unset>> macro's documentation is fairly clear that it unsets TwineScript variables, not object properties. Removing a property from an object is a job for the delete unary operator.

    I'd probably have used something like the following, though:
    <<run delete $bob.misc.disposition>>
    
  • edited August 2016
    You could keep the disposition data separate from the rest of Bob's data, however, you'd run into the same issue with any piece of NPC data which wasn't immutable. As soon as you mutate something, you have to keep track of it and the way story formats do that is via whatever passes for their variable store. In summary, there's no easy way built-in to unload Bob without losing persistence. Can it be done? Sure, but you'd have to write your own subsystem for it.

    The Config.history.maxStates property controls the maximum allowed number of moments which can be stored within the story history. Each moment is chiefly a snapshot of the variable store for a particular "turn". The more moments the story history contains, larger its resource requirements—both in-memory and within the serializations. I usually recommend setting Config.history.maxStates to 1 for projects which are intend to be grand in scope—like an RPG.

    As a suggestion, ditch any object hierarchy that you don't actually need—e.g. main and misc don't seem like useful categorizations/sub-objects.

    The <<unset>> macro's documentation is fairly clear that it unsets TwineScript variables, not object properties. Removing a property from an object is a job for the delete unary operator.

    I'd probably have used something like the following, though:
    <<run delete $bob.misc.disposition>>
    

    Thank you so much for the help. Claretta mentioned that they use several hundred variables without a problem, so I'm not too worried about it scaling anymore. I imagine that the variables will perform better when I'm not intentionally trying to stress everything.

    I'll drop the unnecessary hierarchy elements as suggested. They were mostly for an organizational purpose, but aren't intended to serve any in-game function.

    And you're right, the documentation does clearly state that the <<unset>> macro works on TwineScript variables. Over the past couple of months, I've read through parts of SugarCube 2's documentation almost daily, yet I'm still discovering things.

    Thanks again.
  • edited August 2016
    My game is incredibly complex, but I only use a single state history as TheMadExile suggested, and I only use basic <<if>> and <<set>> commands with my variables, no for loops or even arrays. I feel the simpler I can keep things, the better, even in a complex game.

    Is probably why I don't run into performance issues.

    The other thing which helps my performance is I run my game as a native desktop app through nw.js and not in a browser. If you are making an app, rather than an online game, it's always best to do all testing in the final format, as there is a large performance difference between browsers and apps.
Sign In or Register to comment.