SCUA

Manual

Flags

A flags type is a bit-set: a value that holds any combination of a fixed set of named flags, packed into a single integer. It's the type you reach for when something can be several things at once — a collision mask (Ground | Wall), an entity's state (Grounded | Attacking), an input bitmask, a set of ability flags.

It's a close sibling of enum. An enum is one of its variants; a flags value is any subset of its flags. Where an enum gives you exhaustiveness in a match, a flags type gives you type-safe set operations: combine, test, and remove flags without hand-rolling & / | / << on bare integers (and getting the precedence or the != 0 wrong).

flags Layer {
  Ground,
  Player,
  Enemy,
  Wall,
}

let mask = Layer.Ground | Layer.Player
print(Layer.Player in mask)
print(Layer.Enemy in mask)
print(mask.to_int())

Run it:

$ scua layer.scua
true
false
3

The declaration reads like an enum: the type name first, then the flags in a comma-separated { } list, one per line (so you can comment each one). That's the layout scua fmt produces and keeps.

Each flag is one bit — Ground is 1, Player is 2, Enemy is 4, Wall is 8 — so a combination is just those bits OR-ed together (Ground | Player is 3). That's the same representation a hand-written bitmask would use, so a flags value costs nothing extra at runtime; the difference is that the compiler now knows its type.

#Building and testing a mask

Combine flags with | (union), and test membership with in. flag in mask is true when every bit of the left is set in the right — so it works for a single flag or a combination:

flags Layer {
  Ground,
  Player,
  Enemy,
  Wall,
}
let collides = Layer.Player | Layer.Enemy | Layer.Wall

print(Layer.Player in collides)                 -- a single flag
print((Layer.Enemy | Layer.Wall) in collides)   -- both must be set
print(Layer.Ground in collides)
$ scua in.scua
true
true
false

The other set operations: & is intersection (the flags in both), ^ is toggle (symmetric difference), and .without(...) removes flags — one, or several at once:

flags Layer {
  Ground,
  Player,
  Enemy,
  Wall,
}
let mask = Layer.Ground | Layer.Player | Layer.Wall
print(mask.without(Layer.Player).to_int())              -- remove one
print(mask.without(Layer.Player | Layer.Wall).to_int()) -- remove two at once
print(Layer.none.to_int())
print(Layer.all.to_int())
print(mask == Layer.none)
$ scua ops.scua
9
1
0
15
false

Every flags type comes with none (the empty set) and all (every flag) for free, and you test for "no flags set" by comparing with none.

#Type safety

A flags value is its own type, distinct from a plain int and from every other flags type. Mixing two different flags types — the classic "I OR-ed a collision layer into a state mask" bug — is a compile error, not a value that's silently wrong:

flags Layer {
  Ground,
  Player,
}
flags State {
  Idle,
  Moving,
}
let oops = Layer.Ground | State.Moving
$ scua mix.scua
scua: mix.scua:9: type error: cannot combine a Layer with a State — different flags types

The same protection applies to comparing (==), testing (in), and removing (.without(...)) across two flags types. You can annotate bindings, parameters, and returns with the flags type (let m: Layer = ..., fn f(mask: Layer)), and the type flows through | / & / .without(...) so it stays checked.

#Bridging to and from an int

A flags value already is an int, but its type is the flags type — so when you need the raw number (to store in a save, send to a host, or print), call .to_int(). To go the other way, Name.from_int(n) converts a raw int back to the flags type. It is fail-closed: if the int carries a bit the type doesn't define, it raises a fault rather than smuggle in a value the type can't represent.

flags Layer {
  Ground,
  Player,
}
print(Layer.from_int(3).to_int())   -- ok: bits 0 and 1 are both declared
print(Layer.from_int(8))            -- bit 3 isn't a Layer flag
$ scua fromint.scua
3
scua: fromint.scua:6: from_int: this int has bits that flags `Layer` does not define

#Notes and limits

  • A flags type holds up to 64 flags (one 64-bit word); declaring more is a compile error.
  • Flags are positional — the first flag is bit 0, the next bit 1, and so on. When a flags type is saved and loaded across versions, only ever add flags at the end, so existing bits keep their meaning.
  • Printing a flags value shows its underlying integer (like an enum), not the flag names.

#See also