Structs

Structs are an advanced form of labels with the purpose of making access into structured data blocks easier. The general syntax is as follows

struct {identifier} {snes_address}
    [label...]
endstruct [align {num}]

where identifier can contain any of the following characters:
a-z A-Z 0-9 _
The snes_address parameter can be any number literal or math statement evaluating to an SNES address. This address marks the start of the struct. The label parameter should be any number of labels, ideally coupled with skip commands. These labels become offsets into the struct. Internally, the struct command will do something similar to this

pushpc
base snes_address

whereas the endstruct command will do something similar to this

base off
pullpc

Take a look at the simple example below:

struct ObjectList $7E0100
    .Type: skip 1
    .PosX: skip 2
    .PosY: skip 2
    .SizeX: skip 1
    .SizeY: skip 1
endstruct

This defines a struct called ObjectList at location $7E0100 with a size of 7 (the sum of all skip commands). You can access into this struct like so:

lda ObjectList.PosY

This is equal to:

lda $7E0103     ; $7E0100+1+2

The final address is calculated by taking the start of the struct ($7E0100) and adding to that all the skips preceding the .PosY label (1 and 2). Aside from accessing structs directly, it's also possible to access them as arrays. A simple example:

lda ObjectList[2].PosY

The final address in this case is calculated by the equation:
struct_start + (array_index * struct_size) + label_offset
So in this case, our final address is $7E0100 + (2 * 7) + (1 + 2) = $7E0111. When using structs this way, the optional align parameter becomes relevant. This parameter controls the struct's alignment. Simply put, when setting a struct's alignment, Asar makes sure that its size is always a multiple of that alignment, increasing the size as necessary to make it a multiple. Let's take another look at the example above with an added alignment:

struct ObjectList $7E0100
    .Type: skip 1
    .PosX: skip 2
    .PosY: skip 2
    .SizeX: skip 1
    .SizeY: skip 1
endstruct align 16

With an alignment of 16 enforced, this struct's size becomes 16 (the first multiple of 16 that 7 bytes fit into). So when accessing the struct like this

lda ObjectList[2].PosY

the final address becomes $7E0100 + (2 * 16) + (1 + 2) = $7E0123. If we add some data into the struct

struct ObjectList $7E0100
    .Type: skip 1
    .PosX: skip 2
    .PosY: skip 2
    .SizeX: skip 1
    .SizeY: skip 1
    .Properties: skip 10
endstruct align 16

its original size becomes 17. Since a final size of 16 would now be too small to contain the entire struct, the alignment instead makes the struct's final size become 32 (the first multiple of 16 that 17 bytes fit into), so in our example of

lda ObjectList[2].PosY

we now end up with a final address of $7E0100 + (2 * 32) + (1 + 2) = $7E0143.

Extending structs

Another feature that is unique to structs is the possibility of extending previously defined structs with new data. The general syntax for this is as follows:

struct {extension_identifier} extends {parent_identifier}
    [label...]
endstruct [align {num}]

This adds the struct extension_identifier at the end of the previously defined struct parent_identifier. Consider the following example:

struct ObjectList $7E0100
    .Type: skip 1
    .PosX: skip 2
    .PosY: skip 2
    .SizeX: skip 1
    .SizeY: skip 1
endstruct

struct Properties extends ObjectList
    .Palette: skip 1
    .TileNumber: skip 2
    .FlipX: skip 1
    .FlipY: skip 1
endstruct

The struct ObjectList now contains a child struct Properties which can be accessed like so:

lda ObjectList.Properties.FlipX

Since extension structs are added at the end of their parent structs, the offset of .FlipX in this example is calculated as
parent_struct_start_address + parent_struct_size + extension_struct_label_offset,
in other words, our final address is $7E0100 + 7 + (1 + 2) = $7E0109. Note that extending a struct also changes its size, so in this example, the final size of the ObjectList struct becomes 12. Extended structs can also be accessed as arrays. This works on the parent struct, as well as the extension struct.

lda ObjectList[2].Properties.FlipX
lda ObjectList.Properties[2].FlipX

In the first example, our final address is calculated as
parent_struct_start_address + (combined_struct_size * array_index) + parent_struct_size + extension_struct_label_offset,
whereas in the second example, it's calculated as
parent_struct_start_address + parent_struct_size + (extension_struct_size * array_index) + extension_struct_label_offset,
so we end up with final addresses of $7E0100 + (12 * 2) + 7 + (1 + 2) = $7E0122 and $7E0100 + 7 + (5 * 2) + (1 + 2) = $7E0114.

A few further things to note when using structs in Asar:

  • It's possible to extend a single struct with multiple extension structs. However, this can be counter-intuitive. The size of the extended struct becomes the size of the parent struct plus the size of its largest extension struct, rather than the size of the parent struct plus the sizes of each of its extension structs. This also means that when accessing those extension structs, they all start at the same offset relative to the parent struct. This can be confusing and is often not what's actually intended, so for code clarity, it's recommended to only extend structs with at most a single other struct.
  • It's possible to enforce alignments when using extension structs. However, this will only determine the alignment of the parent struct and/or the extension struct(s), depending on where it's specified. It won't determine the alignment of the combined struct. This can be confusing and is usually not what is intended. There currently is no universal workaround for this, so when a certain alignment is required for a struct, it's recommended to not use extension structs with it.
  • It's not possible to access both, a parent struct and its extension struct, as arrays simultanously.
  • An extension struct can't be extended itself.