#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum EffectKind {
BufferWrite,
Atomic,
HostIo,
GpuDispatch,
Barrier,
AsyncLoad,
Trap,
}
impl EffectKind {
#[must_use]
#[inline]
pub const fn bit(self) -> u32 {
match self {
Self::BufferWrite => 0,
Self::Atomic => 1,
Self::HostIo => 2,
Self::GpuDispatch => 3,
Self::Barrier => 4,
Self::AsyncLoad => 5,
Self::Trap => 6,
}
}
#[must_use]
#[inline]
pub const fn mask(self) -> u32 {
1u32 << self.bit()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct EffectRow(u32);
impl EffectRow {
#[must_use]
#[inline]
pub const fn empty() -> Self {
Self(0)
}
#[must_use]
#[inline]
pub const fn from_bits(bits: u32) -> Self {
Self(bits)
}
#[must_use]
#[inline]
pub const fn single(kind: EffectKind) -> Self {
Self(kind.mask())
}
#[must_use]
#[inline]
pub const fn bits(self) -> u32 {
self.0
}
#[must_use]
#[inline]
pub const fn contains(self, kind: EffectKind) -> bool {
self.0 & kind.mask() != 0
}
#[must_use]
#[inline]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
#[must_use]
#[inline]
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Handler {
handled: EffectRow,
}
impl Handler {
#[must_use]
#[inline]
pub const fn from_row(handled: EffectRow) -> Self {
Self { handled }
}
#[must_use]
#[inline]
pub const fn single(kind: EffectKind) -> Self {
Self {
handled: EffectRow::single(kind),
}
}
#[must_use]
#[inline]
pub const fn handled(self) -> EffectRow {
self.handled
}
}
#[must_use]
#[inline]
pub const fn handler_apply(row: EffectRow, handler: Handler) -> EffectRow {
EffectRow(row.0 & !handler.handled.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_row_stays_empty() {
let h = Handler::single(EffectKind::BufferWrite);
assert_eq!(handler_apply(EffectRow::empty(), h), EffectRow::empty());
}
#[test]
fn handler_discharges_its_kind() {
let row = EffectRow::single(EffectKind::BufferWrite);
let h = Handler::single(EffectKind::BufferWrite);
assert!(handler_apply(row, h).is_empty());
}
#[test]
fn handler_passes_through_other_kinds() {
let row = EffectRow::single(EffectKind::Atomic);
let h = Handler::single(EffectKind::BufferWrite);
assert_eq!(handler_apply(row, h), row);
}
#[test]
fn identity_handler_preserves_every_row() {
let id = Handler::from_row(EffectRow::empty());
for kind in [
EffectKind::BufferWrite,
EffectKind::Atomic,
EffectKind::HostIo,
EffectKind::GpuDispatch,
EffectKind::Barrier,
EffectKind::AsyncLoad,
EffectKind::Trap,
] {
let row = EffectRow::single(kind);
assert_eq!(handler_apply(row, id), row);
}
}
#[test]
fn handler_apply_is_idempotent() {
let row =
EffectRow::single(EffectKind::BufferWrite).union(EffectRow::single(EffectKind::Atomic));
let h = Handler::single(EffectKind::BufferWrite);
let once = handler_apply(row, h);
let twice = handler_apply(once, h);
assert_eq!(once, twice);
}
#[test]
fn multi_effect_row_partial_discharge() {
let row =
EffectRow::single(EffectKind::BufferWrite).union(EffectRow::single(EffectKind::Atomic));
let h = Handler::single(EffectKind::BufferWrite);
let residual = handler_apply(row, h);
assert!(!residual.contains(EffectKind::BufferWrite));
assert!(residual.contains(EffectKind::Atomic));
}
#[test]
fn full_handler_discharges_full_row() {
let row = EffectRow::single(EffectKind::BufferWrite)
.union(EffectRow::single(EffectKind::Atomic))
.union(EffectRow::single(EffectKind::HostIo));
let h = Handler::from_row(row);
assert!(handler_apply(row, h).is_empty());
}
#[test]
fn distinct_kinds_have_distinct_bits() {
let bits: Vec<u32> = [
EffectKind::BufferWrite,
EffectKind::Atomic,
EffectKind::HostIo,
EffectKind::GpuDispatch,
EffectKind::Barrier,
EffectKind::AsyncLoad,
EffectKind::Trap,
]
.iter()
.map(|k| k.bit())
.collect();
for i in 0..bits.len() {
for j in (i + 1)..bits.len() {
assert_ne!(bits[i], bits[j], "kinds {i} and {j} share a bit");
}
}
}
#[test]
fn from_bits_round_trip() {
let raw = 0b0010_1011u32;
let row = EffectRow::from_bits(raw);
assert_eq!(row.bits(), raw);
assert!(row.contains(EffectKind::BufferWrite));
assert!(row.contains(EffectKind::Atomic));
assert!(!row.contains(EffectKind::HostIo));
assert!(row.contains(EffectKind::GpuDispatch));
assert!(!row.contains(EffectKind::Barrier));
assert!(row.contains(EffectKind::AsyncLoad));
assert!(!row.contains(EffectKind::Trap));
}
}