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. `Context` implements it directly (zero-cost,
5//! monomorphized). `ObservedContext` wraps a `Context` and fires
6//! `WriteObserver` callbacks on every mutation.
7
8use brink_format::{DefinitionId, Value};
9
10use crate::rng::StoryRng;
11use crate::story::Context;
12
13/// Trait for accessing and mutating story execution state.
14///
15/// This is the interface between the VM and the mutable story state.
16/// [`Context`] implements it directly. [`ObservedContext`] wraps a
17/// `Context` and fires [`WriteObserver`] callbacks on mutations.
18/// Consumers can also implement this trait themselves to plug in custom
19/// observers (e.g. bevy events) or alternate storage backends.
20///
21/// This does NOT include `Program`, resolver, or any immutable data — it's
22/// purely the mutable state surface.
23pub trait ContextAccess {
24    fn global(&self, idx: u32) -> &Value;
25    fn set_global(&mut self, idx: u32, value: Value);
26
27    fn visit_count(&self, id: DefinitionId) -> u32;
28    fn increment_visit(&mut self, id: DefinitionId);
29
30    fn turn_count(&self, id: DefinitionId) -> Option<u32>;
31    fn set_turn_count(&mut self, id: DefinitionId, turn: u32);
32
33    fn turn_index(&self) -> u32;
34    fn increment_turn_index(&mut self);
35
36    fn rng_seed(&self) -> i32;
37    fn set_rng_seed(&mut self, seed: i32);
38
39    fn previous_random(&self) -> i32;
40    fn set_previous_random(&mut self, val: i32);
41
42    fn next_random<R: StoryRng>(&self, seed: i32) -> i32;
43    fn random_sequence<R: StoryRng>(&self, seed: i32, count: usize) -> Vec<i32>;
44}
45
46impl ContextAccess for Context {
47    #[inline]
48    fn global(&self, idx: u32) -> &Value {
49        &self.globals[idx as usize]
50    }
51
52    #[inline]
53    fn set_global(&mut self, idx: u32, value: Value) {
54        self.globals[idx as usize] = value;
55    }
56
57    #[inline]
58    fn visit_count(&self, id: DefinitionId) -> u32 {
59        self.visit_counts.get(&id).copied().unwrap_or(0)
60    }
61
62    #[inline]
63    fn increment_visit(&mut self, id: DefinitionId) {
64        *self.visit_counts.entry(id).or_insert(0) += 1;
65    }
66
67    #[inline]
68    fn turn_count(&self, id: DefinitionId) -> Option<u32> {
69        self.turn_counts.get(&id).copied()
70    }
71
72    #[inline]
73    fn set_turn_count(&mut self, id: DefinitionId, turn: u32) {
74        self.turn_counts.insert(id, turn);
75    }
76
77    #[inline]
78    fn turn_index(&self) -> u32 {
79        self.turn_index
80    }
81
82    #[inline]
83    fn increment_turn_index(&mut self) {
84        self.turn_index += 1;
85    }
86
87    #[inline]
88    fn rng_seed(&self) -> i32 {
89        self.rng_seed
90    }
91
92    #[inline]
93    fn set_rng_seed(&mut self, seed: i32) {
94        self.rng_seed = seed;
95    }
96
97    #[inline]
98    fn previous_random(&self) -> i32 {
99        self.previous_random
100    }
101
102    #[inline]
103    fn set_previous_random(&mut self, val: i32) {
104        self.previous_random = val;
105    }
106
107    #[inline]
108    fn next_random<R: StoryRng>(&self, seed: i32) -> i32 {
109        let mut rng = R::from_seed(seed);
110        rng.next_int()
111    }
112
113    fn random_sequence<R: StoryRng>(&self, seed: i32, count: usize) -> Vec<i32> {
114        let mut rng = R::from_seed(seed);
115        (0..count).map(|_| rng.next_int()).collect()
116    }
117}
118
119// ── WriteObserver ──────────────────────────────────────────────────────────
120
121/// Observer for state mutations during story execution.
122///
123/// Implement this trait to intercept every write the VM makes to the story
124/// state. All methods have default no-op implementations. The observer
125/// receives the *new* value only — no old-value cloning is performed.
126#[expect(unused_variables)]
127pub trait WriteObserver {
128    fn on_set_global(&mut self, idx: u32, value: &Value) {}
129    fn on_increment_visit(&mut self, id: DefinitionId, new_count: u32) {}
130    fn on_set_turn_count(&mut self, id: DefinitionId, turn: u32) {}
131    fn on_increment_turn_index(&mut self, new_value: u32) {}
132    fn on_set_rng_seed(&mut self, new_seed: i32) {}
133    fn on_set_previous_random(&mut self, new_val: i32) {}
134}
135
136// ── ObservedContext ────────────────────────────────────────────────────────
137
138/// A `ContextAccess` wrapper that delegates to an inner `Context` and
139/// notifies a `WriteObserver` on every mutation.
140pub struct ObservedContext<'a, 'o> {
141    context: &'a mut Context,
142    observer: &'o mut dyn WriteObserver,
143}
144
145impl<'a, 'o> ObservedContext<'a, 'o> {
146    pub fn new(context: &'a mut Context, observer: &'o mut dyn WriteObserver) -> Self {
147        Self { context, observer }
148    }
149}
150
151impl ContextAccess for ObservedContext<'_, '_> {
152    #[inline]
153    fn global(&self, idx: u32) -> &Value {
154        self.context.global(idx)
155    }
156
157    #[inline]
158    fn set_global(&mut self, idx: u32, value: Value) {
159        self.context.set_global(idx, value.clone());
160        self.observer.on_set_global(idx, &value);
161    }
162
163    #[inline]
164    fn visit_count(&self, id: DefinitionId) -> u32 {
165        self.context.visit_count(id)
166    }
167
168    #[inline]
169    fn increment_visit(&mut self, id: DefinitionId) {
170        self.context.increment_visit(id);
171        let new_count = self.context.visit_count(id);
172        self.observer.on_increment_visit(id, new_count);
173    }
174
175    #[inline]
176    fn turn_count(&self, id: DefinitionId) -> Option<u32> {
177        self.context.turn_count(id)
178    }
179
180    #[inline]
181    fn set_turn_count(&mut self, id: DefinitionId, turn: u32) {
182        self.context.set_turn_count(id, turn);
183        self.observer.on_set_turn_count(id, turn);
184    }
185
186    #[inline]
187    fn turn_index(&self) -> u32 {
188        self.context.turn_index()
189    }
190
191    #[inline]
192    fn increment_turn_index(&mut self) {
193        self.context.increment_turn_index();
194        self.observer
195            .on_increment_turn_index(self.context.turn_index());
196    }
197
198    #[inline]
199    fn rng_seed(&self) -> i32 {
200        self.context.rng_seed()
201    }
202
203    #[inline]
204    fn set_rng_seed(&mut self, seed: i32) {
205        self.context.set_rng_seed(seed);
206        self.observer.on_set_rng_seed(seed);
207    }
208
209    #[inline]
210    fn previous_random(&self) -> i32 {
211        self.context.previous_random()
212    }
213
214    #[inline]
215    fn set_previous_random(&mut self, val: i32) {
216        self.context.set_previous_random(val);
217        self.observer.on_set_previous_random(val);
218    }
219
220    #[inline]
221    fn next_random<R: StoryRng>(&self, seed: i32) -> i32 {
222        Context::next_random::<R>(seed)
223    }
224
225    fn random_sequence<R: StoryRng>(&self, seed: i32, count: usize) -> Vec<i32> {
226        Context::random_sequence::<R>(seed, count)
227    }
228}