58556d7281
ADDED: * netx.IsPub * encodingx/hexx Rest are mostly small corrections and docs
236 lines
5.8 KiB
Go
236 lines
5.8 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 - compared to a *single boolean*. But bitmasking lets you store a "boolean" AT EACH BIT boundary - it operates on
|
|
whether a bit in a byte/set of bytes at a given position is 0 or 1.
|
|
|
|
In other words:
|
|
|
|
type (
|
|
OptStruct struct {
|
|
A bool
|
|
B bool
|
|
C bool
|
|
D bool
|
|
E bool
|
|
F bool
|
|
G bool
|
|
H bool
|
|
}
|
|
)
|
|
|
|
One instance of OptStruct takes up *8 bytes* (*64 bits*).
|
|
|
|
As a uint8 bitmask, however, it takes up exactly *one byte/8 bits*:
|
|
|
|
type (
|
|
Opt uint8
|
|
)
|
|
|
|
const OptNone Opt = 0
|
|
const (
|
|
A Opt = 1 << iota // 1
|
|
B // 2
|
|
C // 4
|
|
D // 8
|
|
E // 16
|
|
F // 32
|
|
G // 64
|
|
H // 128
|
|
// Would overflow to 0:
|
|
// I
|
|
)
|
|
|
|
As shown, the size of an unsigned integer determines the number of bit-boundaried flags for that bitmask.
|
|
|
|
Which means on 32-bit platforms, a [MaskBit] can have up to 32 different flags but only occupies 4 bytes of memory.
|
|
|
|
On 64-bit platforms, a [MaskBit] can have up to 64 different flags but only occupies 8 bytes of memory.
|
|
|
|
If you tried to do that with a struct of booleans instead, that'd occupy 32 *bytes* and 64 *bytes* respectively.
|
|
|
|
In summary, a bitmask set occupies 1/8th the amount of memory as a set of equal number of booleans in Go.
|
|
|
|
You can, of course, extend this even further by defining meaningful "composites",
|
|
or OR'd-combination of flags (see the "Composite Flags" section below):
|
|
|
|
type Perms uint8
|
|
const NoPerms Perms = 0
|
|
const (
|
|
PermsRead Perms = 1 << iota
|
|
PermsList
|
|
PermsUpdate
|
|
PermsCreate
|
|
PermsDelete
|
|
PermsUpdateBulk
|
|
PermsCreateBulk
|
|
PermsDeleteBulk
|
|
|
|
// This makes it easy to add multiple permissions at once.
|
|
PermsCRUD Perms = PermsCreate | PermsRead | PermsUpdate | PermsDelete
|
|
PermsCRUDL Perms = PermsCRUD | PermsList
|
|
// And so forth.
|
|
)
|
|
|
|
"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 ZDoom used bitmasking for the [dmflags] value in its server configs,
|
|
and why *NIX platforms uses them for permission/type modes.
|
|
|
|
# Usage
|
|
|
|
To use this library, set constants like thus:
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"r00t2.io/goutils/bitmask"
|
|
)
|
|
|
|
const OPTNONE bitmask.MaskBit = 0
|
|
const (
|
|
OPT1 bitmask.MaskBit = 1 << iota
|
|
OPT2
|
|
OPT3
|
|
// ...
|
|
)
|
|
|
|
var (
|
|
MyMask *bitmask.MaskBit = bitmask.NewMaskBit()
|
|
)
|
|
|
|
func main() {
|
|
|
|
MyMask.AddFlag(OPT1)
|
|
MyMask.AddFlag(OPT3)
|
|
|
|
// This would print true.
|
|
fmt.Println(MyMask.HasFlag(OPT1))
|
|
|
|
// As would this:
|
|
fmt.Println(MyMask.HasFlag(OPT3))
|
|
|
|
// But this would print false:
|
|
fmt.Println(MyMask.HasFlag(OPT2))
|
|
}
|
|
|
|
# Technical Caveats
|
|
|
|
Targeting
|
|
|
|
When implementing, you should always set a "source" mask (e.g. 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)
|
|
userPermMask.HasFlag(bitmask.MaskBit(userPerms))
|
|
|
|
NOR:
|
|
userPermMask = PermsRead
|
|
if userPermMask.HasFlag(bitmask.MaskBit(userPerms)) {
|
|
// ...
|
|
}
|
|
|
|
This will be terribly, horribly wrong, cause incredibly unexpected results,
|
|
and quite possibly cause massive security issues. Don't do it.
|
|
|
|
Remember, [MaskBit.HasFlag] (and other methods) are for comparing/modifying an
|
|
*authoritative/concrete value* with one or more *attributes*, not the other way around.
|
|
|
|
Composite Flags
|
|
|
|
If you want to define a set of flags that are a combination
|
|
(a "composite", or OR'd set) of other flags, your inclination
|
|
would be to bitwise-OR them together:
|
|
|
|
const (
|
|
flagA bitmask.MaskBit = 1 << iota // 1
|
|
flagB // 2
|
|
flagC // 4
|
|
// ...
|
|
)
|
|
|
|
const (
|
|
flagAB bitmask.MaskBit = flagA | flagB // 3
|
|
)
|
|
|
|
Which is fine, and the correct approach.
|
|
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 would only return true if you did:
|
|
|
|
// ...
|
|
myMask.AddFlag(flagAB)
|
|
|
|
or:
|
|
|
|
// ...
|
|
myMask.AddFlag(flagA)
|
|
myMask.AddFlag(flagB)
|
|
|
|
Instead, if you want to see if a mask has *at least one flag of* a composite flag,
|
|
you can use [MaskBit.HasOneOf].
|
|
|
|
# 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]
|
|
|
|
[dmflags]: https://doomwiki.org/wiki/DMFlags
|
|
*/
|
|
package bitmask
|