Development Progress Report, weeks 44-46

A three-week jump this time, due to a lack of time on devblog day. There are some developments that I’m excited about.

Optimisation

I did a mess of refactoring that didn’t seem to do me much good. Profiling the code suggested that I was spending an inordinate amount of cycles in constructors and destructors, so I rewrote and refactored and it didn’t seem to help all that much. Sure, the engine is fast, but it didn’t seem anywhere near as fast as I thought it should be.

The problem was C++11’s new auto syntax. Well, no … the problem was actually me and how I was using it.

C++11 allows you to easily walk through all the elements of a container thusly,

for(auto x : containername)
{ ... }

The auto keyword cheerfully infers the correct value type from the container, and off you go.

The problem here is that when I looked at the type that was being inferred, it was basically copying elements. That makes perfect sense, and in most cases was exactly not what I wanted it to be doing. For speed, I wanted to use references to container elements, not make copies of them that I would then have to throw away. Essentially for any class-type, I’d be running at least one constructor and a matching destructor, every time around the loop, more or less.

As the umpteenth person who has discovered this, I suddenly realised where all of my time was going. My constructors/destructors weren’t actually in need of optimisation. Instead, I was constructing/destructing objects tens-of-thousands of times more often than was needed! The profiler was right about that being where my time was going, but had misled me as to why it was.

Could I fix that while still using the auto keyword? Absolutely! I could qualify the keyword as auto& to give me a reference, or even const auto& (which is what I actually wanted most of the time).

(Why not just replace all the instances of auto with an explicit type? Some of the types involved were pretty complex, and there could be a lot of keyboard-hammering involved. I could have used typedef to simplify some of those, but it would have led to a lot of those which wouldn’t, in itself, improve readability. Nevertheless, I did change some of the simpler ones to explicit types.)

After spending about a day, in pieces, to rummage through the code, I made all of the necessary changes. I don’t believe there are any more cases that I’ve missed.

The result has been a massive improvement in speed at every level. Using the test-character (which runs through the narrative without any graphical output), the entire narrative now runs to completion in about 1600ms! Load and save times are faster, and the UI is snappier and more responsive. The whole thing is blisteringly fast, now.

That’s a huge win and an important lesson. I wonder how many other programmers fell afoul of the same thing before me. A lot, I imagine.

All of this was part of a larger optimisation and refactoring effort, all of which has contributed to the final result, and made some sections of code a lot clearer.

Auto-advance

I’ve implemented an auto-advance feature, which can be toggled on and off. When on, the player no longer has to click through to the next piece of text. The auto-advance delay is configurable in the options screen, and defaults to about three seconds.

This brings me back to the commonly-implemented VN feature where you can fast-forward over text that you’ve already seen. I’ve got a fast-forward feature, but it doesn’t know whether you’ve seen the text or not. It just assumes that you know what you want to do.

The problem with a fast-advance-over-text-you’ve-seen feature is that it is actually really, really difficult for the engine to track which text you have seen (for much the same reason that this story is impractical to voice). It can’t be done by line – since lines of text may individually vary in many ways, based on past character choices.

Entire passages may or may not appear, individual lines might be expressed in different ways. Even comparing with a log of all past transcripts is potentially fraught, because of line-displacement. Even if I were to give every source text-passage its own globally unique identifier, and then add in a hash of actual output, it still isn’t a guarantee that the engine can guess which passages of the story you have definitely seen or not seen in their current forms and positions, and even then, it would require oodles of storage and time-wasting lookups.

I’m not sure that’s a solvable problem, but I’ll think about it again, down the track.

Which brings me to…

Charting narrative flow

Making a chart of how the narrative flows from scene to scene is becoming difficult as well. It’s not that you can’t chart how scenes link to each-other, it’s just that when you do, the resulting chart doesn’t give you a useful representation of how the narrative flows. SNAFU has eleven characters, and narrative branching and variation happens at the sub-scene level as well as at the scene level. Some scenes take functional or procedural roles.

If a chart or diagram conveys no useful information, is there much of a point to it?

Shortly into Chapter two, for example, we indulge in a bit of combinatorial complexity. Eight characters are presented with various choices, yielding 84 possible narrative opportunities (assuming I work hard to limit the scope). Each character is exposed to between 0 and 8 of those possible vignettes in some order. In some cases two or more of those can be bundled into a single variable scene. On top of it, the whole sequence needs proper per-character, choice-aware narrative preambles and postscripts, so that the end-result reads as a single, continuous, logical narrative.

And ultimately, it’ll probably take you about two minutes or so to read through, since, while it is important to the narrative, it is not a set of scenes that requires a lot of actual wordage. It will almost certainly be quite different each-time, though.

None of this, incidentally, is generative text. It’s multilayered, but ultimately hand-written narrative. That’s really the only way to make it feel right. Talk to me again about generative text after another couple decades of AI development.

Expressions and variables

It was high-time that I added proper variables to the expression evaluator. It could handle numeric and string constants, booleans, and functions returning any or all of these, but it didn’t have any actual variable storage, or syntax for them. Twelve lines later, and that was sorted.

I’ve got variables now. Integer ones, at least. I can add string types later, when or if I start feeling like I need them.

Along the way, though, I noticed a copy-paste error in the expression-evaluator’s operator-handling. It seems that when I’d originally popped in the code to handle addition, subtraction, multiplication and division, I’d copied some blocks of code and then gotten distracted by something shiny. So what I actually had was addition, subtraction, some more subtraction, and subtraction again.

Theoretically only a two-character fix, except that me being me, I had to also stop and check for a potential divide-by-zero, and handle it gracefully in the execution context of the story. That took just a tad longer. Crash-to-desktop is way simpler than being graceful.

Scene properties

I needed to extend scene properties to a few more arbitrary flags. There are ways I could have possibly done this that would not have broken the save-file format, but they really weren’t very elegant. So, I broke the save-file format for the first time in quite a while. If I ever have to do it again, hopefully it will be quite a while off.

A lot of this had to do with scene conflicts. While there’s some intricate ways of setting preconditions for scenes, a scene was previously marked as either ‘completed’ or ‘not completed’.

In order to extend the scene dependencies/conflicts system, I needed to know a bit more than that, like if a scene was being excluded from consideration for some other reason. That is, incomplete but not going to be executed either. Things like that. Some of that stuff I mentioned about the vignettes in Chapter two rely on that kind of logic. Bunches of scenes are tossed into the queue, only the ones with satisfiable preconditions are executed, and then other scenes have to be fired off – but only once all the others are either done, or determined to not be going to be done. And at times we’ll need to know which happened.

There’s more code to write to extend this system, including some new functions for the expression evaluator, but it shouldn’t impact on save-file formats, at least.

Shuffle

A minor update to the character-selection screen. Characters are normally presented in the order of their internal actor-IDs. That should be changeable, ultimately. For now, I’ve just tossed in a parameter that allows the story to specify that the order should be shuffled, so that they’re not always in the same order. Later, I’ll allow the story to specify alternate orderings.

Screenshots

Had to revisit screenshot logic again.

The display consists of a whole lot of asynchronous… things. All doing their thing.

In order to have a useful screenshot that we can attach to a save-file, we have to take that shot at the right time. With all sorts of asynchronous wibbly-stuff going on, you’ve got to pick your moments.

That means not taking grabs of the main menu, or the preferences screen, or modal dialogues. No, you want the screen that the user will actually see when the story reloads. That’s a matter of minding a lot of contrary indicators, and then grabbing a snap when you can. I had a few run-ups at this logic before I finally settled on one that – so far – seems to satisfy all the necessary conditions.

Music

There’s new music in the story. It’s not yet for every character, nor necessarily throughout. It’s also not final, but having various transitions in the background music adds a lot to the storytelling.

It’s in the current builds, alongside some of the existing placeholder music. There will be more soon!


Add in a bunch of narrative writing (not nearly as much as I think I should be getting done), and a zillion other little tweaks and improvements, and that’s where we’re at by week 46!