Notes on porting HTML TADS

Recent Changes Affecting Porting

Version 2.5.10

Version 2.5.8

Version 2.5.7

Version 2.5.6

Architectural Overview

First the good news: most of HTML TADS is portable code; you shouldn't need to make any changes to the portable parts in order to move HTML TADS to a new operating system. Now the bad news: it's not all portable code; some of the code is system-specific, and you'll need to re-implement this non-portable portion to get HTML TADS running on a new operating system.

Even though you won't need to make any changes to the portable code, you'll probably find it helpful to know a little about how it works, since you'll make use of services in the portable code in the course of implementing a new system-specific implementation. This section is a brief overview of the design of HTML TADS. HTML TADS has the following major components:

Steps in Porting HTML TADS

The remainder of this document describes the steps you should follow to port HTML TADS to a new system.

First, port the regular TADS to your system

The first thing you need to do is port TADS to your platform. Since TADS has already been ported to most platforms, this should just be a matter of finding the correct set of files for your system, setting up a build environment, and compiling. There's a makefile or its equivalent for most platforms as well, so you shouldn't need to figure out the build commands from scratch.

HTML TADS only uses the TADS interpreter, so you only need to build this component of the normal TADS at this point.

Obtain the Required Third-party Libraries

On Windows, HTML TADS depends on a number of third-party libraries to implement some of its functionality. In particular, the image format support is provided mostly by third-party libraries.

These libraries are not required by the portable code. The Windows implementation uses them, but you don't necessarily have to on another platform. The Macintosh version, for example, doesn't use any of them, because there are better Mac-specific equivalents. We mention these libraries only because they're highly portable, so if you don't know of a better option for your system, these are probably good bets.

If you do use these third-party libraries, it will mean that you'll need to do a little leg-work to integrate them. In the end, though, it should save you a lot of effort compared to implementing all of this functionality from scratch: these libraries are all free, of high quality, and are already highly portable.

The libraries you might find useful are:

Each of the libraries above has been widely ported; there's a good chance there's already a makefile for your platform included in the archive. If not, each includes documentation that describes how to port the library to a new platform.

The TADS virtual OS layer

The regular character-mode TADS has a "virtual operating system" layer of code. This is the interface that TADS uses to perform system-specific operations. The interface itself is defined portably -- there's a set of functions that TADS can call from portable code, and these functions provide the same behavior on all platforms. The implementations of these functions are different on each platform, though. These functions provide a virtual OS by providing system-specific functionality through a standardized, portable interface.

HTML TADS: a replacement virtual OS layer

HTML TADS is designed to look like a virtual OS layer, from TADS's perspective. TADS doesn't know much about HTML; as far as TADS is concerned, it's just generating output the same way it would on DOS or Macintosh or Unix: TADS puts together a buffer with text it wants to display, then calls the virtual OS display-text function.

So, to port HTML TADS to a new platform, you should start with a port of TADS to that platform, then remove a number of the virtual OS function implementations that you would normally use for that platform. You remove them because HTML TADS provides new implementations for them.

For a normal port of TADS, most of the virtual OS functions are in files with names like osdos.c or osmac.c. Some systems have more extensive OS layers than others, so some systems have all of their OS implementation in a single .c file, while others use several files. There's also a file called osgen.c, which provides some OS-specific functions that have a common implementation across a number of systems. Your system's TADS makefile should be helpful in determining which files are used on your platform.

oshtml.c

To determine which of the normal platform OS functions to remove from your build, look at the HTML TADS file called oshtml.c. This file contains the implementation of the HTML TADS replacements for the normal virtual OS functions. Despite its name, oshtml.c is a portable file -- it is the same on all platforms. You don't need to port this file.

Go through the function definitions in oshtml.c; for each one, you need to remove the corresponding implementation in the normal OS layer for your system.

htmlsys.h

Now comes the real work. Just as TADS has a virtual OS layer, HTML TADS has its own virtual OS layer. The functionality of this layer is defined in the HTML TADS file htmlsys.h.

TADS is written in C, but HTML TADS is written in C++. The TADS virtual OS layer used C functions; the HTML TADS virtual OS layer, on the other hand, is defined through a set of C++ classes and methods on those classes. The file htmlsys.h defines the portable interfaces to these classes. These classes have names that start with CHtmlSys: CHtmlSysFont, CHtmlSysWin, and so on.

These classes are not typical C++ classes that define methods and member variables. Instead, these classes are designed as abstract interfaces -- the methods defined in these classes do not have corresponding implementations, but are pure virtual methods that must be overridden and defined in subclasses.

This is where the new system-specific code for your platform needs to be written. For your system, you must define a concrete subclass of each of the CHtmlSysXxx classes. Each of these concrete subclasses must provide implementations for all of the methods in its CHtmlSysXxx abstract base class.

The reason that the CHtmlSysXxx classes are designed as abstract interfaces is so that you can use a third-party class framework to build your version of HTML TADS, if you want. If your system (or compiler) has an application framework, there's probably a class in the framework that corresponds roughly to each of the CHtmlSysXxx interfaces. To use these interfaces with your framework, you can use multiple inheritance.

A word on multiple inheritance: A lot of people dislike multiple inheritance, or have heard that it's a bad thing, or feel that C++'s implementation of multiple inheritance is flawed; but this is a case where it's actually quite useful and reasonably straightforward to use. Since the CHtmlSysXxx classes are abstract, and because they are stand-alone classes without any base classes, they can be "mixed in" to other classes without much chance of an inheritance conflict, and without placing any requirements on the design of the rest of your class hierarchy. In particular, you won't need to use C++'s "virtual" inheritance feature, which is probably where most people's misgivings about C++ multiple inheritance come from.

If you're using a framework, you should find the framework class that corresponds to each of the CHtmlSysXxx interfaces. Then, you should create a subclass of your framework class that inherits from both the framework class and the CHtmlSysXxx interface. For example, suppose that your framework has a class called TWindow that defines the basic window type. (If there's a framework subclass of TWindow that would be more appropriate, such as TScrollingWindow, you should use that as the base class. You don't have to use the bare window class just because CHtmlSysWin defines the interface to a window -- make your framework usage decisions just as though the CHtmlSysXxx interfaces were not involved.) You'd make a TWindow subclass that also inherits from CHtmlSysXxx:

   class CHtmlSysWin_mac: public TWindow, public CHtmlSysWin
   {
    ...
   };

Your framework will probably require that you provide implementations for some methods inherited from TWindow; you should define these as you would for any other application.

In addition, you must provide implementations for all of the functions in CHtmlSysWin. The htmlsys.h header file provides comments that document the functions that these methods are meant to provide.

Here's a summary of the classes in htmlsys.h that you will need to implement.

Note that there also a few classes defined in htmlsys.h that you won't have to implement. Some of these are portable classes that are used simply to parameterize some of the methods of the other classes; they're defined here because their main function is to work with these classes. Others are base classes for more specialized interfaces, so don't need to be implemented directly.

System Object Creation

Since your system code will be providing implementations of these interfaces in subclasses, the portable code has no way of knowing what the final subclasses are called. Thus, the portable code can't ever create a system object directly; instead, your system code creates all of the system objects.

Your system code will create some system objects automatically. For example, it will create the CHtmlSysFrame object when the application starts running (the main application entrypoint is itself always system-specific), and it will create the main HTML window (which provides a CHtmlSysWin interface) during initialization as well.

After startup, the portable code will call methods in existing system objects to create additional system objects. For example, when the formatter needs to create a new banner subwindow, it will call the main HTML window's create_banner_subwin() method; this method, which is implemented by your system code, will create an appropriate final subclass of CHtmlSysWin -- specialized for your system -- and return a pointer to it.

The TADS Win32 Framework

Rather than using an existing class framework to develop the Win32 version of HTML TADS, I developed my own framework. Although I designed and implemented this framework specifically for this project, I designed it to operate as a general-purpose Win32 framework. Note that I could just as well have used one of the commercially available third-party C++ frameworks for Windows (such as MFC, the class library Microsoft includes with Visual C++), but I chose to develop my own framework instead for a number of reasons; one of the main reasons is that I wanted to avoid inadvertantly introducing any framework dependencies in the portable design that might have resulted from developing HTML TADS around an existing framework.

All of the classes with names starting with CTads (such as CTadsWin and CTadsStatusline) belong to the Windows framework.

If you're using an existing framework to port HTML TADS to your system, you won't need to be concerned at all with the CTadsXxx classes. You don't need to port those classes, because you will simply replace them with the corresponding classes from your framework. In fact, you don't even need to replace the CTadsXxx classes exactly; if your framework is laid out differently from the CTads framework, you should follow the organization that you'd normally follow when developing an application with your framework and ignore the CTadsXxx organization. Remember, your job is to provide implementations of the abstract interfaces defined in htmlsys.h -- nothing from the CTads framework is needed by the portable code.

If you're not using an existing class framework, you can use the CTadsXxx classes as a starting point. These classes, though, are completely Win32-specific, so they're filled with Win32 API calls. In addition, these classes are not even designed to be called from portable code, so the interfaces these classes expose are themselves closely coupled to the Win32 API; for example, many of the member variables and method parameters use Win32 system datatypes.

html_os.h

The normal TADS OS layer has some additional definitions in a header file, os.h. This file has some configuration information needed by the portable code, such as the native C types to use for certain abstract TADS types.

HTML TADS has a corresponding header file, html_os.h. This file is in the portable directory, but provides platform-specific definitions. You need to edit this file to provide a set of definitions for your platform. You should add a section, enclosed in an appropriate #ifdef for your system, that includes a system-specific file that you create. For Win32, this included file is called hos_w32.h; you should create a file appropriate for your system.

You should refer to the hos_w32.h to find the set of macros and other definitions that you need to include in your platform-specific header file. Fortunately, this file is considerably simpler than os.h from TADS; one reason is that HTML TADS uses some of the TADS OS layer directly, reducing the amount of new OS layer that needs to be built, and another reason is that C++ is somewhat more standardized than C was in the days when I started TADS.

Note that html_os.h acts only as a "switchboard" for including the appropriate platform-specific file. Please observe this convention, since it will keep the code (both in html_os.h and in your own system-specific files) easier to read by keeping each platform's code in its own set of files, rather than concatenated together into a huge impenetrable set of #ifdef's. For the Win32 definitions, look in hos_w32.h and hos_w32.cpp in the win32 subdirectory.

os_get_sysinfo()

Your system code must define this function:

    int os_get_sysinfo(int code, void *param, long *result);
Refer to the base TADS header file osifc.h for a full description of this function. This is one of the few TADS OS-layer functions that you must define in your system-specific code. The reason this function is part of your system-specific code rather than part of the portable HTML TADS definitions (as are most of the other TADS OS-layer functions) is that this function returns the specific capabilities of your version of HTML TADS. Since each port of HTML TADS may vary in capabilities, HTML TADS cannot at the portable level know which capabilities are implemented for each platform. For example, some ports of HTML TADS may support MIDI files but not WAV files. This function allows the system-specific code to provide the correct information.

os_dbg_sys_msg()

Your system code must define this function:

    void os_dbg_sys_msg(const textchar_t *msg);

This routine displays internal debugging messages on the system console. You can provide an empty implementation for this function if you wish; its only function is to help you debug HTML TADS by providing a place for the system to display internal diagnostic information. When you compile the system without TADSHTML_DEBUG defined, this function is not used at all.

Main entrypoint

Once you've provided implementations for the CHtmlSysXxx interfaces, you're nearly done. The only thing left is that your system code has control over starting up the application.

The main entrypoint is system-specific, because each GUI system has its own way of invoking an application and passing start-up paramters to it. There's no CHtmlSysXxx or other portable interface defined for the main entrypoint; this is a totally system-specific function, so you must provide a main entrypoint as appropriate for your operating system.

Your main entrypoint will undoubtedly have to do some system-specific work to get the application initialized; you should follow the normal application initialization protocol for your system. Among other things, you'll want to parse the command line or read the start-up parameters, or whatever the equivalent is on your system.

In addition, your main entrypoint is required to do a few specific things to get HTML TADS started. You can refer to the implementation of run_game() in the Windows code (in htmlw32.cpp) for an example of the start-up code. Here's an outline of the steps you need to perform:

Some assumptions about GUI OS architecture

The portable HTML TADS code makes some assumptions about the architecture of GUI operating systems. These are true of most of the GUI systems I've encountered, but it's worth spelling these out in case (a) you're porting to a non-GUI system, or (b) you're porting to an unusual GUI system that diverges from the typical patterns found in Windows, Macintosh, X, etc.

Event orientation: One of the main assumptions we make is that we're running on an event-driven system. That is, the operating system maintains an "event" or "message" queue that delivers input to the application. The application's top-level code path is a loop that reads an event/message from the queue, carries out appropriate processing to respond to the event, and loops. The OS generates events to represent user input-device actions (keystrokes, mouse movement, mouse clicks, etc) and higher-level GUI changes (window resizing and moving, redraw needed, etc).

Event models vary considerably by operating system, so the HTML TADS portable code of necessity leaves it up to the port code to read and handle events. Instead, the portable code provides handlers that you call in the course of processing events. So, you have to provide the "glue" that interacts with the OS-level event system to read and decode events, but in many cases you can invoke a portable routine to carry out the processing necessary to respond to a given event.

There are two main styles of event model:

(It's worth pointing out that Windows is a hybrid of the two models. On Windows, the application is responsible for reading events, as in the message loop model, but the application must also register callbacks with the operating system, and the OS can dispatch directly to the callbacks in the course of carrying out arbitrary API calls.)

Invalidation drawing model: We assume that drawing is driven by the OS through the event model. In particular:

On most GUI systems, the redrawing model is tightly integrated with the native event system, so HTML TADS doesn't have much choice but to leave this up to the port code. In most cases, the port code in turn simply leaves it up to the OS. If your OS doesn't have a native invalidation/redraw event model, you'll have to provide one of your own. The simplest way to do this would be to explicitly redraw the entire contents of each window every time an "input" call of some kind is performed; you'd do the drawing at the start of the call, before pausing to read input from the user. "Input" calls are times when the application will pause for user interaction, so it's a natural time to make sure everything's up to date on the display.

In practical terms, you can usually implement "redraw" event handling by doing any necessary OS-level setup, then calling formatter_-<draw() from your CHtmlSysWin subclass. The formatter walks through its display list and calls back to your OS code to do the actual drawing.

Window management: HTML TADS assumes that someone will be taking care of managing the window system - things like providing a UI for the user to resize and move windows, etc. We assume that this "someone" will be doing all of the necessary invalidations as part of this window management. In almost every case, it's the OS that handles these things, but some older GUIs (early PalmOS versions, for example) are rather limited and foist this stuff off on the application, in which case you'll be responsible for handling this in your porting code. But on any modern GUI, this stuff should come for free.