Skip to content

A macro pack adding struct-like functionality to RGBDS

License

Notifications You must be signed in to change notification settings

ISSOtm/rgbds-structs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

55 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RGBDS structs

A RGBDS macro pack that provides struct-like functionality.

Download

Please select a version from the releases, and download either of the "source code" links. (If you do not know what a .tar.gz file is, download the .zip one.)

The latest rgbds-structs version is 4.0.2. It will only work with RGBDS 0.6.0 and newer. A previous version, 1.3.0, is confirmed to work with RGBDS 0.3.7, but should also work with versions 0.3.3 and newer. If you find a compatibility issue, please file it here.

Installing

This doesn't actually require any installing, only to INCLUDE the file structs.inc in your project. This project is licensed under the MIT license, which allows including a copy of structs.inc in your project. I only ask that you credit me, please :)

Examples can be found in the examples folder; both of what to do, and what not to do with explanations of the error messages you should get.

Usage

Please do not rely on any macro or symbol not documented below, as they are not considered part of the API, and may unexpectedly break between releases. RGBDS does not allow any scoping, so macros are "leaky"; apologies if you get name clashes.

Ensuring version compatibility

rgbds-structs follows semantic versioning, so you can know if there are breaking changes between releases. You can also easily enforce this in your code, using the rgbds_structs_version macro: simply call it with the version of rgbds-structs you require (rgbds_structs_version 2.0.0), and it will error out if a (potentially) incompatible version is used.

Declaring a struct

  1. Begin the declaration with struct StructName. This need not be in a SECTION, and it is rather recommended to declare structs in header files, as with C.
  2. Then, declare each member, using the macros bytes, words, and longs. The declaration style is inspired by RGBASM's _RS command family: the macro name gives the unit type, the first argument specifies how many units the member uses, and the second argument gives the member name.
  3. Finally, you must close the declaration with end_struct. This is required to properly define all of the struct's constants, and to be able to declare another struct (which will otherwise fail with a descriptive error message). Please note that forgetting to add an end_struct does not always yield any error messages, so please be careful.

Please do not use anything other than the prescribed macros between struct and end_struct (especially the _RS family of directives), as this may break the macros' operation.

Example of correct usage:

    ; RGBASM requires whitespace before the macro name
    struct NPC
    words 1, YPos         ;  2 bytes
    words 1, XPos         ;  2 bytes
    bytes 1, YBox         ;  1 byte
    bytes 1, XBox         ;  1 byte
    bytes 6, Name         ;  6 bytes
    longs 4, MovementData ; 16 bytes
    end_struct

Note that no padding is inserted between members by rgbds-structs itself; insert "throwaway" members for that.

    struct Example
        bytes 3, First
        bytes 1, Padding  ; like this
        words 2, Second
    end_struct

(Some like to insert an extra level of indentation for member definitions. This is not required, but may help with readability.)

Note also that whitespace is not allowed in a struct or member's name.

Sometimes it's useful to give multiple names to the same area of memory, which can be accomplished with alias.

    struct Actor
        words 1, YPos
        words 1, XPos
        ; Since `alias` is used, the following field will have 2 names
        alias Money ; If this actor is the player, store how much money they have.
        words 1, Target ; If this actor is an enemy, store their target actor.
    end_struct

Passing a size of 0 to any of bytes, words, or longs works the same.

extends can be used to nest a structure within another.

    struct Item
        words 1, Name
        words 1, Graphics
        bytes 1, Type
    end_struct

    struct HealingItem
        extends Item
        bytes Strength
    end_struct

This effectively copies the members of the source struct, meaning that you can now use HealingItem_Name as well as HealingItem_Strength.

If a second argument is provided, the copied members will be prefixed with this string.

    struct SaveFile
        longs 1, Checksum
        extends NPC, Player
    end_struct

This creates constants like SaveFile_Player_Name.

extends can be used as many times as you want, anywhere within the struct.

Defined constants

A struct's definition has one constant defined per member, which indicates its offset (in bytes) from the beginning of the struct. For the NPC example above, NPC_YPos would be 0, NPC_XPos would be 2, NPC_YBox would be 4, and so on.

Two additional constants are defined: sizeof_NPC contains the struct's total size (here, 16), and NPC_nb_fields contains the number of members (here, 6).

Be careful that dstruct relies on all of these constants and a couple more; dstruct may break in unexpected ways if tampering with them, which includes PURGE.

None of these constants are exported by default, but you can export them manually, or INCLUDE the struct definition(s) everywhere the constants are to be used. Since dstruct and family require the constants to be defined at assembling time, those macros require the former solution. However, the latter solution may decrease build times if you have a lot of source files.

If you want the constants to be exported by default, define symbol STRUCTS_EXPORT_CONSTANTS before calling struct.

Using a struct

The following functionality requires the struct to have been defined earlier in the same RGBASM invocation (aka "translation unit").

To allocate a struct in memory, use the dstruct StructName, VarName macro. For example:

    ; Again, remember to put whitespace before the macro name
    dstruct NPC, Player

This will define the following labels: Player (pointing to the struct's first byte), Player_YPos, Player_XPos, Player_YBox, etc. (all pointing to the struct's corresponding attribute). These are all declared as exported labels, and will thus be available at link time. (You can PURGE them if you do not want this.)

You can customize the label naming by defining the string equate STRUCT_SEPARATOR; it will replace the underscore in the above. Of particular interest is DEF STRUCT_SEPARATOR equs ".", which causes members to be defined as local labels, but prevents the "root" label from itself being a local label. (This is because Player.YPos is a valid RGBDS symbol name, but .player.YPos is not.) STRUCT_SEPARATOR can be changed and even PURGEd between invocations of dstruct and family.

It is unnecessary to put a label right before dstruct, since a label is declared at the struct's root.

Unless the struct's name contains a dot ., two extra constants are declared, that mirror the struct's: sizeof_Player would be equal to sizeof_NPC, and Player_nb_fields would equal NPC_nb_fields (sse below). These constants will keep their values even if the originals, such as sizeof_NPC, are PURGE'd. Like structs' constants, these are not exported unless STRUCTS_EXPORT_CONSTANTS is defined.

Defining data from a struct

The use of dstruct described above makes it act like ds (meaning, it can be used in RAM, and will be filled with padding bytes if used in ROM). However, it is possible to use dstruct to define data without having to resort to piles of dbs and dws.

    dstruct NPC, Toad, 42, 69, 3, 2, "TOAD", $DEAD\, $BEEF

The syntax is the same, but add one argument ("initializer") per struct member. bytes will be provided to db, words to dw, and longs to dl. (Of course, this can only be used in ROM SECTIONs.) If you have more than one "unit" per member, you will likely want a list as a single initializer; to achieve this, you can escape commas like shown above.

If an initializer provides less units than specified (such as "TOAD" above being 4 bytes instead of 6), trailing padding bytes will be inserted.

Having to remember the order of arguments is tedious and nondescript, though, so rgbds-structs took a hint from C99/C++20 and supports "designated initializers":

    dstruct NPC, Toad, .YPos=42, .XPos=69, .YBox=3, .XBox=2, .Name="TOAD", .MovementData=$DEAD\, $BEEF
    ; or, equivalent:
    dstruct NPC, Toad, .Name="TOAD", .YPos=42, .XPos=69, .MovementData=$DEAD\, $BEEF, .YBox=3, .XBox=2

When using designated initializers, their order does not matter, but they must all be defined once and exactly once.

Defining an array of structs

It's possible to copy-paste a few calls to dstruct to create an array, but dstructs automates the task. Its first argument is the number of structs to define, and the next two are passed as-is to dstruct, except that a decimal index is appended to the struct name. dstructs does not support data arguments; make manual calls to dstruct for that—you would have to pass all the data arguments individually anyway.

Credits

Written by ISSOtm and contributors.