← Let's make noiseCharsets →
  Preprocessor abuses
Sat 15th August 2015   
Here comes the first article about tips and tricks that can be used to make your Oric development easier and faster.

Choosing an assembler

Originally the Oric C development kit was using FRASM1 to assemble the 6502 code generated by the C compiler front-end.

One of the first changes we did when starting the OSDK was to use instead a real 6502 macro-assembler with an extended set of features, and ultimately we choose XA because it supported most of the C compiler preprocessor directives.

The C Pre-processor

Modern development practices tend to dismiss preprocessor features as being horrible hacks that should not be used by any self-respecting developer, but in practice it happens that you can't even do modern portable C++11 development without a large dose of #ifdef to abstract platform and compiler specific kludges, so take all this with a grain of salt: The preprocessor is here to stay.

You can refer to the XA documentation page for more details, but basically all you need to know is that the following commands will work just as well in the C and 6502 code you are writing using the OSDK:
  • #include "filename"
  • #define NAME text
  • #if/#ifdef/#ifndef/#else/#endif
  • #file "filename"
There are other non standard features, but this subset is sufficient for what we want to achieve: Sharing information between our C and assembler modules.

Software Configuration

When developing a large program, it's often practical to be able to easily enable or disable entire sections of the code so you can concentrate on what you are working on at a particular moment.

Typically if you write a game, you may want to skip the intro sequence and instead force the loading of a precedent save game, or disable all the small animations between the sequences.

An ideal way to achieve that is to use conditional compilation.

The way I do that in my own code is to add a file shared by both my C and assembler modules, containing the list of all options, one such example would be the defines.h in the Oric Tech demo:

#ifndef _DEFINES_INCLUDED_ALREADY_H_
#define _DEFINES_INCLUDED_ALREADY_H_

//
// Comment out these defines to enable or disable the various features of the engine
//
#define ENABLE_SOUND

#define ENABLE_VIP_INTRO // Comment out to disable the entire intro
#define ENABLE_TECH_TECH // Comment out to disable the tech tech part

#define ENABLE_MUSIC
//#define FAST_INTRO

#define CHARMAP_WIDTH 256
#define CHARMAP_HEIGHT 21
#define CHARMAP_SIZE CHARMAP_WIDTH*CHARMAP_HEIGHT

#define BIGFONT_WIDTH 193
#define BIGFONT_HEIGHT 5
#define BIGFONT_SIZE BIGFONT_WIDTH*BIGFONT_HEIGHT

#define INVERSE_WIDTH 36
#define INVERSE_HEIGHT 28
#define INVERSE_SIZE INVERSE_WIDTH*INVERSE_HEIGHT


#endif // _DEFINES_INCLUDED_ALREADY_H_

The code that includes the file can then just contain whole sections of #ifdef ENABLE_xxxx and they will automatically get compiled or not depending if the defines are commented out or not.

APIs

You can also use a similar method to develop API2s for your reusable code.

The issue there is that even if the preprocessor directives are identical, the C and Assembler code on the other hand are vastly different.

Let say you have created a small library to easily draw sprites on the screen, and in particular you want to provide a function to set the screen coordinates of any sprite:

SetSpritePosition, three parameters: Sprite number, X position, Y position

How could you possibly make it usable easily from both C and assembler?

The natural way to express that in C would be with a function call, such as:

void SetSpritePosition(int number,int x,int y);

In assembler on the other hand we try to use registers as much as possible, so we probably would get something like that:

; a: sprite index
; x: x position
; y: y position
_SetSpritePosition

Since most of the reusable libraries are written in Assembler on the Oric, we will assume that the sprite library was written in Assembler, and that the C API will be designed to call assembler code.

One way to abstract the calls would be to use macros instead of function calls:

// SetSpritePosition, assembler version
#define SetSpritePosition(sprite_index,x_position,y_position) lda #sprite_index:ldx #x_position:
ldy #y_position:jsr _SetSpritePosition (same line)

and something similar would work in C as well:

// SetSpritePosition, C version
#define SetSpritePosition(sprite_index,x_position,y_position) \
tempA=sprite_index;\
tempX=x_position;\
tempY=y_position;\
SetSpritePositionFromTempVariables();

The question is: How do we force the C compiler to use the second definition and the Assembler to use the first one?

Detecting the language

The answer is simple: When you build a project with the OSDK, there is a bunch of defines available for you:
  • ASSEMBLER is defined when XA is called, so should be available in any assembler module
  • OSDKNAME_%OSDKNAME% contains the name of the program being compiled so you can share some files between multiple executables3
What that means is that you can do the following:

#ifdef ASSEMBLER
// SetSpritePosition, assembler version
#define SetSpritePosition(sprite_index,x_position,y_position) lda #sprite_index:ldx #x_position:
ldy #y_position:jsr _SetSpritePosition (same line)
#else
// SetSpritePosition, C version
#define SetSpritePosition(sprite_index,x_position,y_position) \
tempA=sprite_index;\
tempX=x_position;\
tempY=y_position;\
SetSpritePositionFromTempVariables();
#endif

And that's how you can have both C and Assembler in the same file and abstract complicated things quite easily.

This is also what the FloppyBuilder is using when it generates the floppy layout file: The list of defines with the file entries are common to the C and Assembler parts, while the actual arrays and metadata elements are only exposed to the LOADER's assembler API.

Hope this will help you :)


1. Frankenstein Assembler
2. Application Programming Interface
3. If your OSDKNAME environment variable contains 'MyTestProgram' then there will be a OSDKNAME_MyTestProgram variable available allowing you to do #ifdef OSDKNAME_MyTestProgram
comments powered by Disqus