Beyond Brown

When brown just isn't enough

Data structures using offsets

Or “How I Learned to Stop Worrying and Love My Tools”

v1.0

The problem

Keeping tracks of assets in assembler can be really painful, and if you’ve used constructs such as objects, classes or even just structs in high-level languages, you can really get to miss them.

“Sure”, we think. “there’s probably some shit solution thought up by someone who thinks resources are unlimited, but that’s not the world of the 16-bit game coder, and certainly not for the demo coder!”

Example

We have a little game, and we have a number of sprites, and we’ve identified a number of different properties each of our sprites have, now we just have to reserve the memory for it, and think up a way our code can access it.

The code below might seem a reasonable approach:

Code

Note: All code in this document is intended for rmac, the assembler of choice for the distinguished gentleman versed in theology and geometry. For Devpac/vasm, see comments.

.68000  ; Devpac: "section text"

draw_sprite:
; In:  d0.w  - Sprite number
;      d1.w  - X position
;      d2.w  - Y position
    ; Determine which sprite
    cmp.w #0,d0
    bne .not_sprite0
    lea sprite0,a0
    bra .sprite_decided
    .not_sprite0
    cmp.w #1,d0
    bne .not_sprite1
    lea sprite1,a0
    bra .sprite_decided
    .not_sprite1
    cmp.w #2,d0
    bne .not_sprite2
    lea sprite2,a0
    bra .sprite_decided
    .not_sprite2
    .sprite_decided:
    ; Get sprite's height in d7
    move.l 2(a0),d7
    ; Get mask address in a6
    move.l 6(a0),a6
    (etc)


.bss  ; Devpac: "section bss"

sprite0:
    sprite0_width:            ds.w 1
    sprite0_height:           ds.w 1
    sprite0_bpls:             ds.w 1
    sprite0_mask_address:     ds.l 1
    sprite0_data_address:     ds.l 1

sprite1:
    sprite1_width:            ds.w 1
    sprite1_height:           ds.w 1
    sprite1_bpls:             ds.w 1
    sprite1_mask_address:     ds.l 1
    sprite1_data_address:     ds.l 1

sprite2:
    sprite2_width:            ds.w 1
    sprite2_height:           ds.w 1
    sprite2_bpls:             ds.w 1
    sprite2_mask_address:     ds.l 1
    sprite2_data_address:     ds.l 1

So this isn’t the worst imaginable solution you could think up, is it? After all, if you need to add a couple of sprites, you can just copy the line “sprite2:” and the 5 lines following it, edit the 2’s into 3’s, and throw in some extra code in the whole “cmp / bne” section at the start of the “draw_sprite:” subroutine.

But what if you suddenly want to expand on the properties each sprite has? It’s not hard to imagine a scenario where this gets a little crazy, fast.

The solution

Actually, part of the solution is already in the code above:

  1. the way all sprites have the same internal structure
  2. the way we use a base address, a0, and offsets

But we can clean this up considerably!

Example with struct

To describe the entity I’m going to introduce here, I’m going to use the term “struct” from C. A struct can be thought of as a template for data, but under the hood of the assembler, it’s really just a bunch of equ’s being managed cleverly.

So, here is the code from above, converted to use a struct, and interspersed with examples and commentary. For a version ready to copy-and-paste, see below.

Code, broken up by explanations

.68000  ; Devpac: "section text"

.abs  ; Devpac: "rsreset"
sprite_width:            ds.w 1  ; Devpac: replace "ds" with "rs" everywhere
sprite_height:           ds.w 1
sprite_bpls:             ds.w 1
sprite_mask_address:     ds.l 1
sprite_data_address:     ds.l 1
.68000  ; Devpac: not necessary
sprite_struct_size equ ^^abscount  ; Devpac: replace "^^abscount" with "__RS"

So, none of this actually generates any code or data! From the assembler’s perspective, this is equivalent to

sprite_width         equ 0
sprite_height        equ 2
sprite_bpl           equ 4
sprite_mask_address  equ 6
sprite_data_address  equ 10
sprite_struct_size   equ 14 

…but of course with the benefit of being completely flexible:

  • The sizes and offsets are automatically correct if we add or remove properties
  • The sizes and offsets are automatically correct if we change the size or order of the properties
  • sprite_struct_size will update automatically to reflect the size of the struct

Let’s also put in an equ to determine how many sprites we want to reserve memory for:

num_sprites equ 3

In our fictional draw_sprite subroutine, we can now remove all those cmp/bne/bra instructions, and while we do need a multiplication, I replaced that with a small table (the size of which is automatic).

draw_sprite:
; In:  d0.w  - Sprite number
;      d1.w  - X position
;      d2.w  - Y position
    ; Determine which sprite
    lea sprites,a0
    lea sprite_offset_table,a1
    add.w d0,d0
    add.w d0,d0
    add.l (a1, d0.w),a0

    ; Get sprite's height in d7
    move.l sprite_height(a0),d7
    ; Get mask address in a6
    move.l sprite_mask_address(a0),a6
    (etc)

Yes, because the offsets are now named, the code is so clear we could even remove those comments without losing any readability.

.data  ; Devpac: "section data"    

sprite_offset_table:
addr set 0
rept num_sprites
  dc.l addr
addr set addr+sprite_struct_size
endr

This is the table used to get rid of the multiplication for setting the offset based on the sprite number. Thanks to the num_sprites equ from above, the table will never be larger than it needs to be!

.bss  ; Devpac: "section bss"

    even
sprites:    ds.b num_sprites*sprite_struct_size

…and here we have the same thing; the bss space will automatically resize when you change the number of sprites or the struct itself.

Code, complete

.68000  ; Devpac: "section text"

.abs  ; Devpac: "rsreset"
sprite_width:            ds.w 1  ; Devpac: replace "ds" with "rs" everywhere
sprite_height:           ds.w 1
sprite_bpls:             ds.w 1
sprite_mask_address:     ds.l 1
sprite_data_address:     ds.l 1
.68000  ; Devpac: not necessary
sprite_struct_size equ ^^abscount  ; Devpac: replace "^^abscount" with "__RS"

num_sprites equ 3


draw_sprite:
; In:  d0.w  - Sprite number
;      d1.w  - X position
;      d2.w  - Y position
    ; Determine which sprite
    lea sprites,a0
    lea sprite_offset_table,a1
    add.w d0,d0
    add.w d0,d0
    add.l (a1, d0.w),a0

    ; Get sprite's height in d7
    move.l sprite_height(a0),d7
    ; Get mask address in a6
    move.l sprite_mask_address(a0),a6
    (etc)



.data  ; Devpac: "section data"    

sprite_offset_table:
addr set 0
rept num_sprites
  dc.l addr
addr set addr+sprite_struct_size
endr


.bss  ; Devpac: "section bss"

    even
sprites:    ds.b num_sprites*sprite_struct_size

Excellence in Art

Self-taught masturbator