RemGlk autosave

Remember back, oh, about April – a million weeks ago – I said that I wanted to get autosave working in Glulxe+RemGlk?

It works now. See the bleeding-edge repositories:


This may be useful if you’re creating a bot or online game-playing service. It supports two slightly different use cases:

  • Hedging against the possibility of process termination

This is how the iOS interpreters work. (Or worked, since I’m not supporting them on current iOS.) The app starts and runs normally, but it could be killed at any time (when in the background). Therefore, we autosave every turn. At startup time, if autosave files exist, we restore them and continue play.

  • Single-turn operation

This allows you to run an ongoing game with no background process! Every time a user input arrives, you launch the interpreter and shove the input into stdin. The interpreter reads the autosave file, processes the input, generates a turn’s worth of output, updates the autosave, and exits.

See the README file at https://github.com/erkyrath/glulxe for more info on using the interpreter in these two modes.

3 Likes

I’ve run through a bunch of the Glulx unit tests with this interpreter (in single-turn mode). It seems to work, but there are undoubtedly bugs lurking. Keeping a stream open for several turns is a likely trouble spot. (I tested keeping a transcript file open, at least.)

In case you’re wondering: the Fizmo Z-code interpreter has support for the autosave API. However, it’s old (2012!) and is mostly written in ObjC, because I only ever used it on iOS. It could be updated to work with this new RemGlk, but I’m not sure when I’d get to it. Feel free to give it a shot…

2 Likes

So I’m in the process of changing how Emglken works: I am getting rid of my custom Glk library and incorporating the whole of Remglk, and so far it’s been surprisingly straightforward.

I have a couple of questions about the autosaving stuff:

  1. How graceful is it with autosaves produced from older versions or even entirely different autosaving systems? If we put an autosaving version of Glulxe in Lectrote will it be able to handle (ie, ignore) the autosaves from Quixe?

  2. If I want to build Glulxe without autosaving (or another terp that doesn’t have autosaving), will it work to just exclude unixautosave.c? I’m wondering will the dead code eliminator then clean up everything from the libremglk.a. Probably? But it looks somewhat closely integrated. I could comment out the GLKUNIX_AUTOSAVE_FEATURES definition, but I’m hoping for something that will work without me needing to maintain a fork.

Let’s see…

If I want to build Glulxe without autosaving (or another terp that doesn’t have autosaving), will it work to just exclude unixautosave.c?

You’re just trying to get the code size down? You can’t comment out GLKUNIX_AUTOSAVE_FEATURES in remglk (it won’t compile). You can add

#undef GLKUNIX_AUTOSAVE_FEATURES

after each #include “glkstart.h” line in unixstrt.c and unixautosave.c.

Autosave compatibility is not graceful at all. Lectrote’s assumption is that if the interpreter changes in any significant way, the autorestore will just fail and the game will start at the beginning. (The user might have to use the Reset menu option.)

This has never happened with Quixe. The advantage of slow development, I guess…

For supporting several interpreters, I’d say use the --autoname argument (or equivalent) and get each interpreter to use a different base filename.

Hmm, I’d prefer not to have to fork Glulxe, so I may just keep it in. It’s probably not too much code space compared with the whole.

I’m using a virtual filesystem and then passing the files to Dialog.js to handle, so the filenames don’t matter. But, I have just thought of a solution: I can add in a property to the JSON before giving it to Dialog, and then check if it’s there before giving it to the VM. That should work!

Yep, that’ll do it.

You can no longer compile Git with Remglk. Using the current code from each (and just changing Git’s Makefile to refer to remglk) it produces the following errors:

In file included from git_unix.c:9:
../remglk/glkstart.h:92:59: error: unknown type name ‘gidispatch_rock_t’
   92 | extern void glkunix_window_set_dispatch_rock(winid_t win, gidispatch_rock_t rock);
      |                                                           ^~~~~~~~~~~~~~~~~
../remglk/glkstart.h:95:59: error: unknown type name ‘gidispatch_rock_t’
   95 | extern void glkunix_stream_set_dispatch_rock(strid_t str, gidispatch_rock_t rock);
      |                                                           ^~~~~~~~~~~~~~~~~
../remglk/glkstart.h:98:62: error: unknown type name ‘gidispatch_rock_t’
   98 | extern void glkunix_fileref_set_dispatch_rock(frefid_t fref, gidispatch_rock_t rock);
      |                                                              ^~~~~~~~~~~~~~~~~
make: *** [<builtin>: git_unix.o] Error 1

Oh, hm. I think I messed up header dependencies.

If you want to get it built in a hurry, add #include "gi_dispa.h" to more .c files.

I pushed a fix for this. I hope.

1 Like

More trouble sorry. I’ve compiled Glulxe against RemGlk (both updated to the latest) in an Ubuntu 19.10 VM.

Running this command and then giving the following input (as suggested in the RemGlk docs) gives a segmentation fault:

./glulxe --autosave Advent.ulx
{ "type": "init", "gen": 0, "metrics": { "width":80, "height":24 } }

It creates an autosave.glksave but not an autosave.json.

Are you able to reproduce this? Any ideas?

Edit: I updated to Ubuntu 20.04 just to check, but it’s no different.

I can’t reproduce it on MacOS. That command and input work for me. I’ve tested everything using a debugging malloc (libgmalloc) so it’s not a simple error, at least.

I’ll give it a shot on a couple of Linux machines.

I think the problem is that the Linux C headers aren’t compatible with gcc -ansi. Functions like strdup() don’t get defined, and then there’s pointer size mismatches at link time.

I removed the -ansi argument from the Makefile and now it seems to work. (I don’t at all remember why I put it in there in the first place.)

Excellent, that’s working for me now. Thank you, I never would have been able to diagnose what was wrong.

I noticed that if you send an event for line input on a non-existent window (such as if you just copied the example from the docs and forgot to change the window…) nothing happens. Is that expected?

How is the process termination procedure meant to work? When you do an autorestore it doesn’t output the Glk state. And the autosave.json data is in a different format to what glkapi.js would produce, so we can’t just send it to GlkOte. In case it matters, I’m running this after killing the previous process.

./glulxe --autosave --autorestore -autometrics Advent.ulx

Does the autosave.json have enough info that glulxe could output a full GlkOte state? Maybe with an extra commandline option?

Edit: Oh, I didn’t realise that GlkOte also has a save_allstate function, not just GlkApi. I can probably use that in Emglken, though I’m curious as to how you’d do it with the regular console glulxe.

You use -singleturn so that the process shuts itself down.

You can’t pass the autosave.json to GlkOte; it’s not compatible. The idea is that GlkOte stays live, so it only needs incremental updates. The interpreter running with --autosave --autorestore -singleturn -autometrics gives incremental updates. That is, it should be giving the same output as a regular (blocking) RemGlk interpreter, running with no arguments, receiving turn-by-turn input.

The save_allstate in GlkOte was set up for Lectrote. I hadn’t thought as far as RemGlk running in Lectrote, but… save_allstate is one way of doing that.

The other option is for GlkOte to send { type: "refresh", gen: 0 }. Then RemGlk will send back a complete update. I haven’t tested that path, mind you.

I’m not sure about the pointer-size mismatches, but strdup(3) on my machine says:

strdup() conforms to SVr4, 4.3BSD, POSIX.1-2001.

…which is to say, it’s not part of ANSI C, and therefore it’s reasonable that gcc -ansi hides it. The manpage also says (under “Feature Test Macro Requirements for glibc”):

_XOPEN_SOURCE >= 500
    || /* Since glibc 2.12: */ _POSIX_C_SOURCE >= 200809L
    || /* Glibc versions <= 2.19: */ _BSD_SOURCE || _SVID_SOURCE

If you just run gcc without -ansi or -std=c99 or whatever, the headers define BSD-like functions, SystemV-like functions, and GNU extensions all intermingled, which is easy to use but can lead to portability surprises later on. On the other hand, if you want earlier warning about portability issues, you can choose what language standard you want with (say) -std=c99 and what platform standard you want with (say) -D_POSIX_C_SOURCE=200809L and if your code compiles it should be guaranteed to work on any platform that meets those requirements.

1 Like

I know how the single turn mode works, but I was asking how you intended for restoring killed apps to work. The refresh message does result in the text content being sent, but not the window definitions you’d need to put that text in, and you’d also need to parse the JSON to know which generation number to send. Seems like the best option is just for the app to autosave its output state alongside what glulxe autosaves.

I’ll have to look at that. It should resend everything GlkOte needs.

Since GlkOte can do that, it would be redundant.

1 Like