Flag-based bitmasking

This is a small resource that I find useful for bitmasking that I couldn’t find anywhere else. Bitmasking is basically just having a lot of true/false values in a single number. For example 511 can equate to 0b111111111, or 9 true/false values.

Flags come in as textual representations of each bit. This allows you to essentially name each bit and have control over it.

Here’s the module:

Module
local bitmask = {}
bitmask.__index = bitmask

function getFlagValue(flags, flag)
	local index = table.find(flags, flag)
	if index == nil then
		error("Flag " .. flag .. " not present in the bitmask")
	end
	return 2^(index-1)
end

function bitmask.new(...)
	local mask = {}
	setmetatable(mask, bitmask)
	mask.flags = {}
	mask.mask = 0
	
	local tempFlags = table.pack(...)
	if typeof(tempFlags[#tempFlags]) == "number" then
		mask.mask = tempFlags[#tempFlags]
		table.remove(tempFlags, #tempFlags)	
	end
	tempFlags["n"] = nil
	mask.flags = tempFlags
	tempFlags = nil
	
	
	return mask
end

function bitmask:GetMask()
	return self.mask
end

function bitmask:SetMask(newMask)
	self.mask = newMask
end

function bitmask:ToggleFlag(flag)
	local value = getFlagValue(self.flags, flag)
	self.mask = bit32.bxor(self.mask, value)
end

function bitmask:EnableFlag(flag)
	local value = getFlagValue(self.flags, flag)
	self.mask = bit32.bor(self.mask, value)
end

function bitmask:DisableFlag(flag)
	local value = getFlagValue(self.flags, flag)
	self.mask = bit32.band(self.mask, bit32.bnot(value))
end

function bitmask:HasFlag(flag)
	local value = getFlagValue(self.flags, flag)
	
	return bit32.band(self.mask, value) == value
end

function bitmask:HasAllFlags(...)
	for _, flag in ipairs(table.pack(...)) do
		if not self:HasFlag(flag) then return false end
	end
	
	return true
end

function bitmask:HasAnyFlags(...)
	for _, flag in ipairs(table.pack(...)) do
		if self:HasFlag(flag) then return true end
	end
	
	return false
end

return bitmask

To make a new bitmask do this:

local bitmask = require(path.to.bitmask)

local mask = bitmask.new("Flag1", "Flag2", "Flag3", "Etc") 

The strings can be any string you want. Additionally at the end you can provide a number which is the initial mask value. I recommend using the 0b syntax which allows you to type it in binary. Remember, it’s reversed, so the last flag "Etc" is actually binary 0b1000, and "Flag1" is represented as 0b0001 (or just 1).

Then you have multiple functions on the bitmask itself to check which flags are on, toggle flags, and enable/disable flags.

You can override the mask with mask:SetMask(newMask)

You can get the current mask as a number with mask:GetMask()

You can toggle a flag with mask:ToggleFlag("Flag") this will flip the current state, so if it’s currently enabled, it will disable it, and vice versa.

You can enable a flag with mask:EnableFlag("Flag") and disable it with mask:DisableFlag("Flag")

You can check if a mask holds flag(s) in 3 ways:

  • Using mask:HasFlag("Flag") will check for a single flag. If it’s enabled, it will return true, false otherwise.
  • Using mask:HasAllFlags("Flag1", "Flag2", "Etc") will check if the bitmask has all of the flags are enabled.
  • And mask:HasAnyFlags("Flag1", "Flag2", "Etc") will check if the bitmask has any of the flags listed.

Feel free to suggest new functions for more control, but bitmasks are a simple, yet powerful tool so I don’t want anything too overly ambitious to creep into the code.

4 Likes

What can this be used for? Simplifying datastore block properties would be cool as it’s binary.

Imagine you have a wall of light switches, that’s basically a bitmask. If the light is on, the value is a 1, otherwise it’s 0. You can use this to pack multiple true/false values into a single object. I mainly made this because I use it often in c# for my enums. An example I can think off the top of my head is an attachment system. You can have each flag be an attachment and whether it’s equipped or not. Or other upgrades. There’s plenty of potential uses for bitmasks, there’s also plenty of ways to do exactly what bitmasks do in a more readable fashion. Making it flag-based does help with that readability aspect as it abstracts everything and does it all for you and all you need to do is provide the flags. GetMask and SetMask exist for saving purposes so persistence is possible.

1 Like