Skip to main content

brink_runtime/
state.rs

1//! Context access trait and write observer.
2//!
3//! The `ContextAccess` trait provides the mutable state interface that the VM
4//! and orchestration use. [`World`](crate::world::World) implements it
5//! directly (zero-cost, monomorphized), as does the routing view
6//! ([`ContextView`](crate::world::ContextView)) that composes `World` with
7//! the (currently empty) per-flow `FlowLocal` layer. `ObservedContext` wraps
8//! any `ContextAccess` implementor and fires `WriteObserver` callbacks on
9//! every mutation.
10
11use brink_format::{DefinitionId, Value};
12
13use crate::rng::StoryRng;
14
15/// Trait for accessing and mutating story execution state.
16///
17/// This is the interface between the VM and the mutable story state.
18/// [`World`](crate::world::World) implements it directly, as does the
19/// [`ContextView`](crate::world::ContextView) routing view.
20/// [`ObservedContext`] wraps an implementor and fires [`WriteObserver`]
21/// callbacks on mutations. Consumers can also implement this trait
22/// themselves to plug in custom observers (e.g. bevy events) or alternate
23/// storage backends.
24///
25/// This does NOT include `Program`, resolver, or any immutable data — it's
26/// purely the mutable state surface.
27pub trait ContextAccess {
28    fn global(&self, idx: u32) -> &Value;
29    fn set_global(&mut self, idx: u32, value: Value);
30
31    fn visit_count(&self, id: DefinitionId) -> u32;
32    fn increment_visit(&mut self, id: DefinitionId);
33    /// Set a visit count directly, rather than incrementing it. Used by
34    /// [`crate::load_state`] to reconcile a durable save, whose entries carry
35    /// absolute counts rather than deltas.
36    fn set_visit_count(&mut self, id: DefinitionId, count: u32);
37
38    fn turn_count(&self, id: DefinitionId) -> Option<u32>;
39    fn set_turn_count(&mut self, id: DefinitionId, turn: u32);
40
41    fn turn_index(&self) -> u32;
42    fn increment_turn_index(&mut self);
43    /// Set the turn index directly, rather than incrementing it. Used by
44    /// [`crate::load_state`] to restore a saved turn index.
45    fn set_turn_index(&mut self, index: u32);
46
47    fn rng_seed(&self) -> i32;
48    fn set_rng_seed(&mut self, seed: i32);
49
50    fn previous_random(&self) -> i32;
51    fn set_previous_random(&mut self, val: i32);
52
53    fn next_random<R: StoryRng>(&self, seed: i32) -> i32;
54    fn random_sequence<R: StoryRng>(&self, seed: i32, count: usize) -> Vec<i32>;
55}
56
57// ── WriteObserver ──────────────────────────────────────────────────────────
58
59/// Observer for state mutations during story execution.
60///
61/// Implement this trait to intercept every write the VM makes to the story
62/// state. All methods have default no-op implementations. The observer
63/// receives the *new* value only — no old-value cloning is performed.
64#[expect(unused_variables)]
65pub trait WriteObserver {
66    fn on_set_global(&mut self, idx: u32, value: &Value) {}
67    fn on_increment_visit(&mut self, id: DefinitionId, new_count: u32) {}
68    fn on_set_visit_count(&mut self, id: DefinitionId, count: u32) {}
69    fn on_set_turn_count(&mut self, id: DefinitionId, turn: u32) {}
70    fn on_increment_turn_index(&mut self, new_value: u32) {}
71    fn on_set_turn_index(&mut self, index: u32) {}
72    fn on_set_rng_seed(&mut self, new_seed: i32) {}
73    fn on_set_previous_random(&mut self, new_val: i32) {}
74}
75
76// ── ObservedContext ────────────────────────────────────────────────────────
77
78/// A `ContextAccess` wrapper that delegates to an inner `ContextAccess`
79/// implementor (typically [`World`](crate::world::World) or the
80/// [`ContextView`](crate::world::ContextView) routing view) and notifies a
81/// `WriteObserver` on every mutation.
82///
83/// Generic over the wrapped implementor so it composes with the routing
84/// view: `ObservedContext::new(&mut ContextView::new(&mut world, &mut
85/// local), observer)` observes exactly what the VM sees, regardless of how
86/// many layers the routing view has behind it.
87pub struct ObservedContext<'a, 'o, C: ContextAccess> {
88    context: &'a mut C,
89    observer: &'o mut dyn WriteObserver,
90}
91
92impl<'a, 'o, C: ContextAccess> ObservedContext<'a, 'o, C> {
93    pub fn new(context: &'a mut C, observer: &'o mut dyn WriteObserver) -> Self {
94        Self { context, observer }
95    }
96}
97
98impl<C: ContextAccess> ContextAccess for ObservedContext<'_, '_, C> {
99    #[inline]
100    fn global(&self, idx: u32) -> &Value {
101        self.context.global(idx)
102    }
103
104    #[inline]
105    fn set_global(&mut self, idx: u32, value: Value) {
106        self.context.set_global(idx, value.clone());
107        self.observer.on_set_global(idx, &value);
108    }
109
110    #[inline]
111    fn visit_count(&self, id: DefinitionId) -> u32 {
112        self.context.visit_count(id)
113    }
114
115    #[inline]
116    fn increment_visit(&mut self, id: DefinitionId) {
117        self.context.increment_visit(id);
118        let new_count = self.context.visit_count(id);
119        self.observer.on_increment_visit(id, new_count);
120    }
121
122    #[inline]
123    fn set_visit_count(&mut self, id: DefinitionId, count: u32) {
124        self.context.set_visit_count(id, count);
125        self.observer.on_set_visit_count(id, count);
126    }
127
128    #[inline]
129    fn turn_count(&self, id: DefinitionId) -> Option<u32> {
130        self.context.turn_count(id)
131    }
132
133    #[inline]
134    fn set_turn_count(&mut self, id: DefinitionId, turn: u32) {
135        self.context.set_turn_count(id, turn);
136        self.observer.on_set_turn_count(id, turn);
137    }
138
139    #[inline]
140    fn turn_index(&self) -> u32 {
141        self.context.turn_index()
142    }
143
144    #[inline]
145    fn increment_turn_index(&mut self) {
146        self.context.increment_turn_index();
147        self.observer
148            .on_increment_turn_index(self.context.turn_index());
149    }
150
151    #[inline]
152    fn set_turn_index(&mut self, index: u32) {
153        self.context.set_turn_index(index);
154        self.observer.on_set_turn_index(index);
155    }
156
157    #[inline]
158    fn rng_seed(&self) -> i32 {
159        self.context.rng_seed()
160    }
161
162    #[inline]
163    fn set_rng_seed(&mut self, seed: i32) {
164        self.context.set_rng_seed(seed);
165        self.observer.on_set_rng_seed(seed);
166    }
167
168    #[inline]
169    fn previous_random(&self) -> i32 {
170        self.context.previous_random()
171    }
172
173    #[inline]
174    fn set_previous_random(&mut self, val: i32) {
175        self.context.set_previous_random(val);
176        self.observer.on_set_previous_random(val);
177    }
178
179    #[inline]
180    fn next_random<R: StoryRng>(&self, seed: i32) -> i32 {
181        self.context.next_random::<R>(seed)
182    }
183
184    fn random_sequence<R: StoryRng>(&self, seed: i32, count: usize) -> Vec<i32> {
185        self.context.random_sequence::<R>(seed, count)
186    }
187}