diff --git a/docs/css/para.css b/docs/css/para.css index f0c5021b48d..88fe41387dd 100644 --- a/docs/css/para.css +++ b/docs/css/para.css @@ -26,3 +26,7 @@ header { --md-typeset-a-color: #ff558d; } + +.md-typeset .admonition { + font-size: 0.8rem; +} diff --git a/docs/references/adv_bitflags.md b/docs/references/adv_bitflags.md new file mode 100644 index 00000000000..a76435abcac --- /dev/null +++ b/docs/references/adv_bitflags.md @@ -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. diff --git a/docs/references/bitflags.md b/docs/references/bitflags.md new file mode 100644 index 00000000000..2df3988181d --- /dev/null +++ b/docs/references/bitflags.md @@ -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. diff --git a/docs/references/glossary.md b/docs/references/glossary.md index ab9f8bf5971..d2921f08c03 100644 --- a/docs/references/glossary.md +++ b/docs/references/glossary.md @@ -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). diff --git a/docs/references/images/bitflags.png b/docs/references/images/bitflags.png new file mode 100644 index 00000000000..2c35287a751 Binary files /dev/null and b/docs/references/images/bitflags.png differ diff --git a/mkdocs.yml b/mkdocs.yml index e3533b9ca27..73aa8d48235 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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'