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            CapabilityMask::SYSTEM,
121            Tick(0),
122            SpawnManyAction::TYPE_CODE,
123            bytes,
124        )
125        .expect("submit ok");
126    }
127
128    fn boot() -> (Kernel, InstanceId) {
129        let mut k = Kernel::new();
130        k.register_action::<SpawnManyAction>();
131        let inst = k.create_instance(InstanceConfig::default());
132        (k, inst)
133    }
134
135    #[test]
136    fn view_none_for_missing_instance() {
137        let k = Kernel::new();
138        let bogus = InstanceId::new(99).unwrap();
139        assert!(k.instance_view(bogus).is_none());
140    }
141
142    #[test]
143    fn view_reflects_entity_count() {
144        let (mut k, inst) = boot();
145        submit(
146            &mut k,
147            inst,
148            &SpawnManyAction {
149                count: 1,
150                type_code: 7,
151                size: 10,
152            },
153        );
154        let _ = k.step(Tick(0), CapabilityMask::SYSTEM);
155
156        let view = k.instance_view(inst).expect("view present");
157        assert_eq!(view.id(), inst);
158        assert_eq!(view.entity_count(), 1);
159        assert_eq!(view.component_count(), 1);
160    }
161
162    #[test]
163    fn view_component_bytes_match_set() {
164        let (mut k, inst) = boot();
165        submit(
166            &mut k,
167            inst,
168            &SpawnManyAction {
169                count: 1,
170                type_code: 7,
171                size: 4,
172            },
173        );
174        let _ = k.step(Tick(0), CapabilityMask::SYSTEM);
175
176        let view = k.instance_view(inst).expect("view present");
177        let comp = view
178            .component(EntityId::new(1).unwrap(), TypeCode(7))
179            .expect("component present");
180        assert_eq!(comp.as_ref(), &[0xAB, 0xAB, 0xAB, 0xAB]);
181    }
182
183    #[test]
184    fn view_entities_iter_ascending() {
185        let (mut k, inst) = boot();
186        submit(
187            &mut k,
188            inst,
189            &SpawnManyAction {
190                count: 3,
191                type_code: 7,
192                size: 1,
193            },
194        );
195        let _ = k.step(Tick(0), CapabilityMask::SYSTEM);
196
197        let view = k.instance_view(inst).expect("view present");
198        let ids: Vec<u64> = view.entities().map(|(id, _)| id.get()).collect();
199        assert_eq!(ids, vec![1, 2, 3]);
200
201        // Spot-check meta reflects the producing principal/tick.
202        let meta = view.entity_meta(EntityId::new(2).unwrap()).expect("meta");
203        assert_eq!(meta.owner, Principal::System);
204        assert_eq!(meta.created, Tick(0));
205    }
206
207    #[test]
208    fn view_components_by_type_filter() {
209        // Spawn 2 entities, attach two different TypeCodes — query one.
210        let (mut k, inst) = boot();
211        submit(
212            &mut k,
213            inst,
214            &SpawnManyAction {
215                count: 2,
216                type_code: 7,
217                size: 1,
218            },
219        );
220        let _ = k.step(Tick(0), CapabilityMask::SYSTEM);
221        // Second action: same entities, different type_code (8).
222        submit(
223            &mut k,
224            inst,
225            &SpawnManyAction {
226                count: 2,
227                type_code: 8,
228                size: 1,
229            },
230        );
231        let _ = k.step(Tick(0), CapabilityMask::SYSTEM);
232
233        let view = k.instance_view(inst).expect("view present");
234        // 2 entities × 2 type_codes = 4 components total.
235        assert_eq!(view.component_count(), 4);
236
237        let tc7: Vec<u64> = view
238            .components_by_type(TypeCode(7))
239            .map(|(eid, _)| eid.get())
240            .collect();
241        assert_eq!(tc7, vec![1, 2]);
242
243        let tc8: Vec<u64> = view
244            .components_by_type(TypeCode(8))
245            .map(|(eid, _)| eid.get())
246            .collect();
247        assert_eq!(tc8, vec![1, 2]);
248    }
249
250    #[test]
251    fn view_local_tick_updates_after_step() {
252        let (mut k, inst) = boot();
253        submit(
254            &mut k,
255            inst,
256            &SpawnManyAction {
257                count: 1,
258                type_code: 7,
259                size: 1,
260            },
261        );
262        // Before step: tick is at 0.
263        let pre = k.instance_view(inst).unwrap().local_tick();
264        let _ = k.step(Tick(0), CapabilityMask::SYSTEM);
265        // The action does not advance local_tick (no Op::AdvanceTick
266        // exists) — the view reflects whatever apply_stage applied.
267        // Both readings should still resolve without panic; equality
268        // documents the current semantics.
269        let post = k.instance_view(inst).unwrap().local_tick();
270        assert_eq!(pre, post);
271    }
272}