Binary flags: различия между версиями
imported>Oranges |
imported>Oranges |
||
Строка 319: | Строка 319: | ||
disabilities = 0b0010 | disabilities = 0b0010 | ||
If we want to toggle the flag again, we can repeat the operation | If we want to toggle the flag again, we can repeat the operation on the current value of the binary flag variable | ||
disabilities = disabilities ^ DISABILITY_ARM | disabilities = disabilities ^ DISABILITY_ARM | ||
disabilities = 0b0010 ^ 0b1000 (note | disabilities = 0b0010 ^ 0b1000 (note DISABILITY_ARM is currently set to 0) | ||
*0 ^ 0 = 0 | *0 ^ 0 = 0 | ||
*1 ^ 0 = 1 | *1 ^ 0 = 1 |
Версия от 10:24, 12 января 2021
So you want to make a variable which only tells you whether something is true or false?
Well you could do it the easy way of:
var/on = 1
The problem comes when you want to work with something like disabilities. There are many disabilities and having one variable for each disability is both costly in terms of memory used, as well as tedious to work with. To solve such a problem we're going to use binary flags.
A quick summary of binary
Counting in binary
We are used to digits from 0 to 9. But did you ever think how we actually count?
When we start with 0 and keep adding 1 we get the following sequence:
0 1 2 3 ... 8 9
After this happens we run out of digits. So we reset the current digit and add 1 to the next one
9 10 11 ...
This works the same in binary, however there are only two digits available: 0 and 1.
So if we want to count, we count:
0 1
And we're already out of digits, so we reset the current digit and add 1 to the next one:
0 1 10 11 100 101 110 111 ...
You may have heard about Hex or "Hexadecimal", which is the same principe as this, but it uses 16 different digits. 0-9, and after that, A-F.
Variable representation in binary
Okay so now that we can count, I'll tell you something that you have probably never heard of. Computers use binary for everything! (No shit...) So yeah, computers do show us numbers like 1,2,3,4,5 and so on as decimal numbers, but in reality they store all of them as binary.
So if you write:
var/c = 8
the actual value that is stored is (The 0b only says that what follows is a binary number) c == 0b1000
Operations in binary
What can we do with these numbers tho? There are 3 binary operators available in byond: And (&), Or (|) and Not (~). NOTE: These are binary operators, which means they do the operation on each individual bit.
A good way to understand how these work is to not think of these 1s and 0s as numbers, but as truth and lies (false). So wherever you have a 1, you can substitute it with a true statement, while where you have a 0 you substitute it with a lie (a false statement).
Binary Not ( ~ )
This is the simplest one. All it does it inverts the statement.
~0 = 1 ~1 = 0
The easiest way to understand this is simply, if you have a true statement "The sky is blue" and you stick a not in it, the statement's meaning is inverted: "The sky is not blue". So if the original statement was true (1), then the inverted one is of course false (0). ~1 = 0.
The same applies if you negate a false statement. If you have a false statement: "The sky is green" and you negate it, it becomes the truth: "The sky is not green". ~0 = 1.
Binary And ( & )
AND is defined with two statements. If you either of them is a lie, then the whole statement will be a lie. Because both the first AND the second must be true for the statement to be the truth.
0 & 0 = 0 (If you tell a lie, and then tell another lie, overall you are lying)
0 & 1 = 0 (If you tell a lie, and then tell some truth, you have still lied overall)
1 & 0 = 0 (If you tell the truth, but then lie, you have lied overall)
1 & 1 = 1 (If you tell the truth and then tell another truth, you have told the truth overall)
Binary Or ( | )
OR is also defined with two statements. It however only requires one of the two to be true for the statement to be true.
0 | 0 = 0 ( The sky is green or the night is sunny. Both statements are lies, so it is still a lie overall )
0 | 1 = 1 ( The sky is green or the night is dark. One of the statements is true, so the overall statement is true )
1 | 0 = 1 ( the sky is blue or the night is sunny. One of the statements is true, so the overall statement is true )
1 | 1 = 1 ( the sky is blue or the night is dark. Both statements are true, meaning the overall statement is true )
Binary Xor ( ^ )
XOR is like OR, except if both statements are true, the output is false. XOR is also known as a difference detector, it'll output true if the two inputs are different from each other.
- 0 ^ 0 = 0
- 0 ^ 1 = 1
- 1 ^ 0 = 1
- 1 ^ 1 = 0
Actual use
In code you don't have 1b variables, let's suppose you have 8b variables (can store values from 0 to 255). In actual code they're larger, but let's suppose that to make this a bit shorter. Binary operators work on each individual bit so if you have a variable d defined as 230 (decimal), which is 11100110 (binary) - what would happen if you used binary operators on this and other values?
var/c = 230 // In binary this is 0b11100110 d = ~c //This inverts each bit, so 0b11100110 becomes 0b00011001 (25 decimal)
Okay, so negation is easy enough, but what about if you did the following:
var/f = 255 // full - this is 0b11111111 var/e = 0 // empty - this is 0b00000000 v1 = c | f // In each of the binary operations the bit in v1 = bit in c OR 1. And we know that anything OR 1 = 1. v1 becomes 0b11111111. This is useful for SETTING bits. v2 = c | e // In each of the binary operations the bit in v2 = bit in c OR 0. We know that anything or 0 remains unchanged. so v2 == c (0b11100110) v3 = c & f // In each of the binary operations the bit in v3 = bit in c AND 1. 1 does not change the outcome in AND operations, so v3 == c (0b11100110) v4 = c & e // In each of the binary operations the bit in v4 = bit in c AND 0. Anything AND 0 = 0, so v4 becomes 0 (0b00000000). This is useful for RESETTING bits.
The idea behind bitflags
Back to our disabilities. Let's assume we want to implement 4 disabilities. The only bit of information we need is whether a mob has a disability or not. We could do:
var/disability1 = 0 var/disability2 = 0 var/disability3 = 0 var/disability4 = 0
The problem however is that it would be very hard to add a 5th disability later on, as well as it requiring four integers, where we only really need 4 bits of information. (0b0000)
So we'll make a flag variable, you define them the same way as you define any numeric variable:
var/disabilities = 0
Remember, I mentioned all of these numeric variables are represented with bits, so we have just created a variable that actually has the value of 0b0000000000000000. We will use the four bits furthest to the right (bold) to determine our four disabilities. But first we will need to define numbers for them.
The numbers we will use have to be unique bits, so we'll use:
0b0001 = poor eyesight 0b0010 = poor hearing 0b0100 = broken leg 0b1000 = broken arm
This will give us enough information to tell if someone has a disability or not, since if someone has the value of 0b0000, we know he doesn't have any disability, if he has 0b0001, we know he has poor eyesight, if he has 0b0101, we know he has both a broken leg and poor eyesight.
For easier use we will define these values as preprocessor constants. How exactly these differ from program constants is not too important for now. You however define them by opening setup.dm (code folder) and adding lines that look like this:
#DEFINE DISABILITY_EYE 1 #DEFINE DISABILITY_EAR 2 #DEFINE DISABILITY_LEG 4 #DEFINE DISABILITY_ARM 8
Most often it is best to put these in setup.dm, because they are only defined from the spot where they're defined in the file on, and setup.dm is the first file loaded.
Why did we pick 1,2,4,8? Because the binary value of 0b0001 is 1 (decimal); 0b0010 is 2 (decimal); 0b0100 is 4 (decimal) and 0b1000 is 8 (decimal).
Okay, so now we have the constants defined that will allow us to manage disabilities and we have a variable to store the information about disabilities.
Changing bitflags
We defined our variable (disabilities) as 0, which means our mob will not have any disabilities when we start. How do we add some?
We're going to use those binary operators now.
Setting a bitflag
Above we did the following:
var/f = 255 //0b11111111 v3 = c & f
What happened was that completely irrespective of what c was, v3 became 0b11111111, since anything OR 1 = 1.
But we don't have to have the entire row of ones. What if we just add a single 1 in there. After all 0 OR anything = anything. The 0 doesn't change the value.
So let's write a proc, where dis is the disability we want to add (DISABILITY_EYE, DISABILITY_EAR, etc):
mob/proc/add_disability(var/dis) disabilities = disabilities | dis
So if disabilities were 0b0000 before we called it, and we called it with DISABILITY_EAR, which is 2, we would get the following calculation
calling M.add_disability(DISABILITY_EAR)
disabilities = 0b0000 | 0b0010
0b0000 OR 0b0010 = 0b0010
So the disabilities variable now becomes 0010, which indicates it has the poor hearing disability. But what if we called the same proc again, with the same argument DISABILITY_EAR, would anything change?
calling M.add_disability(DISABILITY_EAR)
disabilities = 0b0010 | 0b0010
0b0010 OR 0b0010 = 0b0010
Nope. It remains the same, as it should.
And if we want to add another leg disability (DISABILITY_LEG)?
calling M.add_disability(DISABILITY_LEG)
disabilities = 0b0010 | 0b0100
0b0010 OR 0b0100 = 0b0110
The old ear disability remains untouched, and the new leg disability is added.
Checking bitflags
Okay, so now we learned how to add a flag, now to check for one. This time AND (&) will come in handy.
Above we did...
var/f = 255 //0b11111111 var/e = 0 //0b11111111 v3 = c & f // since 1 and anything = anything, v3 didn't change from c. v4 = c & e // since anything and 0 = 0, v4 was set to 0.
We can use a combination of both to help us. If we want to check for the leg disability, we can do it like this:
mob/proc/check_disability(var/dis) if( disabilities & dis ) return 1 else return 0
The proc above takes a parameter and checks whether the bit is set. Let's suppose we have the same situation as we left off at above, with disabilities set to 0b0110.
If we call check_disability(DISABILITY_EAR) the proc will check:
disabilities & dis
0b0110 & 0b0010 = 0b0010
So the value is positive, which means the if check will pass and the proc will return 1 (truth), otherwise it will return 0 (false). The value of disabilities in this proc is not changed.
If we however check for the eye disability by calling check_disability(DISABILITY_EYE)
0b0110 & 0b0001 = 0b0000
The calculation yields a 0, so the if fails, and the proc returns a 0 (false)
Unsetting Bitflags
The last thing we need to cover is how to unset bitflags. I mentioned that anything & 0 = 0, so you can be sure we'll use this.
The problem however is that the values we have (disabilities as 0b0110, DISABILITY_EYE, _EAR, _ARM, _LEG) are not really sufficient, since, if we just do a simple & between the disabilities variable and the constant for the disability, it will end up resetting everything else:
disabilities = disabilities & DISABILITY_EAR
disabilities = 0b0110 & 0b0010
disabilities = 0b0010
This is not what we want. We want the disability we add in to be reset. So we'll need a 0 in place of the disability flag, and a 1 everywhere else. This is because 1 AND anything = anything; at the same time anything AND 0 = 0.
So what we have to do is invert the DISABILITY_EAR constant. We do this with the NOT operator:
/mob/proc/remove_disability(var/dis) disabilities = disabilities & ~dis
So now when we call remove_disability(DISABILITY_EAR) we will get
disabilities = disabilities & ~dis
disabilities = 0b0110 & ~0b0010
disabilities = 0b0110 & 0b1101
disabilities = 0b0100
In other words, disabilities will now no longer have the DISABILITY_EAR flag set.
Toggling bitflags
Sometimes, you want to toggle a bitflag, that is, if the bit on the flag is 1, you want to set it to 0, and if the bit is 0, you want to set it to 1
You can use the xor function to do this trivially, due to the following properties
- where the right hand side of the XOR is a 1, it will "toggle" the bits on the left hand side in the final output
- where the right hand side of the XOR is a 0, it will leave the left hand side values unchanged in the final output
Refer back to the XOR truth table to help see why this is the case
Consider the following case, where the user has the DISABILITY_ARM and the DISABILITY_EAR flags set and we want to toggle the DISABILITY_ARM flag to be the opposite of whatever it was before
disabilities = disabilities ^ DISABILITY_ARM
disabilities = 0b1010 ^ 0b1000
if we run through the XOR truth table for each pair of bits (starting from the least significant bit on the right)
- 0 ^ 0 = 0
- 1 ^ 0 = 1
- 0 ^ 0 = 0
- 1 ^ 1 = 0
Final result, the flag for DISABILITY_ARM is toggled off, but note that DISABILITY_EAR flag is unchanged, perfect! disabilities = 0b0010
If we want to toggle the flag again, we can repeat the operation on the current value of the binary flag variable
disabilities = disabilities ^ DISABILITY_ARM
disabilities = 0b0010 ^ 0b1000 (note DISABILITY_ARM is currently set to 0)
- 0 ^ 0 = 0
- 1 ^ 0 = 1
- 0 ^ 0 = 0
- 0 ^ 1 = 1
Final result and the DISABILITY_ARM flag is toggled back on, and DISABILITY_EAR remains unchanged still. disabilities = 0b1010
XOR can be trivially used then to toggle any specific flag by simply xoring the flag holding variable with the define that represents the bitflag you want to toggle.
Shorter forms of writing
If you noticed we have always written lines similar to
disabilities = disabilities & dis disabilities = disabilities | dis disabilities = disabilities & ~dis
There is a simpler way of writing:
disabilities |= dis disabilities = disabilities | dis
The two lines above are identical, as are:
disabilities &= dis disabilities = disabilities & dis
And of course:
disabilities &= ~dis disabilities = disabilities & ~dis
This is why you will frequently find code that is written with the abbreviated form.
Code
#DEFINE DISABILITY_EYE 1 #DEFINE DISABILITY_EAR 2 #DEFINE DISABILITY_LEG 4 #DEFINE DISABILITY_ARM 8 /mob var/disabilities = 0 /mob/proc/add_disability(var/dis) disabilities |= dis /mob/proc/check_disability(var/dis) if( disabilities & dis ) return 1 else return 0 /mob/proc/remove_disability(var/dis) disabilities &= ~dis