Skip to content

Glk Multi Display Support

Andrew Plotkin edited this page Mar 14, 2015 · 6 revisions

The Glk API assumes that the application is managing exactly one Glk game display at a time. This fits poorly with the modern desktop GUI, in which a game interpreter might want to display several windows with a different game in each.

Interpreter applications typically work around this by launching sub-processes, each of which manages one game in one window. This is adequate, but we would like a better solution.

This is a proposal for a Glk interface update which supports multiple displays within a single application, without breaking backwards compatibility.

(I'm saying "display" here because "window" already has a meaning in Glk.)

(This plan deals only with the C header glk.h and C library implementations. The Javascript GlkOte implementation already supports multiple Glk displays in separate browser windows -- each browser window is a separate Javascript context, so we get that for free. It could be extended to support multiple Glk displays in a single browser window, but this is not very important.)

Changes to the glk.h header

The glk.h header should serve both old (single-display) libraries and new (multi-display) libraries. Therefore, we need some preprocessor abuse!

Function definitions will now look like:

/* extern void glk_put_char(unsigned char ch); */
GLK_DECLARE_FN1(void, put_char, unsigned char ch);

(GLK_DECLARE_FN1 means "declare Glk function with one argument". GLK_DECLARE_FN0 to GLK_DECLARE_FN6 will be available.)

If the symbol GLK_LIBRARY_MULTI is not defined, this will compile to the old-style declaration, as shown in the comment. If GLK_LIBRARY_MULTI is defined, it compiles to two declarations, one with an extra argument:

extern void glk_put_char(unsigned char ch);
extern void glkmd_put_char(glk_context *gcontext, unsigned char ch);

The library will maintain a global variable:

extern glk_context *glk_context_singleton;

A call to the old-style function will invoke the new-style function using this context. That is, glk_put_char(ch) will be implemented as a call to glkmd_put_char(glk_context_singleton, ch). This allows old-style applications to compile with minimal change.

(These boilerplate old-style functions will live in a new file gi_multi.c, which will be distributed along with gi_dispa.c and gi_blorb.c.)

The declaration glk_main() will be duplicated in the same way, as glkmd_main(context). These functions must be defined by the application.

Note that some functions, such as glk_char_to_lower(), don't interact with the display state. For consistency, these will also get glkmd_XXX(context) implementations, but they can ignore the context argument.

Changes to gi_dispa.c

When GLK_LIBRARY_MULTI is set, the gidispatch_function_t struct will contain pointers to both the glk_XXX() and glkmd_XXX() versions of each function. (This will entail more preprocessor abuse.)

The gidispatch_call() entry point will remain, calling glk_XXX() functions. Under GLK_LIBRARY_MULTI, there will also be gidispatch_call_multi(), with an additional context argument, which calls the glkmd_XXX() functions. (I haven't decided what kind of evil I will use to accomplish this, but nobody else ever has to read gi_dispa.c, so don't sweat it.)

Changes to libraries

An old-style library can import the new glk.h header file, and not define GLK_LIBRARY_MULTI. It will build the way it's always built, no changes needed.

To fully support multi-display Glk, a library will have to be refactored so that all Glk display state is contained in a glk_context struct. (The library must define this struct type.) It will have to rename all its glk_XXX() functions to glkmd_XXX(), adding a glk_context * as the first argument.

(The library will not have to define glk_XXX() functions. Those will be provided by gi_multi.c.)

(CheapGlk, GlkTerm and iOSGlk will remain as old-style, single-display libraries. RemGlk will become a multi-display library, although this is more a proof of concept than a useful feature. A RemGlk interpreter can be launched with multiple games; input and output for each game will be tagged with a distinct session ID.)

Changes to library startup code

The library startup code is the C main() function. In an old-style library this checks command-line arguments, initializes everything, runs glkunix_startup_code() (application-specific startup code), and then calls glk_main() followed by glk_exit().

A multi-display library is different. The obvious case is a Mac/Windows application which can open multiple game windows. A library built to support this will launch with some kind of file selection UI. The user can select additional game files at any time; each one becomes a new Glk context.

This will probably require two startup functions. There might be a glkosx_startup_code() to set up the UI toolkit and display the file selection UI. Each file selection triggers a glkosx_startcontext_code() call, which would open the game file and prepare the game, followed by a glkmd_main() call.

On the other hand, not all applications will behave this way. You might build a standalone game application which launches straight into the game. So the glkosx_startup_code() call will need a way to signal this. If it signals "wait", the library startup code will wait for files to be chosen (however that works). If the glkosx_startup_code() call signals "immediate", the library startup code will proceed directly to glkosx_startcontext_code() and then glkmd_main().

(I am not specifying this API. It's up to the library, although of course it's nice if they all follow the same pattern. For backwards compatibility, "immediate" should be the default behavior.)

Changes to applications that don't care about this stuff

A multi-display library always calls glkmd_main() rather than glk_main(), because it has to pass in the context.

To compile a Glk application with a new library therefore requires some changes. However, if you want to keep using a single display, the changes will be minimal.

You'll choose the "immediate" path (see above). The application will still need a glkmd_main() function, but it can simply add this code:

#ifdef GLK_LIBRARY_MULTI
void glkmd_main(glk_context *gcontext) {
  glk_main();
}
#endif /* GLK_LIBRARY_MULTI */

The argument in this case will be glk_context_singleton, so the existing glk_main() implementation will continue to work.

Changes to applications to support multiple displays

XXX TODO

Clone this wiki locally