Binary flags: различия между версиями

imported>Rohesie
Нет описания правки
imported>Bawhoppennn
(Added Category)
 
(не показаны 2 промежуточные версии 2 участников)
Строка 397: Строка 397:


{{Contribution guides}}
{{Contribution guides}}
[[Category:Coding]]

Текущая версия от 03:23, 17 февраля 2022

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 though? 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 ( ~ )

NOTgate.gif

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 ( & )

ANDgate.gif


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 ( | )

ORgate.gif


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 ( ^ )

XORgate.gif

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 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

If you refer back to the XOR truth table you can see these principles clearly

First the truth table entries that deal with a 1 on the right hand side of the XOR operation

  • 0 ^ 1 = 1
  • 1 ^ 1 = 0

Then the truth table entries that deal with a 0 on the right hand side of the XOR operation

  • 0 ^ 0 = 0
  • 1 ^ 0 = 1


To help illustrate this concept, lets do an example 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 horizontally

       0b1010
XOR    0b1000
RESULT 0b0010

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)

       0b0010
XOR    0b1000
RESULT 0b1010

Final result, 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, you can even combine sets of flags to toggle multiple at once

e.g disabilities = disabilities ^ (DISABILITY_ARM | DISABILITY_LEG)

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 = NONE
 
 /mob/proc/add_disability(dis)
   disabilities |= dis
 
 /mob/proc/check_disability(dis)
   if( disabilities & dis )
     return TRUE
   else
     return FALSE
 
 /mob/proc/remove_disability(dis)
   disabilities &= ~dis


Hosting Hosting a serverSetting up the databaseWorking with /tg/station as an upstream repository
Contributing Guide to contributing to the gameSetting up gitDownloading the source codeReporting issuesChangelogs
Coding Understanding SS13 codeSS13 for experienced programmersCode docsCoding standardsGetting Your Pull AcceptedBinary flags‎Text FormattingMySQL
Mapping Guide to mappingMap mergerGuide to door access
Spriting Guide to spritingResolving icon conflicts
Wiki Guide to contributing to the wikiWikicodeAutowiki