RemGlk: remote-procedure-call implementation of the Glk IF API

RemGlk version 0.3.0
Designed by Andrew Plotkin (erkyrath@eblong.com)
(Glk home page)

The RemGlk library is copyright 2012-22 by Andrew Plotkin. The GiDispa, and GiBlorb libraries, as well as the glk.h header file, are copyright 1998-2022 by Andrew Plotkin. They are distributed under the MIT license.

This documentation is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License.


What is RemGlk?

RemGlk is a C library which implements the Glk API. You can compile a Glk application and link it with this library.

RemGlk does not provide a user interface. Instead, it wraps up the application's output as a JSON data structure and sends it to stdout. It then waits for input to arrive from stdin; the input data must also be encoded as JSON.

RemGlk is therefore like CheapGlk, in that it works entirely through input and output streams, and can easily be attached to a bot or web service. However, unlike CheapGlk, RemGlk supports multiple Glk windows and most Glk I/O features. Whatever it's attached to just has to decode the structured output and display it appropriately.

RemGlk was designed to complement the GlkOte Javascript library. GlkOte more or less implements the other half of the job: it accepts game output (as Javascript objects), displays it, and then returns the player's commands in the same format. It is possible to connect an IF interpreter, RemGlk, and GlkOte to build a complete IF web service. (See an experimental implementation of this idea.) Since RemGlk supports autosave/autorestore, it is practical to create a service which fires up a RemGlk process momentarily to process each player command.

RemGlk is also used by the RegTest testing framework. In this setup, the "display layer" does not actually display anything. It is a Python script which generates test input and verifies the game's output.

Using RemGlk

Starting up

Once you have compiled your application, run it as usual. For glulxe, this probably looks like:

glulxe Advent.ulx

If you do this, you will see nothing at first. The application is waiting for an initial input which describes the display and font sizes. Try typing this, exactly:

{ "type": "init", "gen": 0, "metrics": { "width":80, "height":24 } }

This tells RemGlk that it has an 80x24 "screen" to work with. The default "font size" is 1, so this gives the effect of an 80x24 terminal window.

If you instead entered:

{ "type": "init", "gen": 0, "metrics": { "width":400, "height":600,
  "charwidth":12, "charheight":14 } }

...you would get a layout for a 400x600 pixel window, assuming a 14-point font with 12-pixel-wide characters.

(The font size is only critical for grid (status) windows. You can approximate the measurements for buffer (story) windows; these typically have proportional fonts and perhaps various font sizes as well.)

The metrics object also allows you to specify margins and border spacing, which improve the layout behavior. See below for details.

You can also include information about what optional features you are prepared to support. (RemGlk needs to know this in order to set appropriate gestalt flags for the game.)

{ "type": "init", "gen": 0, "metrics": { "width":80, "height":24 },
  "support": [ "timer", "hyperlinks" ] }

The support field is a list of strings. Currently the recognized values are:

Starting up with no init event

For some scenarios, often including debugging, you want the application to not wait for an init event. You can request this with the -fixmetrics (or -fm) flag. You can also specify basic layout information on the command line. If you type:
glulxe -fm -width 80 -height 50 Advent.ulx

...then the game will start immediately (with an 80x50 "terminal window"), and send its first screenful of output. Your first reply will then be a player command; see below.

In -fm mode, you must specify optional features on the command line. Use the -support argument with one of the support strings listed above (-support timer, -support hyperlinks, etc.).

The first screen of output

For Adventure, the initial output looks something like:
{"type":"update", "gen":1,
 "windows":[
 { "id":25, "type":"grid", "rock":202,
   "gridwidth":80, "gridheight":1,
   "left":0, "top":0, "width":80, "height":1 },
 { "id":22, "type":"buffer", "rock":201,
   "left":0, "top":1, "width":80, "height":49 } ],
 "content":[
 {"id":25, "lines": [
  { "line":0, "content":[{ "style":"normal", "text":" At End Of Road
     Score: 36    Moves: 1      "}]}
 ] },
 {"id":22, "clear":true, "text": [
  {"append":true},
  {}, {}, {}, {}, {},
  {"content":[{ "style":"normal", "text":"Welcome to Adventure!"}]},
  {},
  {"content":[{ "style":"header", "text":"ADVENTURE"}]},
  {"content":[{ "style":"normal", "text":"The Interactive Original"}]},
  {"content":[{ "style":"normal", "text":"By Will Crowther (1973)
     and Don Woods (1977)"}]},
  {"content":[{ "style":"normal", "text":"Reconstructed in three steps by:"}]},
  {"content":[{ "style":"normal", "text":"Donald Ekman, David M. Baggett (1993)
     and Graham Nelson (1994)"}]},
  {"content":[{ "style":"normal", "text":"[In memoriam Stephen Bishop
     (1820?-1857): GN]"}]},
  {"content":[{ "style":"normal", "text":"Release 5 / Serial number
     961209 / Inform v6.21(G0.33) Library 6/10 "}]},
  {},
  {"content":[{ "style":"subheader", "text":"At End Of Road"}]},
  {"content":[{ "style":"normal", "text":"You are standing at the end of
     a road before a small brick building. Around you is a forest. A small
     stream flows out of the building and down a gully."}]},
  {},
  {"content":[{ "style":"normal", "text":">"}]}
 ] } ],
 "input":[
 {"id":22, "gen":1, "type":"line", "maxlen":256 }
 ]}

This is just to give you a feel for what's going on. Roughly, this says:

(The window ID numbers will vary from run to run, by the way. Window rock numbers are consistent for a given game, but may vary from game to game.)

The first input

A human types "go east" into some user interface, which then passes the following input to RemGlk:

{ "type":"line", "gen":1, "window":22, "value":"go east" }

The library accepts this, mulls it, and generates a new output update. So it goes.

Referring to resources

RemGlk supports Blorb-packaged games which include images. (Sound support is on the to-do list.) However, it does not try to encode the image data in the JSON output. Instead, it assumes that the display library (GlkOte) has access to the same images. When the game draws an image, RemGlk sends a JSON stanza which includes the image number and size. (See the GlkOte documentation.) The display library is then responsible for finding the image with that number.

To make this easier, you can configure RemGlk to include an image URL in the stanza. Do this with the -resourceurl (or -ru) flag:

glulxe -ru http://example.com/datadir/ game.gblorb

The URL should end with a slash, and should refer to a directory containing image files. RemGlk will send URLs with the format http://example.com/datadir/pict-1.png, http://example.com/datadir/pict-2.jpeg. (Note lower-case, and "jpeg" has an "e".) (This is the same filename convention used by blorbtool.py with the giload command.)

You may also specify a local pathname with the -resourcedir (or -rd) flag:

glulxe -rd path/dir game.gblorb

RemGlk will convert the pathname to a file: URL and send it as above.

The Data Format

I have not written out this documentation in detail. Please refer to the GlkOte documentation. Input what GlkOte outputs, and vice versa.

Prior to version 0.2.1, this library used contents and inputs as the names of the top-level data fields, rather than the correct content and input. The prior version also omitted the xpos and ypos fields for grid window input.

As of version 0.2.2, the library accepts noninteger numbers. If the field expects an integer, the values will be rounded (towards zero). As of 0.2.4, some of the fields in the metrics object expect non-integer numbers.

As of version 0.2.6, the library accepts debuginput events. They are passed along to the game (interpreter) via the gi_debug.h interface.

As of version 0.3.0, the library accepts noninteger values in the metrics object. (GlkOte can send fractional values when the browser zoom is changed.) Window positioning will always be in integers, rounded so that everything fits properly.

A note on JSON: this is a strict (in both senses) subset of Javascript's data literal format. The biggest differences are: you always use double quotes, never single quotes; strings are always quoted, but numbers never are; an object (dictionary) always has quotes around its keys.

All input and output is UTF-8.

When generating output, a complete update JSON update will be followed by a blank line (two newlines in a row). You can use this as a hint for breaking the output stream into stanzas. (But RemGlk does not require you to follow this convention when sending it input JSON objects.)

Autosave/autorestore

Autosave and autorestore are available as of version 0.3.0. If the interpreter implements autosave, it will use RemGlk's facilities to store a library serialization dump every turn (before each glk_select() call). This state dump is JSON, but it does not follow the GlkOte format specification. It contains more detailed information, including the "live" state of every window and stream.

The autosave format is deliberately not documented; it is specific to the implementation of RemGlk. It is not meant to be transferred between platforms or between interpreters. Use the interpreter's normal .glksave save files for that.


Last updated July 3, 2022.

Glk home page