Skip to content

Commit

Permalink
docs: Add tutorial on bitflags (#27192)
Browse files Browse the repository at this point in the history
* docs: Add tutorial on bitflags

* add bit about max flags, also fix horrible admonition font size

* accidentally a word

* better terminology

* include contra's docs, better distinction between value and variable (hopefully), better example without a leading 0

* fuck

* this should stay higher up

* fix operator
  • Loading branch information
warriorstar-orion authored Nov 10, 2024
1 parent b38c4bb commit 1328a3f
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/css/para.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ header {

--md-typeset-a-color: #ff558d;
}

.md-typeset .admonition {
font-size: 0.8rem;
}
114 changes: 114 additions & 0 deletions docs/references/adv_bitflags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Advanced Bitwise Operations

Expanding on information covered in [Bitflags](./bitflags.md), this document
further explores how other bitwise operators can be used. This document uses the
prefix `0b` to denote a binary number, to distinguish them from numbers in base
ten. For example, the base ten number 5 is `0b101`, and the base ten number
1,110 is `0b10001010110`.

## Or Operator

The Or operator can be used merge bitflags, even if they contain the same flag.

```
var/a = 5 // 0b101
var/b = 6 // 0b110
var/c = a | b // 0b111 = 7
```

The Or operator can be used in conjunction with each other to reduce line count.
This example from the previous bitflags document can be simplified.

```dm
var/player_abilities = WALK
player_abilities |= SING
player_abilities |= READ
```

Instead of assigning twice, we can simplify.

```dm
var/player_abilities = DANCE
player_abilities |= (SING|READ)
```

Note that these are now wrapped in parentheses, primarily to make them easier to
read that they are a pair of OR'd bitflags.

## And Operator

The And operator can be used to check if 2 variables contain the same bitflag.

```
var/a = 5 // 0b101
var/b = 6 // 0b110
var/c = a & b // 0b100 = 4
```

The only bit that stayed true was the bit that was in both inputs. This is
different to the Or operator, where it only had to be in one input.

In the previous section, we turned a flag off using this example:

```dm
player_abilities &= ~SING
```

This done by using the & (And) operator and a negation of the bitflag. Negating
this bitflag replace all the 1s with 0s and all the 0s with 1s.

```
#define DANCE (1 << 4) // 0b10000 = 16
to_chat(world, DANCE) // 0b10000 = 16, note that all the leading zeros have been hidden here.
to_chat(world, ~DANCE) // 0b111111111111111111101111 = 16777199
```

When the `&= ~DANCE` operation is performed, it takes negated version, where the
flag is only zero and applies the and operation

```
var/player_abilities = (DANCE|SING|SWIM)
player_abilities &= ~DANCE // remove the dance flag
to_chat(world, player_abilities) // now just contains (SING | SWIM)
```

## Exclusive Or Operator.

The exclusive or operator `^` acts as a "toggle", and doesn't require knowledge
of what is inside the flags, as seen in the example below.

```
var/player_abilities = (DANCE|SING|SWIM)
player_abilities ^= DANCE // remove the dance flag
to_chat(world, player_abilities) // (SING | SWIM)
player_abilities ^= DANCE // enable the dance flag
to_chat(world, player_abilities) // (DANCE | SING | SWIM)
```

When used between two variables, this can be used to check the differences
between them.

```
var/a = 3 // 0b010
var/b = 5 // 0b101
var/c = a ^ b // 0b110 = 6
```

## Bitwise Shift Right

Finally, in conjunction to `<<` bitwise left shift, there is a bitwise right
shift, used with `>>`. These shifts can also be used with numbers other than
one, as shown in the example below.

```dm
var/a = 5 // 0b101 = 5
var/b = (a<<3) // 0b101000 = 40
var/c = (b>>1) // 0b10100 = 20
```

However, using these shifts are not common outside of defining bitflags, bitwise
right shift is barely used.

Not to be confused, these operators (`<<` and `>>`) are also used as "output"
and "input" operators for mobs, savefiles, etc. These are only rarely used,
users should be using helper procs like to_chat() instead.
146 changes: 146 additions & 0 deletions docs/references/bitflags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Bitflags

Let's say we wanted to store the abilities of a player. We could create a set of
variables like this:

```dm
/mob/player
var/can_walk = TRUE
var/can_swim = FALSE
var/can_fly = FALSE
var/can_sing = TRUE
var/can_dance = FALSE
var/can_read = TRUE
```

This isn't very clean or extensible. If we added a new ability, we'd have to add
a new variable. Instead, we can store all of these in a single variable using
_bitflags_.

Bitflags are a way of storing many pieces of information in a single number.

This is a simplification, but when you assign a number to a variable, that
number is stored in binary. If you write:

```dm
var/player_abilities = 41
```

Internally, BYOND stores the number 41 as the binary value `101001`. If we think
of every `0` and `1` in binary as representing an on/off switch, then we can
store lots of switches in a single number, by associating every binary digit
with a setting.

To be able to access each "on/off" switch in that number, we create a set of
defines. Each one represents one thing we want to toggle. We use the _bitwise
left shift_ operator, `<<`, to make reading them easier:

```dm
#define WALK (1 << 0) // 000001 = 1 in binary
#define SWIM (1 << 1) // 000010 = 2 in binary
#define FLY (1 << 2) // 000100 = 4 in binary
#define SING (1 << 3) // 001000 = 8 in binary
#define DANCE (1 << 4) // 010000 = 16 in binary
#define READ (1 << 5) // 100000 = 32 in binary
```

A bitwise left shift "shifts" the value 1 to each place in a binary
digit. If our binary value has 6 places, i.e. `000000`, then `(1 << 0)`
represents a `1` in the "zeroth" place: `000001`. Then `(1 << 1)` represents a
`1` in the first place: `000010`, and so on. These are still specific numbers;
we are just representing them in a unique way. For example, `(1 << 3)` is equal
to 8 in base ten, and is equal to `001000` in binary.

![](./images/bitflags.png)

> [!WARNING]
>
> Because of how BYOND represents numbers, a single number can only hold 24
> flags. In other words, once the amount of flags you wish to represent in a
> number reaches `(1 << 23)`, you have run out of available places to store
> flags in that variable.
>
> The technical explanation is: BYOND has a single numeric datatype stored as
> 32-bit IEEE 754 floating point. Performing bitwise operations on numbers in
> BYOND converts the number to its integer representation, using the 24 bits of
> the significand in the floating point representation, and then back to
> floating point afterwards.
## Operating on Bitflags

There will be several kinds of operations you'll want to perform on bitflags.
These operations are performed using _binary arithemetic operators_.

> [!NOTE]
>
> This guide only describes the most common bitflag operations. For a deeper
> dive into binary arithmetic and more complex operators, see the
> [Advanced Bitflags](./adv_bitflags.md) reference.
### Setting and Unsetting

In order to set flags, use the OR bitwise operator, `|`:

```dm
var/player_abilities = WALK | SING | READ
```

This "toggles" the slots for the provided flags and returns the result. In this
case, the value of `player_abilities` is now the number 41, because that is the
sum of the values represented by those three individual flags.

In other words, the value of these two variables is the same:

```dm
var/alice_abilities = 41
var/brian_abilities = WALK | SING | READ
```

The OR bitwise operator can also be used in assignment. For example:

```dm
var/player_abilities = WALK
player_abilities |= SING
player_abilities |= READ
```

This results in the same value as above.

If you have a flag you wish to toggle "off", you will use a combination of
bitwise AND (`&`) and negation (`~`). For example, if we wanted to remove
`SING` from the bitflag above:

```dm
player_abilities &= ~SING
```

This removes `SING` from the bitflag while keeping the other values set.

### Checking

In order to see if a bitflag has a specific flag toggled, use the bitwise AND
(`&`) operator in a conditional:

```dm
if(player_abilities & READ)
world << "Player can read!"
if(!(player_abilities & SING))
world << "Player can't sing!"
```

## Important Notes

### Use flags for unique settings

Bitflags should be used when it makes sense that multiple flags can be set
simultaneously. For example, it would not make sense to make the following
bitflag:

```dm
#define CAN_WALK (1 << 0)
#define CANNOT_WALK (1 << 1)
```

Because then both values can be toggled on in a single variable. Only use
bitflags when it makes sense to toggle any or all of the flags simultaneously.
4 changes: 4 additions & 0 deletions docs/references/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ An SS13 variable that saves the data of what is underneath if that that is
removed. For example, under station floors there would be a space turf and under
Lavaland turfs there would be lava.

## Bitflag
A single variable made of individual TRUE/FALSE values. See
[Bitflags](./bitflags.md) for an introduction on how to use bitflags.

## Buff
A buff is a change to a gameplay mechanic that makes it more powerful or more
useful. Generally the opposite of a [nerf](#nerf).
Expand Down
Binary file added docs/references/images/bitflags.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,7 @@ nav:
- 'References':
- 'Glossary': './references/glossary.md'
- 'Autodoc Guide': './references/autodoc.md'
- 'Bitflags': './references/bitflags.md'
- 'Advanced Bitflags': './references/adv_bitflags.md'
- 'Using Feedback Data': './references/feedback_data.md'
- 'Tick Order': './references/tick_order.md'

0 comments on commit 1328a3f

Please sign in to comment.