Skip to main content

vyre_primitives/effects/
handler_compose.rs

1//! Effect-handler composition primitive (P-1.0-V1.2).
2//!
3//! Builds a single handler from two: the composed handler discharges
4//! the union of the two handlers' effect rows.
5//!
6//! Algebraic laws (proven by tests below):
7//!
8//! * **Associativity**: `compose(a, compose(b, c)) == compose(compose(a, b), c)`.
9//! * **Commutativity**: `compose(a, b) == compose(b, a)` (handlers are
10//!   modeled as effect rows; row-union is commutative).
11//! * **Identity**: `compose(a, identity) == a` where identity is the
12//!   empty-row handler.
13//! * **Idempotence on equal handlers**: `compose(a, a) == a`.
14//! * **Apply-compose distributivity**:
15//!   `handler_apply(row, compose(a, b)) ==
16//!    handler_apply(handler_apply(row, a), b)`.
17
18use super::handler_apply::{EffectRow, Handler};
19
20/// Compose two handlers. The result discharges every effect kind in
21/// either input handler.
22#[must_use]
23#[inline]
24pub const fn handler_compose(a: Handler, b: Handler) -> Handler {
25    Handler::from_row(EffectRow::from_bits(
26        a.handled().bits() | b.handled().bits(),
27    ))
28}
29
30#[cfg(test)]
31mod tests {
32    use super::super::handler_apply::{handler_apply, EffectKind, EffectRow, Handler};
33    use super::handler_compose;
34
35    fn handler_for(kinds: &[EffectKind]) -> Handler {
36        let mut row = EffectRow::empty();
37        for k in kinds {
38            row = row.union(EffectRow::single(*k));
39        }
40        Handler::from_row(row)
41    }
42
43    #[test]
44    fn composed_handler_discharges_union() {
45        let a = Handler::single(EffectKind::BufferWrite);
46        let b = Handler::single(EffectKind::Atomic);
47        let composed = handler_compose(a, b);
48        assert!(composed.handled().contains(EffectKind::BufferWrite));
49        assert!(composed.handled().contains(EffectKind::Atomic));
50        assert!(!composed.handled().contains(EffectKind::HostIo));
51    }
52
53    #[test]
54    fn compose_is_commutative() {
55        let a = Handler::single(EffectKind::BufferWrite);
56        let b = Handler::single(EffectKind::Atomic);
57        assert_eq!(handler_compose(a, b), handler_compose(b, a));
58    }
59
60    #[test]
61    fn compose_is_associative() {
62        let a = Handler::single(EffectKind::BufferWrite);
63        let b = Handler::single(EffectKind::Atomic);
64        let c = Handler::single(EffectKind::HostIo);
65        assert_eq!(
66            handler_compose(a, handler_compose(b, c)),
67            handler_compose(handler_compose(a, b), c)
68        );
69    }
70
71    #[test]
72    fn identity_left_neutral() {
73        let id = Handler::from_row(EffectRow::empty());
74        let h = Handler::single(EffectKind::BufferWrite);
75        assert_eq!(
76            handler_compose(id, h).handled().bits(),
77            EffectKind::BufferWrite.mask()
78        );
79    }
80
81    #[test]
82    fn identity_right_neutral() {
83        let id = Handler::from_row(EffectRow::empty());
84        let h = Handler::single(EffectKind::BufferWrite);
85        assert_eq!(
86            handler_compose(h, id).handled().bits(),
87            EffectKind::BufferWrite.mask()
88        );
89    }
90
91    #[test]
92    fn idempotent_on_equal() {
93        let h = handler_for(&[EffectKind::BufferWrite, EffectKind::Atomic]);
94        let mask = EffectKind::BufferWrite.mask() | EffectKind::Atomic.mask();
95        assert_eq!(handler_compose(h, h).handled().bits(), mask);
96    }
97
98    #[test]
99    fn apply_compose_equals_apply_apply() {
100        // handler_apply(row, compose(a, b)) ==
101        //   handler_apply(handler_apply(row, a), b)
102        let row = handler_for(&[
103            EffectKind::BufferWrite,
104            EffectKind::Atomic,
105            EffectKind::HostIo,
106        ])
107        .handled();
108        let a = Handler::single(EffectKind::BufferWrite);
109        let b = Handler::single(EffectKind::Atomic);
110        let lhs = handler_apply(row, handler_compose(a, b));
111        let rhs = handler_apply(handler_apply(row, a), b);
112        assert_eq!(lhs, rhs);
113    }
114
115    #[test]
116    fn composed_full_handler_discharges_full_row() {
117        let row = handler_for(&[
118            EffectKind::BufferWrite,
119            EffectKind::Atomic,
120            EffectKind::HostIo,
121            EffectKind::GpuDispatch,
122        ])
123        .handled();
124        let composed = handler_compose(
125            handler_for(&[EffectKind::BufferWrite, EffectKind::Atomic]),
126            handler_for(&[EffectKind::HostIo, EffectKind::GpuDispatch]),
127        );
128        assert!(handler_apply(row, composed).is_empty());
129    }
130}