"""
A module implementing bit flags and fields.
"""
# built-in
from typing import Optional as _Optional
from typing import cast as _cast
# third-party
from vcorelib.io.types import JsonObject as _JsonObject
# internal
from runtimepy.mixins.enum import EnumMixin as _EnumMixin
from runtimepy.mixins.regex import CHANNEL_PATTERN as _CHANNEL_PATTERN
from runtimepy.mixins.regex import RegexMixin as _RegexMixin
from runtimepy.primitives.int import UnsignedInt as _UnsignedInt
from runtimepy.registry.name import RegistryKey as _RegistryKey
from runtimepy.ui.controls import (
Controls,
Controlslike,
bit_slider,
normalize_controls,
)
[docs]
class BitFieldBase:
"""A simple bit-field implementation."""
def __init__(
self,
raw: _UnsignedInt,
index: int,
width: int,
commandable: bool = False,
description: str = None,
controls: Controlslike = None,
default: _Optional[int | bool | str] = None,
) -> None:
"""Initialize this bit-field."""
self.raw = raw
self.index = index
self.commandable = commandable
self.description = description
if controls:
controls = normalize_controls(controls)
self.controls: _Optional[Controls] = controls # type: ignore
self.default = default
# Compute a bit-mask for this field.
self.width = width
self.mask = (2**self.width) - 1
self.shifted_mask = self.mask << self.index
[docs]
def where_str(self) -> str:
"""
Get a simple string representing the bit locations this field occupies.
"""
result = ""
for idx in reversed(range(self.raw.size * 8)):
val = 1 << idx
if val & self.shifted_mask:
result += "^"
else:
result += "-"
return result
def __call__(self, val: int = None) -> int:
"""
Set (or get) the underlying value of this field. Return the actual
value of the field.
"""
# Apply the field mask.
result: int
if val is not None:
result = val & self.mask
# Get the underlying value and apply the new value.
self.raw.value = (self.raw.value & ~self.shifted_mask) | (
result << self.index
)
else:
result = _cast(
int, (self.raw.value & self.shifted_mask) >> self.index
)
return result
[docs]
def invert(self) -> int:
"""Invert the value of this field and return the result."""
return self(~self())
[docs]
class BitField(BitFieldBase, _RegexMixin, _EnumMixin):
"""A class managing a portion of an unsigned-integer primitive."""
name_regex = _CHANNEL_PATTERN
def __init__(
self,
name: str,
raw: _UnsignedInt,
index: int,
width: int,
enum: _RegistryKey = None,
commandable: bool = False,
description: str = None,
**kwargs,
) -> None:
"""Initialize this bit-field."""
super().__init__(
raw,
index,
width,
commandable=commandable,
description=description,
**kwargs,
)
# Verify bit-field parameters.
assert (
raw.kind.is_integer
), f"Can't create a bit field with {raw.kind}!"
assert (
index < raw.kind.bits
), f"Field can't start at {index} for {raw.kind}!"
assert (
width <= raw.kind.bits
), f"Field can't be {width}-bits wide for {raw.kind}!"
assert self.validate_name(name), f"Invalid name '{name}'!"
self.name = name
self._enum = enum
[docs]
def set_slider(self) -> "BitField":
"""Set slider controls for this bit field."""
assert self.controls is None
self.controls = bit_slider(self.width, False)
return self
[docs]
def asdict(self) -> _JsonObject:
"""Get this field as a dictionary."""
result: _JsonObject = {
"name": self.name,
"index": self.index,
"width": self.width,
"value": self(),
}
if self.is_enum:
result["enum"] = self.enum
if self.commandable:
result["commandable"] = True
if self.controls:
result["controls"] = self.controls # type: ignore
if self.default is not None:
result["default"] = self.default
return result
[docs]
class BitFlag(BitField):
"""A bit field that is always a single bit."""
def __init__(
self,
name: str,
raw: _UnsignedInt,
index: int,
enum: _RegistryKey = None,
commandable: bool = False,
description: str = None,
**kwargs,
) -> None:
"""Initialize this bit flag."""
super().__init__(
name,
raw,
index,
1,
enum=enum,
commandable=commandable,
description=description,
**kwargs,
)
[docs]
def clear(self) -> None:
"""Clear this field."""
self(val=0)
[docs]
def set(self, val: bool = True) -> None:
"""Set this flag."""
self(val=int(val))
[docs]
def get(self) -> bool:
"""Get the value of this flag."""
return bool(self())