-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: Add tutorial on bitflags (#27192)
* 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
1 parent
b38c4bb
commit 1328a3f
Showing
6 changed files
with
270 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,3 +26,7 @@ header { | |
|
||
--md-typeset-a-color: #ff558d; | ||
} | ||
|
||
.md-typeset .admonition { | ||
font-size: 0.8rem; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters