-
Notifications
You must be signed in to change notification settings - Fork 4
Glk Multi Display Support
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.)
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.
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.)
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.)
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.)
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.
XXX TODO