
FIXED: * More clear docs for bitmask * Resolved potential issue for using PriorityAll in logging.logPrio.HasFlag.
163 lines
4.3 KiB
Go
163 lines
4.3 KiB
Go
/*
|
|
Package bitmask handles a flag-like opt/bitmask system.
|
|
|
|
See https://yourbasic.org/golang/bitmask-flag-set-clear/ for basic information on what bitmasks are and why they're useful.
|
|
|
|
Specifically, in the case of Go, they allow you to essentially manage many, many, many "booleans" as part of a single value.
|
|
|
|
A single bool value in Go takes up 8 bits/1 byte, unavoidably.
|
|
|
|
However, a [bitmask.MaskBit] is backed by a uint which (depending on your platform) is either 32 bits/4 bytes or 64 bits/8 bytes.
|
|
|
|
"But wait, that takes up more memory though!"
|
|
|
|
Yep, but bitmasking lets you store a "boolean" AT EACH BIT - it operates on
|
|
whether a bit in a byte/set of bytes at a given position is 0 or 1.
|
|
|
|
Which means on 32-bit platforms, a [MaskBit] can have up to 4294967295 "booleans" in a single value (0 to (2^32)-1).
|
|
|
|
On 64-bit platforms, a [MaskBit] can have up to 18446744073709551615 "booleans" in a single value (0 to (2^64)-1).
|
|
|
|
If you tried to do that with Go bool values, that'd take up 4294967295 bytes (4 GiB)
|
|
or 18446744073709551615 bytes (16 EiB - yes, that's [exbibytes]) of RAM for 32-bit/64-bit platforms respectively.
|
|
|
|
"But that has to be so slow to unpack that!"
|
|
|
|
Nope. It's not using compression or anything, the CPU is just comparing bit "A" vs. bit "B" 32/64 times. That's super easy work for a CPU.
|
|
|
|
There's a reason Doom used bitmasking for the "dmflags" value in its server configs.
|
|
|
|
# Usage
|
|
|
|
To use this library, set constants like thus:
|
|
|
|
package main
|
|
|
|
import (
|
|
"r00t2.io/goutils/bitmask"
|
|
)
|
|
|
|
const OPTNONE bitmask.MaskBit = 0
|
|
const (
|
|
OPT1 bitmask.MaskBit = 1 << iota
|
|
OPT2
|
|
OPT3
|
|
// ...
|
|
)
|
|
|
|
var MyMask *bitmask.MaskBit
|
|
|
|
func main() {
|
|
MyMask = bitmask.NewMaskBit()
|
|
|
|
MyMask.AddFlag(OPT1)
|
|
MyMask.AddFlag(OPT3)
|
|
|
|
_ = MyMask
|
|
}
|
|
|
|
This would return true:
|
|
|
|
MyMask.HasFlag(OPT1)
|
|
|
|
As would this:
|
|
|
|
MyMask.HasFlag(OPT3)
|
|
|
|
But this would return false:
|
|
|
|
MyMask.HasFlag(OPT2)
|
|
|
|
# Technical Caveats
|
|
|
|
TARGETING
|
|
|
|
When implementing, you should always set MyMask (from Usage section above) as the actual value.
|
|
For example, if you are checking a permissions set for a user that has the value, say, 6
|
|
|
|
var userPerms uint = 6 // 0x0000000000000006
|
|
|
|
and your library has the following permission bits defined:
|
|
|
|
const PermsNone bitmask.MaskBit = 0
|
|
const (
|
|
PermsList bitmask.MaskBit = 1 << iota // 1
|
|
PermsRead // 2
|
|
PermsWrite // 4
|
|
PermsExec // 8
|
|
PermsAdmin // 16
|
|
)
|
|
|
|
And you want to see if the user has the PermsRead flag set, you would do:
|
|
|
|
userPermMask = bitmask.NewMaskBitExplicit(userPerms)
|
|
if userPermMask.HasFlag(PermsRead) {
|
|
// ...
|
|
}
|
|
|
|
NOT:
|
|
|
|
userPermMask = bitmask.NewMaskBitExplicit(PermsRead)
|
|
// Nor:
|
|
// userPermMask = PermsRead
|
|
if userPermMask.HasFlag(userPerms) {
|
|
// ...
|
|
}
|
|
|
|
This will be terribly, horribly wrong, cause incredibly unexpected results,
|
|
and quite possibly cause massive security issues. Don't do it.
|
|
|
|
COMPOSITES
|
|
|
|
If you want to define a set of flags that are a combination of other flags,
|
|
your inclination would be to bitwise-OR them together:
|
|
|
|
const (
|
|
flagA bitmask.MaskBit = 1 << iota // 1
|
|
flagB // 2
|
|
)
|
|
|
|
const (
|
|
flagAB bitmask.MaskBit = flagA | flagB // 3
|
|
)
|
|
|
|
Which is fine and dandy. But if you then have:
|
|
|
|
var myMask *bitmask.MaskBit = bitmask.NewMaskBit()
|
|
|
|
myMask.AddFlag(flagA)
|
|
|
|
You may expect this call to [MaskBit.HasFlag]:
|
|
|
|
myMask.HasFlag(flagAB)
|
|
|
|
to be true, since flagA is "in" flagAB.
|
|
It will return false - HasFlag does strict comparisons.
|
|
It will only return true if you then ALSO do:
|
|
|
|
// This would require setting flagA first.
|
|
// The order of setting flagA/flagB doesn't matter,
|
|
// but you must have both set for HasFlag(flagAB) to return true.
|
|
myMask.AddFlag(flagB)
|
|
|
|
or if you do:
|
|
|
|
// This can be done with or without additionally setting flagA.
|
|
myMask.AddFlag(flagAB)
|
|
|
|
Instead, if you want to see if a mask has membership within a composite flag,
|
|
you can use [MaskBit.IsOneOf].
|
|
|
|
# Other Options
|
|
|
|
If you need something with more flexibility (as always, at the cost of complexity),
|
|
you may be interested in one of the following libraries:
|
|
|
|
* [github.com/alvaroloes/enumer]
|
|
* [github.com/abice/go-enum]
|
|
* [github.com/jeffreyrichter/enum/enum]
|
|
|
|
[exbibytes]: https://simple.wikipedia.org/wiki/Exbibyte
|
|
*/
|
|
package bitmask
|