Indirect calls with the Z80

Page 4/4
1 | 2 | 3 |

By DarkSchneider

Paladin (932)

DarkSchneider's picture

10-04-2018, 16:18

What we do is store the virtual functions as number and then use a general method for calling from a global virtual functions table. The global functions table could be public depending of implementation (not in our case because only the engine have access, and this iterates the objects arrays to call them), not private for each object, but who cares. In any case they must have (the function themselves) global visibility if we want the object to call them.
This way you can store any number of virtual functions easily on an array field of the object. Also updating object virtual functions are more friendly and flexible because you only need to know the number (that you bookkeep in your design).

Example: you have an object with a defined GameObjectOnHit. If you want to change it (the update want to do it for any reason, flexibility), if it is a number simply change the number.

This allows to read and store the object behavior externally generated from tools, as this swap can be stored as object data that the associated script to the object will make the swap in its behavior.

When names or symbols (addresses) are involved, can be more complicated.


Champion (293)

MOA's picture

11-04-2018, 01:41

@DarkSchneider: you have some code fragments to look at? Actual code tells me more than a thousand words, hehe.

By DarkSchneider

Paladin (932)

DarkSchneider's picture

11-04-2018, 09:29

Is made in C (BYTE is unsigned 8-bit):

typedef struct {
BYTE iaupd, iacoll, iacmap; // functions index for update, collision and collision with map
BYTE datupd[IADATA_SIZE], datcol[IADATA_SIZE]; // object data used by IA functions
} GameObject;

At engine:

#define IAFUNC_SIZE 256
VOID (*iafunc[IAFUNC_SIZE])(); //later each one get its own parameters

At engine initialization we set the functions, it is fixed but by this way you can easily change the order if wanted:

VOID engini()
	iafunc[0] = ianull;
	iafunc[1] = iamvln;
	iafunc[2] = iaclba;
	iafunc[3] = iacldg;
	iafunc[4] = iadead;
	iafunc[5] = ialfzr;
	iafunc[6] = iaplyr;
	iafunc[7] = iapmcl;

Then the engine at some calls (like update) iterates over the GameObjects array, calling (here in the update, notice the iaupd number we use and the data we pass):
(*iafunc[((GameObject*)obj1)->iaupd])(obj1, ((GameObject*)obj1)->datupd)

The update functions are like this prototype (i.e. the linear movement one):
VOID iamvln(GameObject *this, BYTE *iadata)

For collisions, are like:
(*iafunc[obj1->iacoll])(obj1, obj1->datcol, obj2)
(*iafunc[obj2->iacoll])(obj2, obj2->datcol, obj1)
VOID iaclba(GameObject *this, BYTE *iadata, GameObject *against)

Now I see that we could get iadata from "this", not sure why we decided to pass too, I'll revise it.

The C generated code uses the RET method, so it is fine. If it was not a good generated code, simply put your own one made in ASM for "virtual calling".

The symbols are 6-length because required by M80 that is the one used by MSX-C T_T

Look the flexibility, that i.e. a linear movement object, can easily, under some conditions, change to a "homming against player" one simply changing a number.

Page 4/4
1 | 2 | 3 |