1use std::collections::VecDeque;
24
25use bytes::Bytes;
26use serde::{Deserialize, Serialize};
27
28use crate::abi::{EntityId, InstanceId, Principal, RouteId, TypeCode};
29use crate::state::ledger::ResourceLedger;
30use crate::state::{EntityMeta, ScheduledActionId, ScheduledEntry};
31
32use super::event::{KernelEvent, ObserverHandle};
33
34#[derive(Debug, Default, Clone, Serialize, Deserialize)]
35pub(crate) struct StepStage {
36 pub state_ops: Vec<StagedStateDelta>,
37 pub events: VecDeque<KernelEvent>,
38 pub schedule_deltas: Vec<ScheduledEntryDelta>,
39 pub pending_signals: Vec<PendingSignal>,
40 pub id_counters: IdCountersDelta,
41 pub ledger_delta: ResourceLedgerDelta,
42 pub wall_remainder_delta: u128,
43 pub local_tick_delta: u64,
44 pub observer_eviction_pending: Vec<ObserverHandle>,
45}
46
47impl StepStage {
48 pub(crate) fn clear(&mut self) {
58 let Self {
59 state_ops,
60 events,
61 schedule_deltas,
62 pending_signals,
63 id_counters,
64 ledger_delta,
65 wall_remainder_delta,
66 local_tick_delta,
67 observer_eviction_pending,
68 } = self;
69 state_ops.clear();
70 events.clear();
71 schedule_deltas.clear();
72 pending_signals.clear();
73 *id_counters = IdCountersDelta::default();
74 ledger_delta.ops.clear();
75 *wall_remainder_delta = 0;
76 *local_tick_delta = 0;
77 observer_eviction_pending.clear();
78 }
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub(crate) enum StagedStateDelta {
83 SpawnEntity {
84 id: EntityId,
85 meta: EntityMeta,
86 },
87 DespawnEntity {
88 id: EntityId,
89 },
90 SetComponent {
91 entity: EntityId,
92 type_code: TypeCode,
93 bytes: Bytes,
94 size: u64,
95 },
96 RemoveComponent {
97 entity: EntityId,
98 type_code: TypeCode,
99 size: u64,
100 },
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub(crate) enum ScheduledEntryDelta {
105 Add(ScheduledEntry),
106 Remove(ScheduledActionId),
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub(crate) struct PendingSignal {
111 pub target: InstanceId,
112 pub route: RouteId,
113 pub payload: Bytes,
114 pub principal: Principal,
115}
116
117#[derive(Debug, Default, Clone, Serialize, Deserialize)]
118pub(crate) struct IdCountersDelta {
119 pub next_entity_advance: u64,
120 pub next_scheduled_advance: u64,
121 pub next_source_seq_advance: u64,
122}
123
124#[derive(Debug, Default, Clone, Serialize, Deserialize)]
125pub(crate) struct ResourceLedgerDelta {
126 pub ops: Vec<LedgerOp>,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub(crate) enum LedgerOp {
131 AddEntity(EntityId),
132 RemoveEntity(EntityId),
133 AddComponent {
134 entity: EntityId,
135 type_code: TypeCode,
136 size: u64,
137 },
138 RemoveComponent {
139 entity: EntityId,
140 type_code: TypeCode,
141 size: u64,
142 },
143}
144
145pub(crate) fn projected_component_bytes(
166 baseline: u64,
167 stage: &StepStage,
168 ledger: &ResourceLedger,
169) -> u64 {
170 let mut total = baseline;
171 for op in &stage.ledger_delta.ops {
172 match op {
173 LedgerOp::AddComponent { size, .. } => {
174 total = total.saturating_add(*size);
175 }
176 LedgerOp::RemoveComponent {
177 entity, type_code, ..
178 } => {
179 let freed = ledger.component_size(*entity, *type_code).unwrap_or(0);
180 total = total.saturating_sub(freed);
181 }
182 LedgerOp::AddEntity(_) | LedgerOp::RemoveEntity(_) => {}
183 }
184 }
185 total
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use crate::abi::{CapabilityMask, Tick};
192
193 #[test]
194 fn step_stage_default_all_buckets_empty() {
195 let s = StepStage::default();
196 assert!(s.state_ops.is_empty());
197 assert!(s.events.is_empty());
198 assert!(s.schedule_deltas.is_empty());
199 assert!(s.pending_signals.is_empty());
200 assert_eq!(s.id_counters.next_entity_advance, 0);
201 assert_eq!(s.id_counters.next_scheduled_advance, 0);
202 assert_eq!(s.id_counters.next_source_seq_advance, 0);
203 assert!(s.ledger_delta.ops.is_empty());
204 assert_eq!(s.wall_remainder_delta, 0);
205 assert_eq!(s.local_tick_delta, 0);
206 assert!(s.observer_eviction_pending.is_empty());
207 }
208
209 #[test]
210 fn clear_resets_all_buckets() {
211 let id = EntityId::new(1).unwrap();
215 let mut s = StepStage::default();
216 s.state_ops.push(StagedStateDelta::DespawnEntity { id });
217 s.events.push_back(KernelEvent::ActionExecuted {
218 instance: InstanceId::new(1).unwrap(),
219 action_type: TypeCode(1),
220 at: Tick(1),
221 });
222 s.schedule_deltas
223 .push(ScheduledEntryDelta::Remove(ScheduledActionId::new(1).unwrap()));
224 s.pending_signals.push(PendingSignal {
225 target: InstanceId::new(1).unwrap(),
226 route: RouteId(1),
227 payload: Bytes::from_static(b"x"),
228 principal: Principal::System,
229 });
230 s.id_counters.next_entity_advance = 9;
231 s.id_counters.next_scheduled_advance = 9;
232 s.id_counters.next_source_seq_advance = 9;
233 s.ledger_delta.ops.push(LedgerOp::AddEntity(id));
234 s.wall_remainder_delta = 12345;
235 s.local_tick_delta = 7;
236
237 s.clear();
238
239 assert!(s.state_ops.is_empty());
240 assert!(s.events.is_empty());
241 assert!(s.schedule_deltas.is_empty());
242 assert!(s.pending_signals.is_empty());
243 assert_eq!(s.id_counters.next_entity_advance, 0);
244 assert_eq!(s.id_counters.next_scheduled_advance, 0);
245 assert_eq!(s.id_counters.next_source_seq_advance, 0);
246 assert!(s.ledger_delta.ops.is_empty());
247 assert_eq!(s.wall_remainder_delta, 0);
248 assert_eq!(s.local_tick_delta, 0);
249 assert!(s.observer_eviction_pending.is_empty());
250 }
251
252 #[test]
253 fn staged_state_delta_variants_clone() {
254 let id = EntityId::new(1).unwrap();
255 let meta = EntityMeta {
256 owner: Principal::System,
257 created: Tick(0),
258 };
259 let _ = StagedStateDelta::SpawnEntity { id, meta }.clone();
260 let _ = StagedStateDelta::DespawnEntity { id }.clone();
261 let _ = StagedStateDelta::SetComponent {
262 entity: id,
263 type_code: TypeCode(1),
264 bytes: Bytes::from_static(b"x"),
265 size: 1,
266 }
267 .clone();
268 let _ = StagedStateDelta::RemoveComponent {
269 entity: id,
270 type_code: TypeCode(1),
271 size: 1,
272 }
273 .clone();
274 }
275
276 #[test]
277 fn scheduled_entry_delta_variants() {
278 let entry = ScheduledEntry {
279 id: ScheduledActionId::new(1).unwrap(),
280 at: Tick(0),
281 actor: None,
282 principal: Principal::System,
283 action_type_code: TypeCode(0),
284 action_bytes: vec![],
285 caps_ceiling: CapabilityMask::all(),
286 };
287 let _ = ScheduledEntryDelta::Add(entry).clone();
288 let _ = ScheduledEntryDelta::Remove(ScheduledActionId::new(1).unwrap()).clone();
289 }
290
291 #[test]
292 fn projected_oversized_add_saturates_no_negative_wrap() {
293 let id = EntityId::new(1).unwrap();
297 let mut stage = StepStage::default();
298 stage.ledger_delta.ops.push(LedgerOp::AddComponent {
299 entity: id,
300 type_code: TypeCode(1),
301 size: u64::MAX,
302 });
303 let ledger = ResourceLedger::new();
304 assert_eq!(projected_component_bytes(0, &stage, &ledger), u64::MAX);
305 }
306
307 #[test]
308 fn projected_remove_credits_stored_size_not_caller_size() {
309 let id = EntityId::new(1).unwrap();
312 let mut ledger = ResourceLedger::new();
313 ledger.add_entity(id);
314 ledger.add_component(id, TypeCode(1), 100);
315 let mut stage = StepStage::default();
316 stage.ledger_delta.ops.push(LedgerOp::RemoveComponent {
317 entity: id,
318 type_code: TypeCode(1),
319 size: u64::MAX, });
321 assert_eq!(projected_component_bytes(100, &stage, &ledger), 0);
323 }
324
325 #[test]
326 fn projected_remove_of_unaccounted_component_credits_zero() {
327 let id = EntityId::new(1).unwrap();
330 let ledger = ResourceLedger::new();
331 let mut stage = StepStage::default();
332 stage.ledger_delta.ops.push(LedgerOp::RemoveComponent {
333 entity: id,
334 type_code: TypeCode(1),
335 size: 50,
336 });
337 assert_eq!(projected_component_bytes(0, &stage, &ledger), 0);
338 }
339
340 #[test]
341 fn ledger_op_variants() {
342 let id = EntityId::new(1).unwrap();
343 let _ = LedgerOp::AddEntity(id).clone();
344 let _ = LedgerOp::RemoveEntity(id).clone();
345 let _ = LedgerOp::AddComponent {
346 entity: id,
347 type_code: TypeCode(1),
348 size: 100,
349 }
350 .clone();
351 let _ = LedgerOp::RemoveComponent {
352 entity: id,
353 type_code: TypeCode(1),
354 size: 100,
355 }
356 .clone();
357 }
358
359 #[test]
360 fn pending_signal_construction() {
361 let s = PendingSignal {
362 target: InstanceId::new(1).unwrap(),
363 route: RouteId(1),
364 payload: Bytes::from_static(b"hello"),
365 principal: Principal::System,
366 };
367 assert_eq!(s.payload.len(), 5);
368 }
369}