vyre_primitives/effects/
handler_apply.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15#[non_exhaustive]
16pub enum EffectKind {
17 BufferWrite,
19 Atomic,
21 HostIo,
23 GpuDispatch,
25 Barrier,
27 AsyncLoad,
30 Trap,
32}
33
34impl EffectKind {
35 #[must_use]
37 #[inline]
38 pub const fn bit(self) -> u32 {
39 match self {
40 Self::BufferWrite => 0,
41 Self::Atomic => 1,
42 Self::HostIo => 2,
43 Self::GpuDispatch => 3,
44 Self::Barrier => 4,
45 Self::AsyncLoad => 5,
46 Self::Trap => 6,
47 }
48 }
49
50 #[must_use]
52 #[inline]
53 pub const fn mask(self) -> u32 {
54 1u32 << self.bit()
55 }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
61pub struct EffectRow(u32);
62
63impl EffectRow {
64 #[must_use]
66 #[inline]
67 pub const fn empty() -> Self {
68 Self(0)
69 }
70
71 #[must_use]
73 #[inline]
74 pub const fn from_bits(bits: u32) -> Self {
75 Self(bits)
76 }
77
78 #[must_use]
80 #[inline]
81 pub const fn single(kind: EffectKind) -> Self {
82 Self(kind.mask())
83 }
84
85 #[must_use]
87 #[inline]
88 pub const fn bits(self) -> u32 {
89 self.0
90 }
91
92 #[must_use]
94 #[inline]
95 pub const fn contains(self, kind: EffectKind) -> bool {
96 self.0 & kind.mask() != 0
97 }
98
99 #[must_use]
101 #[inline]
102 pub const fn is_empty(self) -> bool {
103 self.0 == 0
104 }
105
106 #[must_use]
108 #[inline]
109 pub const fn union(self, other: Self) -> Self {
110 Self(self.0 | other.0)
111 }
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
117pub struct Handler {
118 handled: EffectRow,
119}
120
121impl Handler {
122 #[must_use]
124 #[inline]
125 pub const fn from_row(handled: EffectRow) -> Self {
126 Self { handled }
127 }
128
129 #[must_use]
131 #[inline]
132 pub const fn single(kind: EffectKind) -> Self {
133 Self {
134 handled: EffectRow::single(kind),
135 }
136 }
137
138 #[must_use]
140 #[inline]
141 pub const fn handled(self) -> EffectRow {
142 self.handled
143 }
144}
145
146#[must_use]
153#[inline]
154pub const fn handler_apply(row: EffectRow, handler: Handler) -> EffectRow {
155 EffectRow(row.0 & !handler.handled.0)
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn empty_row_stays_empty() {
164 let h = Handler::single(EffectKind::BufferWrite);
165 assert_eq!(handler_apply(EffectRow::empty(), h), EffectRow::empty());
166 }
167
168 #[test]
169 fn handler_discharges_its_kind() {
170 let row = EffectRow::single(EffectKind::BufferWrite);
171 let h = Handler::single(EffectKind::BufferWrite);
172 assert!(handler_apply(row, h).is_empty());
173 }
174
175 #[test]
176 fn handler_passes_through_other_kinds() {
177 let row = EffectRow::single(EffectKind::Atomic);
178 let h = Handler::single(EffectKind::BufferWrite);
179 assert_eq!(handler_apply(row, h).bits(), EffectKind::Atomic.mask());
180 }
181
182 #[test]
183 fn identity_handler_preserves_every_row() {
184 let id = Handler::from_row(EffectRow::empty());
185 for kind in [
186 EffectKind::BufferWrite,
187 EffectKind::Atomic,
188 EffectKind::HostIo,
189 EffectKind::GpuDispatch,
190 EffectKind::Barrier,
191 EffectKind::AsyncLoad,
192 EffectKind::Trap,
193 ] {
194 let row = EffectRow::single(kind);
195 assert_eq!(handler_apply(row, id).bits(), kind.mask());
196 }
197 }
198
199 #[test]
200 fn handler_apply_is_idempotent() {
201 let row =
202 EffectRow::single(EffectKind::BufferWrite).union(EffectRow::single(EffectKind::Atomic));
203 let h = Handler::single(EffectKind::BufferWrite);
204 let once = handler_apply(row, h);
205 let twice = handler_apply(once, h);
206 assert_eq!(once, twice);
207 }
208
209 #[test]
210 fn multi_effect_row_partial_discharge() {
211 let row =
212 EffectRow::single(EffectKind::BufferWrite).union(EffectRow::single(EffectKind::Atomic));
213 let h = Handler::single(EffectKind::BufferWrite);
214 let residual = handler_apply(row, h);
215 assert!(!residual.contains(EffectKind::BufferWrite));
216 assert!(residual.contains(EffectKind::Atomic));
217 }
218
219 #[test]
220 fn full_handler_discharges_full_row() {
221 let row = EffectRow::single(EffectKind::BufferWrite)
222 .union(EffectRow::single(EffectKind::Atomic))
223 .union(EffectRow::single(EffectKind::HostIo));
224 let h = Handler::from_row(row);
225 assert!(handler_apply(row, h).is_empty());
226 }
227
228 #[test]
229 fn distinct_kinds_have_distinct_bits() {
230 let bits: Vec<u32> = [
231 EffectKind::BufferWrite,
232 EffectKind::Atomic,
233 EffectKind::HostIo,
234 EffectKind::GpuDispatch,
235 EffectKind::Barrier,
236 EffectKind::AsyncLoad,
237 EffectKind::Trap,
238 ]
239 .iter()
240 .map(|k| k.bit())
241 .collect();
242 for i in 0..bits.len() {
243 for j in (i + 1)..bits.len() {
244 assert_ne!(bits[i], bits[j], "kinds {i} and {j} share a bit");
245 }
246 }
247 }
248
249 #[test]
250 fn from_bits_round_trip() {
251 let raw = 0b0010_1011u32;
255 let row = EffectRow::from_bits(raw);
256 assert_eq!(row.bits(), raw);
257 assert!(row.contains(EffectKind::BufferWrite));
258 assert!(row.contains(EffectKind::Atomic));
259 assert!(!row.contains(EffectKind::HostIo));
260 assert!(row.contains(EffectKind::GpuDispatch));
261 assert!(!row.contains(EffectKind::Barrier));
262 assert!(row.contains(EffectKind::AsyncLoad));
263 assert!(!row.contains(EffectKind::Trap));
264 }
265}