v1.16.9
ADDED: * netx.IsPub * encodingx/hexx Rest are mostly small corrections and docs
This commit is contained in:
+117
-44
@@ -11,21 +11,84 @@ However, a [bitmask.MaskBit] is backed by a uint which (depending on your platfo
|
||||
|
||||
"But wait, that takes up more memory though!"
|
||||
|
||||
Yep, but bitmasking lets you store a "boolean" AT EACH BIT - it operates on
|
||||
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.
|
||||
|
||||
Which means on 32-bit platforms, a [MaskBit] can have up to 4294967295 "booleans" in a single value (0 to (2^32)-1).
|
||||
In other words:
|
||||
|
||||
On 64-bit platforms, a [MaskBit] can have up to 18446744073709551615 "booleans" in a single value (0 to (2^64)-1).
|
||||
type (
|
||||
OptStruct struct {
|
||||
A bool
|
||||
B bool
|
||||
C bool
|
||||
D bool
|
||||
E bool
|
||||
F bool
|
||||
G bool
|
||||
H bool
|
||||
}
|
||||
)
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -34,6 +97,8 @@ To use this library, set constants like thus:
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"r00t2.io/goutils/bitmask"
|
||||
)
|
||||
|
||||
@@ -45,34 +110,32 @@ To use this library, set constants like thus:
|
||||
// ...
|
||||
)
|
||||
|
||||
var MyMask *bitmask.MaskBit
|
||||
var (
|
||||
MyMask *bitmask.MaskBit = bitmask.NewMaskBit()
|
||||
)
|
||||
|
||||
func main() {
|
||||
MyMask = bitmask.NewMaskBit()
|
||||
|
||||
MyMask.AddFlag(OPT1)
|
||||
MyMask.AddFlag(OPT3)
|
||||
|
||||
_ = MyMask
|
||||
// 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))
|
||||
}
|
||||
|
||||
This would return true:
|
||||
|
||||
MyMask.HasFlag(OPT1)
|
||||
|
||||
As would this:
|
||||
|
||||
MyMask.HasFlag(OPT3)
|
||||
|
||||
But this would return false:
|
||||
|
||||
MyMask.HasFlag(OPT2)
|
||||
|
||||
# Technical Caveats
|
||||
|
||||
TARGETING
|
||||
Targeting
|
||||
|
||||
When implementing, you should always set a "source" mask (e.g. MyMask, from Usage section above)
|
||||
as the actual value.
|
||||
|
||||
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
|
||||
@@ -88,7 +151,7 @@ and your library has the following permission bits defined:
|
||||
PermsAdmin // 16
|
||||
)
|
||||
|
||||
And you want to see if the user has the PermsRead flag set, you would do:
|
||||
and you want to see if the user has the PermsRead flag set, you would do:
|
||||
|
||||
userPermMask = bitmask.NewMaskBitExplicit(userPerms)
|
||||
if userPermMask.HasFlag(PermsRead) {
|
||||
@@ -98,55 +161,65 @@ And you want to see if the user has the PermsRead flag set, you would do:
|
||||
NOT:
|
||||
|
||||
userPermMask = bitmask.NewMaskBitExplicit(PermsRead)
|
||||
// Nor:
|
||||
// userPermMask = PermsRead
|
||||
if userPermMask.HasFlag(userPerms) {
|
||||
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.
|
||||
|
||||
COMPOSITES
|
||||
Remember, [MaskBit.HasFlag] (and other methods) are for comparing/modifying an
|
||||
*authoritative/concrete value* with one or more *attributes*, not the other way around.
|
||||
|
||||
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:
|
||||
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 dandy. But if you then have:
|
||||
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]:
|
||||
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)
|
||||
It would only return true if you did:
|
||||
|
||||
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].
|
||||
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
|
||||
|
||||
@@ -157,6 +230,6 @@ you may be interested in one of the following libraries:
|
||||
* [github.com/abice/go-enum]
|
||||
* [github.com/jeffreyrichter/enum/enum]
|
||||
|
||||
[exbibytes]: https://simple.wikipedia.org/wiki/Exbibyte
|
||||
[dmflags]: https://doomwiki.org/wiki/DMFlags
|
||||
*/
|
||||
package bitmask
|
||||
|
||||
Reference in New Issue
Block a user