Using Bitmasks

From GECK
(Redirected from Bitmask)
Jump to: navigation, search

Bitmasks are a useful and efficient way to store data inside a single integer, commonly used with NVSE script functions, but they often seem daunting for beginners. In this tutorial we'll go over what are bitmasks, what are they used for and different ways to usem.

What are bitmasks?

Most commonly used numbers in scripting (and daily life) are in decimal. Every such number can be represented in a binary form, i.e. as a set of bits (ones and zeroes). For example, decimal number 5 can be represented as 0101. Changing any of these bits will give us a new decimal number, for example, 1101 becomes decimal 13. What this means in practice is that we can use every bit in a decimal number as a toggle, representing two states - on and off.
A bitmask is simply a collection of bits, and a programmer can give significance to each bit (on/off state) depending on how they choose to interpret them in their program.

  • In bitmasks, bits are counted from 0 and go from end to start of the binary representation. For example, 1110 has first bit (bit 0) off, then bit 1, 2, and 3 on.

What are bitmasks used for?

The most common use case for bitmasks is script functions that use flags. Each flag can only be on or off which makes them a perfect application for bitmasks. For example, GetWeaponFlags1 will return a number with first 7 bits each either on or off, representing specific flag values.

The reason for using bitmasks instead of a bunch of easy-to-manipulate boolean values is because it saves space. Say we wanted to store a bunch of Weapon Flags. A single int variable is usually represented by a 32 bits long binary number, which means we can represent up to 32 flags as being on/off using a single variable, instead of taking multiple variables and wasting space.

How to use bitmasks?

There are multiple ways to use bitmasks, which one to use is up to personal preference.

int i   ;* initialized to 0, in binary: 0
i = SetBit i 0    ; Set the 0th bit of i to 1, i now contains 1.
i = SetBit i 1    ; Set the 1th bit of i to 1, i now has two first bits set, which makes it 11 in binary and 3 in decimal
GetBit i 1        ; returns 1, first bit is set
int flags = GetWeaponFlags1 MyWeapon
if (GetBit flags 2)            ; check if bit 2 is on - "has scope" flag is set
...
endif

SetBit flags 3                 ; toggle bit 3 - enable "can't drop" flag
SetWeaponFlags1 flags MyWeapon ; set modified bitmask back on the weapon
  • Binary Notation - xNVSE allows directly specifying numbers in binary representation.
int i
i = 0b101 ; binary number with first and third bits set, 5 in decimal
  • NVSE Expressions - directly manipulate bits without the use of functions (less safe, recommended to advanced users only):
int flags = GetWeaponFlags1 MyWeapon
if (flags & 0b10)              ; check if bit 2 is on - "has scope" flag is set, using bitwise AND
..
endif

; the above is equivalent to:
if (flags & (1 << 1))          ; check if bit 2 is on - "has scope" flag is set, using bitwise AND + binary left shift
..
endif

flags |= (1 << 2)              ; toggle bit 3 - enable "can't drop" flag, using bitwise OR And Assign + binary left shift
;equivalent to: flags |= 0b100
SetWeaponFlags1 flags MyWeapon ; set modified bitmask back on the weapon
int a = 40;	  // 40 = 0010 1000 
int b = 90;	  // 90 = 0101 1010
int c = 0;        // 0  = 0000 0000   

; Bitwise AND
c = a & b;        // 8  = 0000 1000
;* Only the enabled bits that both a and b share are kept.

; Bitwise OR
c = a | b;        //122 = 0111 1010
;* Only the enabled bits that either a or b have are kept.

; Binary Left Shift
c = a << 2;       //160 = 1010 0000
;Shift the bits in 40 to the left by 2, first 2 bits are replaced with zeros.

; Binary Right Shift
c = a >> 2;       // 10 = 0000 1010
;Shift the bits in 40 to the right by 2, last 2 bits are replaced with zeros.

Above example loosely adapted from here.

See Also