Categories
Eldritch 2

Dev’s Journal – 7 Mar 2025

I spent a bit of time of last weekend fleshing out Eldritch 2‘s first level a bit more, with spike traps and proper art for doors; and then I noticed that it was taking longer than usual to save the game, so I had to switch to investigating that.

Since as long ago as the original Eldritch (2013), the way I’d formatted save files in my engine is to serialize the current level and all previous levels that the player could backtrack to into a big buffer in memory, compress that with zlib, and write it to disk. When loading a game, the “backtrackable” levels would be unpacked and written to disk as separate loose files until they needed to be opened (either because the player returned to that level, or for inclusion in another main save file). The motivation for that design, if I recall correctly, was to keep memory overhead low; but a typical level’s save data in Eldritch was only on the order of 150-200 KB, and there was a cap of around a dozen or so levels which could ever be stored at once. The loose file structure was an unnecessary, premature, and slightly misguided optimization.

But Eldritch 2‘s levels are a lot bigger; the uncompressed save data for a single level is now hovering around 750 KB, and will likely continue to grow as I add more items and traps and enemies. But more importantly, there is no cap on the number of unique levels the player can visit in a single run. (There is technically a limit, but it’s around 1 million.) And it turned out, when the player visited a significant number of levels, the last-minute compression step before writing the save file got very expensive as it crunched through several MBs of save data. And the game autosaves every 30 seconds, which now produced a regular frame hitch. No good.

One option would have been to make my save process asynchronous; that’s something most game engines do and it’s only because my save files have historically been so small that I never bothered. But refactoring to support async saving wasn’t really addressing the core problem: every time I made a save, I was loading a bunch of loose, uncompressed files from disk, compressing them all together, and writing the bundle to disk. Why weren’t they compressed in the first place? And why weren’t they in memory, when the game’s total memory footprint is so low anyway?

So I just switched the order of operations around. Now, any backtrackable levels are compressed once, and only uncompressed when the player returns to that level. They stay resident in memory while playing, instead of being written and read as files on disk. And most importantly, the main save file (encompassing all backtrackable levels) is not compressed, because the individual level chunks are already compressed. So the compression cost of making a save file no longer scales with the number of visited levels. And while I was down this rabbit hole, I also evaluated each compression level that zlib offers, and decided that a faster/less compressed option (3) was a better fit than the default (6).

I stress tested the new system by traveling to 50 unique levels and then making a save. What previously took upward of 500ms (and would only get worse with more levels) now takes less than 3ms.