Skip to main content

arkhe_kernel/runtime/
view.rs

1//! `InstanceView` — read-only public projection of one `Instance`.
2//!
3//! L1 applications need to *read* kernel state (entity rosters, component
4//! bytes, post lists, etc.) without holding `&mut` to the kernel and
5//! without reaching into private internals. `InstanceView<'a>` is the
6//! single sanctioned read surface; it borrows `&self` from the kernel,
7//! so callers cannot concurrently mutate while a view is live.
8//!
9//! No write methods exist on this struct; `&mut Instance` never escapes.
10
11use bytes::Bytes;
12
13use crate::abi::{EntityId, InstanceId, TypeCode};
14use crate::state::{EntityMeta, Instance};
15
16/// Read-only borrow of one instance's state.
17pub struct InstanceView<'a> {
18    pub(crate) instance: &'a Instance,
19}
20
21impl<'a> InstanceView<'a> {
22    /// `InstanceId` of the viewed instance.
23    pub fn id(&self) -> InstanceId {
24        self.instance.id()
25    }
26
27    /// Number of entities currently registered.
28    pub fn entity_count(&self) -> usize {
29        self.instance.entities_len()
30    }
31
32    /// Total component count across all entities.
33    pub fn component_count(&self) -> usize {
34        self.instance.components_len()
35    }
36
37    /// Logical local tick of the instance.
38    pub fn local_tick(&self) -> u64 {
39        self.instance.local_tick()
40    }
41
42    /// Per-entity metadata. `None` if the entity is not registered.
43    pub fn entity_meta(&self, entity: EntityId) -> Option<&'a EntityMeta> {
44        self.instance.entity_meta(entity)
45    }
46
47    /// Component bytes for an `(entity, type_code)` pair. `None` if
48    /// the component is not attached to that entity.
49    pub fn component(&self, entity: EntityId, type_code: TypeCode) -> Option<&'a Bytes> {
50        self.instance.component(entity, type_code)
51    }
52
53    /// Iterate every `(entity, &meta)` in ascending `EntityId` order
54    /// (A23 canonical, supplied by `BTreeMap` iteration).
55    pub fn entities(&self) -> impl Iterator<Item = (EntityId, &'a EntityMeta)> + 'a {
56        self.instance.entities_iter()
57    }
58
59    /// Iterate every `(entity, &bytes)` whose component matches
60    /// `type_code`. Useful for "list all posts" / "list all rolls"
61    /// style queries without exposing the raw component map.
62    /// Order: ascending `EntityId` (A23 canonical).
63    pub fn components_by_type(
64        &self,
65        type_code: TypeCode,
66    ) -> impl Iterator<Item = (EntityId, &'a Bytes)> + 'a {
67        self.instance.components_by_type_iter(type_code)
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use crate::abi::{CapabilityMask, EntityId, Principal, Tick, TypeCode};
75    use crate::state::traits::_sealed::Sealed;
76    use crate::state::{ActionCompute, ActionContext, ActionDeriv, InstanceConfig, Op};
77    use crate::Kernel;
78    use serde::{Deserialize, Serialize};
79
80    /// Test action: spawns entities `[1..=count]` and attaches the same
81    /// component bytes (under `type_code`) to each.
82    #[derive(Serialize, Deserialize)]
83    struct SpawnManyAction {
84        count: u64,
85        type_code: u32,
86        size: u64,
87    }
88    impl Sealed for SpawnManyAction {}
89    impl ActionDeriv for SpawnManyAction {
90        const TYPE_CODE: TypeCode = TypeCode(900);
91        const SCHEMA_VERSION: u32 = 1;
92    }
93    impl ActionCompute for SpawnManyAction {
94        fn compute(&self, _ctx: &ActionContext) -> Vec<Op> {
95            let mut ops = Vec::with_capacity((self.count * 2) as usize);
96            for n in 1..=self.count {
97                let entity = EntityId::new(n).unwrap();
98                ops.push(Op::SpawnEntity {
99                    id: entity,
100                    owner: Principal::System,
101                });
102                ops.push(Op::SetComponent {
103                    entity,
104                    type_code: TypeCode(self.type_code),
105                    bytes: Bytes::from(vec![0xAB; self.size as usize]),
106                    size: self.size,
107                });
108            }
109            ops
110        }
111    }
112
113    fn submit(k: &mut Kernel, inst: InstanceId, action: &SpawnManyAction) {
114        use crate::state::Action;
115        let bytes = Action::canonical_bytes(action);
116        k.submit(
117            inst,
118            Principal::System,
119            None,
120            Tick(0),
121            SpawnManyAction::TYPE_CODE,
122            bytes,
123        )
124        .expect("submit ok");
125    }
126
127    fn boot() -> (Kernel, InstanceId) {
128        let mut k = Kernel::new();
129        k.register_action::<SpawnManyAction>();
130        let inst = k.create_instance(InstanceConfig::default());
131        (k, inst)
132    }
133
134    #[test]
135    fn view_none_for_missing_instance() {
136        let k = Kernel::new();
137        let bogus = InstanceId::new(99).unwrap();
138        assert!(k.instance_view(bogus).is_none());
139    }
140
141    #[test]
142    fn view_reflects_entity_count() {
143        let (mut k, inst) = boot();
144        submit(
145            &mut k,
146            inst,
147            &SpawnManyAction {
148                count: 1,
149                type_code: 7,
150                size: 10,
151            },
152        );
153        let _ = k.step(Tick(0), CapabilityMask::SYSTEM);
154
155        let view = k.instance_view(inst).expect("view present");
156        assert_eq!(view.id(), inst);
157        assert_eq!(view.entity_count(), 1);
158        assert_eq!(view.component_count(), 1);
159    }
160
161    #[test]
162    fn view_component_bytes_match_set() {
163        let (mut k, inst) = boot();
164        submit(
165            &mut k,
166            inst,
167            &SpawnManyAction {
168                count: 1,
169                type_code: 7,
170                size: 4,
171            },
172        );
173        let _ = k.step(Tick(0), CapabilityMask::SYSTEM);
174
175        let view = k.instance_view(inst).expect("view present");
176        let comp = view
177            .component(EntityId::new(1).unwrap(), TypeCode(7))
178            .expect("component present");
179        assert_eq!(comp.as_ref(), &[0xAB, 0xAB, 0xAB, 0xAB]);
180    }
181
182    #[test]
183    fn view_entities_iter_ascending() {
184        let (mut k, inst) = boot();
185        submit(
186            &mut k,
187            inst,
188            &SpawnManyAction {
189                count: 3,
190                type_code: 7,
191                size: 1,
192            },
193        );
194        let _ = k.step(Tick(0), CapabilityMask::SYSTEM);
195
196        let view = k.instance_view(inst).expect("view present");
197        let ids: Vec<u64> = view.entities().map(|(id, _)| id.get()).collect();
198        assert_eq!(ids, vec![1, 2, 3]);
199
200        // Spot-check meta reflects the producing principal/tick.
201        let meta = view.entity_meta(EntityId::new(2).unwrap()).expect("meta");
202        assert_eq!(meta.owner, Principal::System);
203        assert_eq!(meta.created, Tick(0));
204    }
205
206    #[test]
207    fn view_components_by_type_filter() {
208        // Spawn 2 entities, attach two different TypeCodes — query one.
209        let (mut k, inst) = boot();
210        submit(
211            &mut k,
212            inst,
213            &SpawnManyAction {
214                count: 2,
215                type_code: 7,
216                size: 1,
217            },
218        );
219        let _ = k.step(Tick(0), CapabilityMask::SYSTEM);
220        // Second action: same entities, different type_code (8).
221        submit(
222            &mut k,
223            inst,
224            &SpawnManyAction {
225                count: 2,
226                type_code: 8,
227                size: 1,
228            },
229        );
230        let _ = k.step(Tick(0), CapabilityMask::SYSTEM);
231
232        let view = k.instance_view(inst).expect("view present");
233        // 2 entities × 2 type_codes = 4 components total.
234        assert_eq!(view.component_count(), 4);
235
236        let tc7: Vec<u64> = view
237            .components_by_type(TypeCode(7))
238            .map(|(eid, _)| eid.get())
239            .collect();
240        assert_eq!(tc7, vec![1, 2]);
241
242        let tc8: Vec<u64> = view
243            .components_by_type(TypeCode(8))
244            .map(|(eid, _)| eid.get())
245            .collect();
246        assert_eq!(tc8, vec![1, 2]);
247    }
248
249    #[test]
250    fn view_local_tick_updates_after_step() {
251        let (mut k, inst) = boot();
252        submit(
253            &mut k,
254            inst,
255            &SpawnManyAction {
256                count: 1,
257                type_code: 7,
258                size: 1,
259            },
260        );
261        // Before step: tick is at 0.
262        let pre = k.instance_view(inst).unwrap().local_tick();
263        let _ = k.step(Tick(0), CapabilityMask::SYSTEM);
264        // The action does not advance local_tick (no Op::AdvanceTick
265        // exists) — the view reflects whatever apply_stage applied.
266        // Both readings should still resolve without panic; equality
267        // documents the current semantics.
268        let post = k.instance_view(inst).unwrap().local_tick();
269        assert_eq!(pre, post);
270    }
271}