[Snowman 2] Saving and autosaving

I give up. I’m not knowledgeable enough in JS to be able to setup a saving system in Snowman 2, even less an autosave one. I’m stuck at this:

Saving:

window.localStorage.setItem('save',window.story.save())

This saves the state to localStorage, but also has unwanted results like changing the browser URL and adding an entry to the browser history.

Loading:

window.story.restore(window.localStorage.getItem('save'))

This works fine as far as I understand.

I’d also like to write an autosave system, running the save function for every passage on the event sm.passage.shown, but it doesn’t work so far. First I’d need a working save function without save effects, I think.

Can anyone help? Thanks!

Short “TL;DR” answer:

The method you need—<Story>.saveHash()—was removed by the new owner of Snowman. He wants to be convinced that it needs to be restored. Thus, someone needs to convince him of that.


Long answer:

As I noted in your previous thread about this, the method you really want <Story>.saveHash() was removed by the new owner of Snowman, Dax Cox (a.k.a. videlais). I think the reason he gave for doing so was poor, but it is what it is.

He did note that you could attempt to convince him to put it back:

As to that, I’m unsure what was lacking in the reason I gave in that very thread: (emphasis added)

Case in point, I’ve already written—a couple times now (here’s one example)—automatic save systems for, previous, versions of Snowman, similar to what you’re now attempting, that depended on <Story>.saveHash(). They’re irrevocably broken in the current release of Snowman.

But surely there must be some way to do savegames using only story.save(), isn’t it? (Though I have no idea how.)

Not if you want to avoid the side-effects mentioned within your OP. The only official way, currently, to get at the serialization of the playthrough state is via <Story>.save(), and that method does the other things that you want to avoid.

In the short term, you could try something like the following which attempts to polyfill the <Story>.saveHash() method: (put this before the rest of your startup code)

/*
	Attempt to monkey patch Snowman 2.0's `Story` class to
	polyfill the removed `<Story>.saveHash()` method.
*/
if (!Story.prototype.saveHash) {
	Object.defineProperty(Story.prototype, 'saveHash', {
		configurable : true,
		writable     : true,
		value        : function () {
			return LZString.compressToBase64(JSON.stringify({
				state          : this.state,
				history        : this.history,
				checkpointName : this.checkpointName
			}));
		}
	});
}

WARNING: While that should work in the short term, it is not a good long term solution as it depends on Dan not making any changes that would break it. Having an officially supported method really is the best way forward.

2 Likes

Thanks for this! If this is the case, I think I’ll stay in Snowman 1.3 until a reliable solution is available.

I’ll work on getting the function back in, @pseudavid.

@TheMadExile: I’m much more likely to take your advice and suggestions, across multiple public channels, if you are less of a jerk about how you present it. Insulting me indirectly in multiple ways (here and on GitHub) just makes me want to ignore you completely.

1 Like

@videlais It was not my intention to give offense, here or elsewhere. If I’ve done so, then you have my apologies.

That said, I’m unsure what I said here—and especially on GitHub, assuming it was recent—that was insulting. May I ask for clarification via a message wherever you’d prefer?

@TheMadExile: Calling my reasons “poor” or my previous choices “bizarre” here. Your language choices to emphasize your own suggestions over and over again makes me look foolish when, in reality, I’d already been convinced. I just hadn’t made the change yet.

@pseudavid: If you still want to use Snowman 2.0, I’ve updated it to 2.0.3 just now. The builds directory has direct links to the different versions you can use to load them in Twine 2.

If you don’t want to wait for the latest Twine 2 update, you can load a new story format directly from the story listing. Click on “Formats” and then “Add a New Format”. Copy and paste the exact URL for the version. For example, for 2.0.3, that would be https://videlais.github.io/snowman/builds/2.0.3/format.js

Thanks @videlais. I’ll look into it.

For the record, I don’t care about having one solution or another! I just want one that works, and I totally admit that I am underprepared to make it work myself, since I barely understand JS, so perhaps the functions in 2.0 were enough but I was missing something to make it work.

Nah. You got it, @pseudavid. I should have made the change weeks ago instead of waiting till tonight. It’s fixed in the repo now and the new, updated version will go out with the next Twine 2 update in a few weeks for future folks.

1 Like

Given the circumstances, which I’ll PM you about, I don’t think that my choice of words were all that far off the mark, but I will, again, apologize for any offense you’ve taken from them. It was not my intention to ruffle your feathers.

1 Like

With Snowman 2.0.3, I got autosave working. This is the saving code:

window.localStorage.setItem('save',window.story.saveHash())

And this is the loading code:

window.story.restore(window.localStorage.getItem('save'))

The complete autosave function, which checks the passage for a “nosave” tag so the game is not saved in non-game passages (main menu, etc.) is like this:

$(window).on('sm.passage.shown', function(event, eventObject) {		
	// If the passage is tagged "nosave", don't save. 
	if( _.contains( eventObject.passage.tags, 'nosave' ) == false) {	
		// Saving to browser's localStorage
		window.localStorage.setItem('save',window.story.saveHash())		
	} else {
		console.log( "Not saving history.")
	}		
});

I notice your solution includes no error checking in relation to accessing the window.localStorage object:
a. when the object doesn’t exist. eg. web-browser’s that don’t support Web-Storage.
b. when object has restricted access. eg. privacy mode, disabled cookies, viewed via file:// protocol, etc.

Yeah, that’s sadly beyond me… I first tried following the old code from the cookbook that seems to do that and adapt it to the new Snowman, but something didn’t work.

A question, what about the file:// protocol? I’ve written all my stories locally and saving worked. Is it a recent thing? Or perhaps a Chrome thing (I use Firefox and I may undertest Chrome).

Yeah, Chrome enforces much stricter security on file:// URLs than Firefox does. But I don’t think it’s a new thing…

1 Like

Interesting. I’ve just tested the savegame system in my IFcomp game (Snowman 1.3), opening the local file with Chrome, and it did work. However I’ll work a bit more to see if I can make the cookbook saving system work in Snowman 2.

Most browsers do not critically alter the functionality of the Web Storage API based on the scheme, so use of the file:// scheme should not generally be an issue. Some known exceptions, off the top of my head, are: Microsoft’s desktop browsers—i.e., both Internet Explorer and Edge (as of Dec 2019).

You’re much more likely to have issues with the Web Storage API based on two things: the player disabling 3rd-party cookies and/or running the game within a private browsing mode—e.g., In Private, Incognito, etc. The former is an issue because browsers, generally, lump control over the Web Storage API in with their 3rd-party cookie controls. The latter because private browsing modes usually disable, or outright break, all in-browser storage providers.

Unfortunately, the actual behavior of a missing, disabled, or broken Web Storage API very much depends on both the browser and version used. Observed behaviors: API simply does nothing, API throws an exception, API is completely undefined, etc. This is why feature testing the Web Storage API is important.


An additional issue. You’re using a static key name—i.e., 'save'—to store your saves. The issue there being that browsers silo/segment Web Storage instances based on origin. Thus, all pages and/or web applications placed into the same origin by the browser will get the same Web Storage instance. Meaning that using non-unique/common key names—i.e., 'save'—when you do not control the origin is problematic as anything within same origin could trample the stored values or vice versa.

In particular to the file:// scheme, Blink-based browsers—maybe WebKit too—generally place all locally opened files within the same origin, with the same storage instance.

To avoid potential issues, I’d suggest using something unique to the project, like a pen name combined with your project’s name—e.g., 'Pseudavid:AGameOfGnomes'; or if you’ll be storing multiple keys, that plus a description of the data, 'Pseudavid:AGameOfGnomes:save'.

1 Like

Thanks! I’ll look into all that. I generally use a more complex name for the localStorage saves, just made it simpler for the example.

The thing with browsers breaking the games got me thinking. This might be an issue for the conservation of twines in the future. I’ve already suffered a minor inconvenience with that.* It looks improbable that future browsers would break basic HTML and CSS, but what if we use some fancy library, or what if browser break a lot of secondary features? What could we do to make twines more future-proof? (Is is possible? Is there actually anything more future-proof than Twine?)

*I distribute everything with my games. That includes the fonts: no downloading of web fonts. Turns out that a browser change in 2019 about something CORS something prevented fonts from loading if you downloaded my 2018 game and played it offline. So I had to patch it, now I don’t include font files but I encode them with base64 in the CSS. This may be an interesting detail to put somewhere in the Twine docs.

note: Due to reasons related to the Twine Trademark, please don’t use the term “twines” when describing a Twine generated story HTML file / web-application.

Future proofing against “web-browser implementation” changes (like those you mention) is a common headache for anyone developing a web-application, and unfortunately there isn’t a simple answer to this problem.

Greyelf is spot on. There are things which can be done to help mitigate potential issues—e.g., aggressive feature testing—but ultimately the balance of power rests in the hands of the various standards committees and browser manufacturers. Thus, if they decide to make significant changes to how parts of the various web technologies work, which could include breakage, then there’s not much people using the technology can do about it—aside grin and bear it.

Depending on the scale of the change, the fallout could range from: largely unnoticeable, a minor annoyance, demanding a workaround/rewrite either of the author’s code or, worse, the story format itself—anecdote follows.


Here’s a very relevant anecdote from 2015 that bit story formats themselves right in the keister, which some may recall.

Prior to Aug/Sep 2015, most of the then current story formats provided access to their history controls by integrating them with browser’s built-in history controls via the History API. Players were generally happy with that setup and things were good. Then Chrome 45 rolled out and things got ugly.

With that release, the Chrome developers attempted to reduce the risk of certain types of spoofing/phishing attacks by excluding blob and filesystem URLs from being allowed with the ‘self’ source defined by Content Security Policy directives. As one consequence of that change, locally opened files—the aforementioned filesystem URLs; i.e., those opened via file://—would no longer be allowed to use the History API in Chrome—quite possibly any Chromium-/Blink-based browser.

The fallout was that then current story formats were suddenly broken in the affected browsers for local play—the API would simply throw an exception, crashing the game. The end result of that fiasco was that story formats largely abandoned the History API and moved their history controls completely into their own UIs, separate from the browser’s.

It caused quite the gnashing of teeth among players, authors, and story format developers at the time—players and authors because suddenly their games stopped working, story format developers because we had to rewrite a core chunk of our story formats.

In fairness, that sort of apocalyptic change is extremely rare, so no one should be overly concerned—the sky is not falling. I’ve regaled you with the anecdote simply to reiterate Greyelf’s point that there’s no simple solution, for anyone, to the looming issue of future technology changes breaking existing code.

1 Like