Re: [stella] Object handler organisation

Subject: Re: [stella] Object handler organisation
From: Christopher Tumber <christophertumber@xxxxxxxxxx>
Date: Thu, 07 Nov 2002 12:16:09 -0500
>Well, since I slowly started loosing control over all my 
>objects behaviour, I started reorganizing it in some OOP 
>like manner. I've now one giant loop, calling the 
>required subroutines for every object. It looks like 
>this:

Ah, design philosophy, just the thing to get me back into writing huge posts...

Okay, first the most obvious thing is that several of the routines you're calling are probably not object specific.
That is, the routine which handles a Ship's movement is different from the routine which handles a Meteor's movement
is going to be different, however, most of the other routines are going to be the same (GeneralMovement,JoystickMovement,
CollisionDetection,ZMovement).

So you don't need to include these in the branch, rather you can execute them always:


    LDX #MAXOBJECTS-1

MoveNextSprite
    JSR GeneralMovement     ; General Movement
    JSR JoystickMovement    ; Move according to Joystick
    JSR CollisionDetection  ; Handle Collision with shot
    JSR ZMovement           ; Move along the z axxis

    LDA sprtsizetype,X
    CMP #$28
    BMI NoShipSprite
    JSR HandleShip          ; Special ship handling
    JMP Spritedone          ; Done

NoShipSprite
    CMP #$20
    BMI NoMeteorSprite
    JSR HandleMeteor        ; Special meteor handling
    JMP Spritedone          ; Done


Etc...

Now, for objects where you don't want to call some of the routines, just add a test at the begining of the appropriate "general" routine or before calling the routine (though that's reintroducing some of the overhead, but hopefully not as much).

And then remove GeneralMovement, JoystickMovement, CollisionDetection, ZMovement as subroutines and code them right into the main routine and eliminate the jsr/rts overhead.

You can also remove JSR HandleShip (et al) as subroutines and hard code them right in, again eliminating a bunch of jsr/rts overhead.

Space Instigators uses very few subroutines - Drawing routines which go into RAM (no choice), the routine which sets-up the explosion sound (because it's called from several places), the routine which does collision detection on houses (ditto), score updating routines (ditto). That's about it. Big Dig has no subroutines at all and no stack.

My general feeling is that you only want to use subroutines if it saves ROM space by re-using code (and even then, if you're doing a 32k game that might not be cost effective). Otherwise, the overhead from jsr/rts (particularly in loops that get executed a bunch of times) can become really severe (as you're seeing).. I realise this runs completely contrary to accepted coding practice, however, accepted coding practice generally assumes a modern PC with a boatload of RAM and more clock cycles than you're ever likely to need. Wherever possible, I think you want to avoid subroutines in the main game code (obviously title screens and such are a whole other matter where you're probably not trying to milk out every last cycle possible...)


Amd I'm going to suggest something even more radical - Dump OOP altogether. OOP is usefull in environments with plenty of resources, where you're going to be sharing code with a team, where code is going to need to be modified and updated later. But none of those conditions really apply to older consoles. So you get all the costs of OOP (overhead) but few of the benefits.

For example, let's say you've got a total of 20 possible objects on screen. The way you're doing it now is very flexible, with a general data structure you can ship between as many Meteors or Ships as you want. But do you really need that flexibility?

If there are always 10 meteors and always 5 ships (until they get killed, obviously) then you do not need a general purpose structure.

Instead, consider going with a Meteor structure, a Ship structure, a Laser structure, etc.

This will eliminate the whole "CMP #$20/BMI NoMeteorSprite" testing, it also may free up a BUNCH of RAM because your status data is simplified - You just need a 1 bit alive/dead flag instead of a "What am I?" byte/nybble. (Or maybe a 2 bit Alive/Dead/Exploding flag)

If you keep the structures similar, you can still re-use most of the same "routines" (GeneralMovement, JoystickMovement, CollisionDetection, ZMovement)


Now, this is less flexible than your general purpose structure, but you can add flexibility by realigning the break-down on a per-level basis. ie: If level 1 has 10 Meteors and 5 ships, and level 2 has 12 meteors and 3 ships you can still do this, just abstract further into a table (Or, if you have RAM to spare, use variables to keep track of the number of Meteors/Ship/etc per level (either randomise or load from a table but will be faster re-referencing a variable)) an allocate space dynamically.


For example:


     ldx level               ;Figure out how many Metors on this level
     lda NumberOfMeteors,x   ;Could be done more efficiently (ie: Use Y as a counter instead of RAM)
     sta temp                ;But I'm assuming you're going to want to use Y

     ldx #$80                ;Start of Meteor data structure
NextMeteor:
     <handle meteor stuff>
     inx
     inx
     inx                     ;Move to the next Meteor (however big the structure is, assuming 3 bytes here)
     dec temp 
     bne NextMeteor          ;Check if there are more Meteors

     ldx level               ;Figure out how many ships on this level
     lda NumberOfShips,x     
     sta temp                

     lda NumberOfMeteors,x   ;Find offset for Ship Structure
     sta temp2
     asl
     clc
     adc temp2               ;A=Ax3
     adc #$80                ;Offset from start of Meteors structure     
     
NextShip:
     <handle ship stuff>
     inx
     inx
     inx                     ;Move to the next Meteor (however big the structure is)
     dec temp 
     bne NextShip            ;Check if there are more Meteors

These routines would only handle jobs specific to a certain object type, general object manipulation would be applied to everything.

It does look like there quite a bit of overhead here, however, all the overhead (setting up offsets and such) is done outside loops so you don't get the kind of compounded overhead from doing CMP/BMI or JSR/RTS 20 or 30 times. If you only have a couple objects then your general purpose data structure is more efficient but if you have a lot of objects then OOP becomes quite costly.

And you can always unroll the loops here if you have ROM space to spare.

I've found from Space Instigators that you're often trading off between RAM space/Rom Space and Speed and trying to strike a balance between those three. For example, if you have a bit flag of some sort, it's faster and uses less ROM if you treat it as a whole byte. However, since RAM is usually at a premium it's often better to consolidate several bit flags onto one byte. Which means you're having to mask out the relevant bit which leads to slower and longer code because you're doing ANDs and ORs you wouldn't otherwise have to bother with. Same idea, really, an OOP design is a little more flexible and certainly more readable but it's almost certainly going to be slower and most likely take up more RAM/ROM space...


I realise you may not want to redesign your entire data structure to suit my whim, but, I really think you should consider pulling out your much less used data types (Mothership, Lasers and Shots) and handle them complete seperately. It might help a lot (ie: It's really a waste to be checking every object, every frame, to see if it's a mothership). So if you kept Meteors/Ships/Explosions (since you can just switch a flag to indicate an explosion and not have to worry about duping the Horiz/Vert positions to another explosion structure) as one structure with 10/20/whatever objects and then had a second more general purpose structure for everything else (Lasers, Motherships, Shots) that would be far fewer objects (5?) and would be more eficient.

Chris...

----------------------------------------------------------------------------------------------
Archives (includes files) at http://www.biglist.com/lists/stella/archives/
Unsub & more at http://www.biglist.com/lists/stella/


Current Thread