Chapter 6. Application Framework

Table of Contents

Overview
Program Flow
xfcAppPref()
xfcAppInit()
The Uses-Interface
Application Class
Renderer Class
Controller Input Class

Overview

This chapter describes the core application framework of the X-Forge engine. The application flow is designed to work, without any changes, on multiple platforms, some of which are more restrictive than others. The second important design goal was to keep the application complexity as low as possible and to shield the application developers from most of the complexity that happens behind the scenes on some platforms.

Some of the complexity cannot be hidden, such as the lack of explicit "show this on screen" commands, and the fact that the "main loop" is not inside the application. Additionally, the application must not use any global data in order to be portable to, for example, Symbian platforms.

Warning

While global data does work on other platforms, it is recommended that no global data is used in order to make porting to Symbian as easy as possible. Removing global data afterwards is more difficult than designing the whole application not to use it in the first place.

Program Flow

Every X-Forge application contains two global functions, xfcAppPref() and xfcAppInit().

In addition to these, a typical application extends the three callback interfaces XFcApp, XFcRenderer and XFcInput. The input interface contains callbacks for button and pointer events. Renderer interface calls the application whenever rendering should be performed, and finally the App interface contains application initialization, deinitialization and device state callbacks. The application class is also important in that the program can request the address for the application object globally (being the only thing that the application can access globally).

xfcAppPref()

The first call the application gets is the xfcAppPref() global function.

void xfcAppPref(XFcAppPrefs &xfcAppPrefs)

This function may only alter the contents of the xfcAppPrefs object. No memory management has been initialized at this point, so file access or any other memory-allocating action will cause a crash.

The XFcAppPrefs class contains, at the moment, the following members:

INT32 mTotalMemoryLimit;
INT32 mResourceMemoryLimit;
INT32 mPreferredWidth;
INT32 mPreferredHeight;
const CHAR * mTitleString;
INT32 mUID;
INT32 mMinimumExtraMemory;
const CHAR * mOutOfMemoryErrorString;

The total memory limit is the amount of memory the application requires to function. After the xfcAppPref() call, the memory pool is initialized. If there is not enough memory for the memory pool, the user is informed of this with a dialog and the application will exit.

The resource memory limit is the maximum amount of memory that resources may take from memory. For example all textures are resources. Whenever the application is allocating memory, and not enough memory is found, the resource manager automatically frees up the least recently used resources, until enough memory was found, or it becomes clear that the application has run out of memory. Whenever a resource is needed, it is recreated if it is not in memory.

The preferred width and height only affect the desktop Windows version, and can be used to develop software for screens that are not 240x320.

The title string is used on platforms with a title bar, and will replace the default "X-Forge/Platform" title.

The mUID value is required on Symbian platform. This is the application's unique id.

The mMinimumExtraMemory tracks how much extra memory should be left unallocated on the system after the memory pool has been allocated. This is due to the fact that at least on some Symbian platforms, if the operating system (or some other applications on the device) can't get enough memory, random applications will start crashing. If core cannot detect this much unallocated system memory after the pool has been allocated, the application will exit with an 'out of memory' error. It's recommended that this value is set to at least 128KB on Symbian platforms.

With the mOutOfMemoryErrorString pointer, the text in the out of memory dialog can be customized. Leaving the string to NULL will give a rather technical error message which might not be desirable in a finished product.

After the xfcAppPref() call, the memory pool and other core mechanisms are initialized.

Note

The XFcAppPref() may be called several times on application startup.

xfcAppInit()

After the core has initialized, the application's xfcAppInit() function is called.

INT xfcAppInit() 

This function should, at least, use the 'uses' interface and initialize the application class object. If it can detect that something is wrong, it should return a non-zero value, in which case the core deinitializes and quits the application. At this point the memory pool has been initialized, so the application can try to do anything. Nothing can be displayed on the screen, however, since the rendering loop has not started yet.

After the xfcAppInit() call, the application class onAppInit() is called, after which the rendering loop starts.

The Uses-Interface

In order to minimize the application size at link-time, the application can decide not to use certain parts of the X-Forge engine. The current set of use-functions is as follows:

xfcUseDefaults();
xfcUseCFLFilterZlib();
xfcUseGLDefault();
xfcUseImageFilterPCX();
xfcUseImageFilterTGA();
xfcUseGLDefaultFSAA();
xfcUseGLDefaultUpsample();
xfcUseGLZBuffer();
xfcUseNetwork(INT aCommHandlerArraySize);

You can find these function prototypes in XFcUse.h header file.

The xfcUseDefaults() includes CFLFilterZlib, GLDefault, ImageFilterPCX and ImageFilterTGA. The DefaultFSAA, DefaultUpsample and UseGLZbuffer rasterizers extend the default rasterizer.

The FSAA rasterizer tells the default rasterizer that the screen is actually twice as wide and twice as high as it really is, and then does 2x2 downsampling, averaging these four pixels. Being a very slow device, it is meant only for rather static or very low-framerate use.

The Upsample rasterizer is the reverse of the FSAA rasterizer, and speeds up the application if and only if most of the time is spent in the actual rasterization.

Another use for the Upsample rasterizer is to make double-pixel desktop versions for presentation purposes. Just double the preferred resolution in xfcAppPrefs, initialize the Upsample rasterizer instead of the default one, and you're done. (Also remember that the upsample rasterizer eats somewhat more memory, so you may need to increase the memory pool size).

The zbuffer rasterizer is still under construction, and is not meant to be used at this time.

Note

Please note that the linker drops things out based on reference, not execution path. So if you have xfcUseGLDefault() somewhere in the code, even inside an if clause, the default rasterizer is always included in your application.

xfcGLUse

New since X-Forge 1.1, you can trim down the executable size by only enabling the fillers you actually need. The 40 or so fillers in X-Forge fill approximately 100k of your executable size, so it makes sense to only enable the ones that you're actually using.

Since the Default rasterizer automatically links with all of the fillers, you should not call xfcUseGLDefault() or xfcUseDefaults() in your application. (Note that the Upsample rasterizer also uses the default rasterizer and initializes all of the fillers).

After creating or recreating the GL object, you must call the xfcGLUseXXX() functions to activate the fillers you need. Any fillers not activated will point at a stub rasterizer, so if some of your polygons are not being rendered, you most probably haven't activated the correct filler.

Here's a partial list of the xfcGLUseXXX() functions; full list can be found in the XFcUse.h header file.

void xfcGLUseDefaults();
void xfcGLUseFillerFlat();
void xfcGLUseFillerFlatAdd();
void xfcGLUseFillerFlatAlpha();
void xfcGLUseFillerFlatInvmul();
void xfcGLUseFillerGouraud();
void xfcGLUseFillerGouraudAdd();
void xfcGLUseFillerGouraudAlpha();
void xfcGLUseFillerGouraudInvmul();
void xfcGLUseFillerTexture();
void xfcGLUseFillerTexture1555();

The xfcGLUseDefaults() function initializes all of the fillers. Most of the functions are self-descriptive; the 1555 methods are those that read the 1555 ARGB texture format.

Application Class

The application class has two important functions. First, it serves as a callback host for application initialization and deinitialization, device state and tick calls. Second, it is the only object that can be accessed from anywhere in the system, so it is the prime candidate for any data that has to be global.

Only one instance of application class can exist at any time. The XFcApp constructor registers the class with the core, and the core makes sure the application object gets deleted.

The initialization / deinitialization callbacks are named as follows:

void onAppInit();
void onAppDeinit();

The application initialization function is called after the xfcAppInit() call. The deinitialization gets called before the core deinitializes.

Note

While the core takes care of most deinitializations automatically (such as freeing the memory pool), it is still a very healthy habit of having zero memory leaks, no open files, no open audio streams, etc. when the application quits. The onAppDeinit() is a handy place to keep any global cleanup code.

Also note that core doesn't automatically kill off any extra threads you may have launched, so be sure to clean them off here.

Sometimes the device's state changes in a way that affects the application. These state changes include loss of focus, loss of audio, incoming phone calls, etc. These state changes are reported via the following callback:

INT onDeviceStateEvent(INT32 aEvent, INT32 aSeverity, void *aMoreInfo);

The aEvent parameter describes the event. Currently listed events include:

XFCDSE_FOCUSLOST    Application has lost focus.
XFCDSE_FOCUSGAIN    Application has (re)gained focus.
XFCDSE_AUDIOLOST    Application has lost audio. 
XFCDSE_KILLSIGNAL   Operating system tells program to terminate.
XFCDSE_MEDIACHANGED Removable media has been removed or inserted.
XFCDSE_LOWMEMORY    Operating system is calling for more memory. 
XFCDSE_PHONEEVENT   Some phone event (incoming call, SMS, etc) has occured.

Note

Before X-Forge 1.1, most of these events were reported via separate callbacks, including onLostFocus() and onRegainFocus(). These callbacks are now deprecated and users should move to the new system.

The aSeverity parameter describes the severity of this event. There are three severity levels:

XFCDSE_SEVERITY_NOTE     Application may choose to ignore this event.
XFCDSE_SEVERITY_MODERATE Application should go to a pause mode, and shut down audio.
XFCDSE_SEVERITY_CRITICAL Application should shut down its threads and terminate immediately.

Events such as 'focusgain' and 'lowmemory' are categorized as 'note' events, and can in most cases be ignored. 'Focus lost' and 'audio lost' are moderate events, and should cause some action, but the application can still continue. 'Kill signal' is an example of a critical event, and the application should make sure all of its threads are shut down and quit as soon as possible.

The aMoreInfo pointer is platform- and event-specific, and is reserved for future use.

Note

Although we list 'phone event' as one event, we unfortunately cannot detect this on (most) current phone systems. Thus, loss of focus may be the only notification you get when the device receives a phone call. Thus it is critical to pause all audio and go to a pause mode whenever this happens.

The application will still lose focus even if we can detect a phone event.

The application may lose focus for many reasons on a PDA or a smartphone. It may be that some calendar alert pops up, or you get a phone call. You should at the least pause the game whenever this happens. Stopping audio is especially critical. Automatically saving the game at this point is also recommended. If you can restore your application state completely from disk, it's best if you save the state and exit completely. If you decide to go into a pause mode, you should pause all audio and call the XFcCore method setExclusiveMode() to set non-exclusive mode, and then call sleep() (also in XFcCore) a lot. This behavior will ensure that the phone user can call and receive calls while your application is running.

Sometimes there is a situation where the system decides to ask for the application to terminate. The default action is to simply exit the application. You can override this behavior; you can even decide not to exit at all. Do note, however, that the terminate request is most likely critical. You can simulate this by pressing the little 'x' on top right corner of the window in the Desktop Windows platform.

Sometimes the audio system is lost. Re-initializing the audio system right away doesn't neccessarily help, if the cause of the audio system loss is still active. The application may try to re-initialize audio later.

Note

Some lostfocus/regainfocus call pairs are very brief, such as the PocketPC cradle handshake window popup. Should you implement a total application shutdown, it is best to wait in idle mode for a second or so before deciding whether to exit.

Finally, the application gets called on every rendering loop.

void onTick();

This call happens just before the renderer class render call. While the renderer class can change, there may only be one application class, and thus the onTick function is a good candidate for application-wide events.

A pointer to the application object can be acquired from anywhere in the application by calling the XFcCore static member function getApp(). In practice you will have to cast the pointer to your actual application class pointer in order to access your global data.

void foo::bar()
{
    MyApp * myApp = (MyApp *)XFcCore::getApp();
    myApp->globalFunction();
}

Renderer Class

The XFcRenderer class is the interface for renderer callbacks. Its interface is rather simple:

void render();
void initRenderer();
void deinitRenderer();
INT updateScreen(void *aMoreInfo);

Render is called whenever the rendering loop reaches the point where something should be rendered on screen. In practice the rendering will be done through an XFcGL object.

The init and deinit calls are done whenever a new renderer is set via the static XFcCore function setRenderer().

Warning

All rendering is done via a double-buffered interface. On some devices the buffers are actually on video memory, and thus forgetting to render the whole screen in subsequent frames may cause flickering.

The reason why the renderer and controller objects are separate from the application object is that you can have separate controller and renderer objects for different states of your application. One for menus, one for game, etc.

The updateScreen() call exists so that the application can override X-Forge's normal screen update functionality. Most of the time the application should not need to do this, but on some platforms the application may wish to perform some hardware-accelerated operations, such as color-keyed blit from video memory.

Controller Input Class

The XFcInput class is the interface for controller callbacks.

void onPointerDown(INT32 aX, INT32 aY);
void onPointerMove(INT32 aX, INT32 aY);
void onPointerUp(INT32 aX, INT32 aY);
void onKeyDown(INT32 aCode);
void onKeyUp(INT32 aCode);
void onControlDown(INT32 aCode);
void onControlUp(INT32 aCode); 
void onCharEntered(CHAR aChar);
void onJoystickMoved(INT32 aX, INT32 aY);

Pointer down, move and up calls are made whenever a pointer (mouse on desktop, stylus on a PDA) does something. For the mouse, move calls are only called if the button is pressed. There is no 'mouse hover' functionality as you cannot 'hover' with a stylus.

Warning

The pointer resolution and update frequency are very platform-specific.

onKeyDown() and onKeyUp() calls are made whenever a button is pressed or released. These calls report the actual scancode the device reports. On desktop, all keyboard keys are buttons. On most phones, the numeric keypad also works.

onControlDown() and onControlUp() calls are made based on a translation table in the core. The codes are enumerated:

XFCCL_LEFT
XFCCL_RIGHT
XFCCL_UP
XFCCL_DOWN
XFCCL_FIRE1
XFCCL_FIRE2
..
XFCCL_FIRE8
XFCCL_WHEELDOWN
XFCCL_WHEELUP

These codes are, by default, mapped to a 'sensible' configuration. Fire1 is meant to be the "most accessible" fire, and Fire8 is meant to be the "least accessible" fire. Naturally, most devices don't even have 8 separate fire buttons; Microsoft GAPI (Game API for PocketPC) only supports 4.

The wheeldown/wheelup controls are for devices which have a wheel control. When the wheel is used, call for controldown and controlup are done immediately, as the wheel does not have a 'depressed' state. The only device with such control so far is the Ericsson P-800. (On Desktop Windows, the mouse wheel also works).

The onCharEntered() method gets called whenever the operating system reports that a character has been entered. While the keys on the keyboard have scancodes that match their corresponding keys, this is not a rule; pressing 'left' arrow key, for instance, has a scancode that is still printable. In order to create input fields, you should use this callback for text input on devices that can support it, and have virtual keyboards for other platforms.

onJoystickMoved() is reserved for analog joystick input.

Mapping controls

The controls can be remapped using the following static XFcCore functions:

void mapControlCode(INT32 aControlCode, INT32 aHardwareScanCode);
void unmapControlCode(INT32 aControlCode, INT32 aHardwareScanCode);
UINT32 getControlMappingCount();
void getControlMapping(UINT32 aControlIndex, INT32 &aControlCode, INT32 &aHardwareScanCode);
void resetControlMappings();

You can map a scan code to a control code using mapControlCode(). Please note that you can map a single scan code to several control codes, and vice versa. You can unlink control code and scan code using the unmapControlCode() method. In order to iterate the controls, you can get the control map count using the getControlMappingCount() method, and you can get the actual map using the getControlMapping() method, looping aControlIndex from 0 to whatever getControlMappingCount() retuned, minus one. Finally, you can reset the control mappings to default by using the resetControlMappings() method.

Note

Please note that this API is different from pre-1.1 X-Forge. Earlier version only supported one-to-one mappings, which was found to be too limiting. In this system you can map both the numeric keypad and the joystick on a phone to directional keys.

The 'hardware' in aHardwareScanCode was also added for clarification.

Screen Buttons

On devices with a touch screen you can create virtual screen buttons using the following static XFcCore methods:

void addScreenButton(XFcRectangle &aRect, INT32 aScanCode);
void resetScreenButtons();

The addScreenButton adds a single screen button. Whenever stylus (or, in most cases, the player's thumb) enters or leaves this rectangle, onKeyDown and onKeyUp methods are called with the scancode with which the screen button was created. These scancodes can be mapped to control codes just as if the scancode came from a physical button.

The resetScreenButtons() method removes all screen buttons.

Please note that screen buttons are not widgets; they do not have any visual representation, nor do they act like a button widget would.

Note

Due to their nature, only one screen button can be pressed at the same time. Result of overlapping screen buttons is undefined.