Skip to main content

arkhe_kernel/runtime/
registry.rs

1//! `ActionRegistry` — TypeCode → deserializer fn-pointer table.
2//!
3//! Each registered Action is reachable through a monomorphic function
4//! pointer (discipline: no vtable on the deserialize path; vtable
5//! arises only at the single `compute_dyn` call inside the kernel scheduler
6//! tick).
7
8use std::collections::BTreeMap;
9
10use crate::abi::TypeCode;
11use crate::state::{Action, ActionContext, DeserializeError, Op};
12
13/// Object-safe wrapper exposing the subset of Action methods that Kernel
14/// dispatch needs. `impl<T: Action> ActionDyn for T` blanket-applies to
15/// every registered type. `canonical_bytes_dyn` is the test/snapshot
16/// round-trip surface (production callers ship with the deferred snapshot
17/// integration).
18pub(crate) trait ActionDyn: 'static {
19    #[cfg_attr(not(test), allow(dead_code))]
20    fn canonical_bytes_dyn(&self) -> Vec<u8>;
21    fn compute_dyn(&self, ctx: &ActionContext) -> Vec<Op>;
22}
23
24impl<T: Action> ActionDyn for T {
25    fn canonical_bytes_dyn(&self) -> Vec<u8> {
26        self.canonical_bytes()
27    }
28    fn compute_dyn(&self, ctx: &ActionContext) -> Vec<Op> {
29        self.compute(ctx)
30    }
31}
32
33pub(crate) type ActionDeserializer = fn(u32, &[u8]) -> Result<Box<dyn ActionDyn>, DeserializeError>;
34
35#[derive(Clone)]
36pub(crate) struct ActionRegistration {
37    pub schema_version: u32,
38    pub deserializer: ActionDeserializer,
39}
40
41#[derive(Default)]
42pub(crate) struct ActionRegistry {
43    by_type_code: BTreeMap<TypeCode, ActionRegistration>,
44}
45
46impl ActionRegistry {
47    pub(crate) fn new() -> Self {
48        Self::default()
49    }
50
51    pub(crate) fn register<A: Action>(&mut self) {
52        let reg = ActionRegistration {
53            schema_version: A::SCHEMA_VERSION,
54            deserializer: |version, bytes| {
55                A::from_bytes(version, bytes).map(|boxed| boxed as Box<dyn ActionDyn>)
56            },
57        };
58        self.by_type_code.insert(A::TYPE_CODE, reg);
59    }
60
61    pub(crate) fn get(&self, tc: TypeCode) -> Option<&ActionRegistration> {
62        self.by_type_code.get(&tc)
63    }
64
65    #[cfg_attr(not(test), allow(dead_code))]
66    pub(crate) fn len(&self) -> usize {
67        self.by_type_code.len()
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use crate::abi::Principal;
75    use crate::state::traits::_sealed::Sealed;
76    use crate::state::{ActionCompute, ActionDeriv};
77    use serde::{Deserialize, Serialize};
78
79    #[derive(Serialize, Deserialize)]
80    struct ProbeAction;
81    impl Sealed for ProbeAction {}
82    impl ActionDeriv for ProbeAction {
83        const TYPE_CODE: TypeCode = TypeCode(0xAB_CD);
84        const SCHEMA_VERSION: u32 = 1;
85    }
86    impl ActionCompute for ProbeAction {
87        fn compute(&self, _ctx: &ActionContext) -> Vec<Op> {
88            Vec::new()
89        }
90    }
91
92    #[test]
93    fn registry_register_and_lookup() {
94        let mut r = ActionRegistry::new();
95        assert_eq!(r.len(), 0);
96        r.register::<ProbeAction>();
97        assert_eq!(r.len(), 1);
98        let reg = r.get(TypeCode(0xAB_CD)).expect("registered");
99        assert_eq!(reg.schema_version, 1);
100    }
101
102    #[test]
103    fn registry_deserializer_roundtrip() {
104        let mut r = ActionRegistry::new();
105        r.register::<ProbeAction>();
106        let reg = r.get(TypeCode(0xAB_CD)).unwrap();
107        let action = (reg.deserializer)(1, &[]).expect("deserialize");
108        let _bytes = action.canonical_bytes_dyn();
109        // Smoke: compute returns empty Op vec for probe.
110        let inst = crate::state::Instance::new(
111            crate::abi::InstanceId::new(1).unwrap(),
112            crate::state::InstanceConfig::default(),
113        );
114        let ctx = ActionContext::new(None, crate::abi::Tick(0), inst.id(), &inst);
115        assert!(action.compute_dyn(&ctx).is_empty());
116        let _ = Principal::System;
117    }
118
119    #[test]
120    fn registry_unknown_type_code_returns_none() {
121        let r = ActionRegistry::new();
122        assert!(r.get(TypeCode(0xDEAD)).is_none());
123    }
124}