1use super::stage::{LedgerOp, PendingSignal, ScheduledEntryDelta, StagedStateDelta, StepStage};
16use crate::state::Instance;
17
18pub(crate) fn apply_stage(instance: &mut Instance, stage: &mut StepStage) -> Vec<PendingSignal> {
40 {
42 let c = instance.id_counters_mut();
43 c.next_entity = c
44 .next_entity
45 .saturating_add(stage.id_counters.next_entity_advance);
46 c.next_scheduled = c
47 .next_scheduled
48 .saturating_add(stage.id_counters.next_scheduled_advance);
49 c.next_source_seq = c
50 .next_source_seq
51 .saturating_add(stage.id_counters.next_source_seq_advance);
52 }
53
54 for op in stage.state_ops.drain(..) {
56 match op {
57 StagedStateDelta::SpawnEntity { id, meta } => {
58 instance.insert_entity(id, meta);
59 }
60 StagedStateDelta::DespawnEntity { id } => {
61 instance.remove_entity(id);
62 }
63 StagedStateDelta::SetComponent {
64 entity,
65 type_code,
66 bytes,
67 ..
68 } => {
69 if instance.entity_meta(entity).is_some() {
79 instance.insert_component((entity, type_code), bytes);
80 }
81 }
82 StagedStateDelta::RemoveComponent {
83 entity, type_code, ..
84 } => {
85 instance.remove_component((entity, type_code));
86 }
87 }
88 }
89
90 {
92 let ledger = instance.ledger_mut();
93 for lop in stage.ledger_delta.ops.drain(..) {
94 match lop {
95 LedgerOp::AddEntity(id) => {
96 let _ = ledger.add_entity(id);
97 }
98 LedgerOp::RemoveEntity(id) => {
99 let _ = ledger.remove_entity(id);
100 }
101 LedgerOp::AddComponent {
102 entity,
103 type_code,
104 size,
105 } => {
106 let _ = ledger.add_component(entity, type_code, size);
107 }
108 LedgerOp::RemoveComponent {
109 entity,
110 type_code,
111 size,
112 } => {
113 let _ = ledger.remove_component(entity, type_code, size);
114 }
115 }
116 }
117 }
118
119 {
124 let scheduler = instance.scheduler_mut();
125 for sd in stage.schedule_deltas.drain(..) {
126 match sd {
127 ScheduledEntryDelta::Add(entry) => {
128 scheduler.schedule_with_id(
134 entry.id,
135 entry.at,
136 entry.actor,
137 entry.principal,
138 entry.caps_ceiling,
139 entry.action_type_code,
140 entry.action_bytes,
141 );
142 }
143 ScheduledEntryDelta::Remove(id) => {
144 let _ = scheduler.cancel(id);
145 }
146 }
147 }
148 }
149
150 instance.advance_wall_remainder(stage.wall_remainder_delta);
152 instance.advance_local_tick(stage.local_tick_delta);
153
154 stage.pending_signals.drain(..).collect()
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use bytes::Bytes;
165
166 use crate::abi::{CapabilityMask, EntityId, InstanceId, Principal, Tick, TypeCode};
167 use crate::runtime::stage::{IdCountersDelta, LedgerOp, ScheduledEntryDelta, StagedStateDelta};
168 use crate::state::{
169 EntityMeta, Instance, InstanceConfig, QuotaReductionPolicy, ScheduledActionId,
170 ScheduledEntry,
171 };
172
173 fn id(n: u64) -> InstanceId {
174 InstanceId::new(n).unwrap()
175 }
176 fn entity(n: u64) -> EntityId {
177 EntityId::new(n).unwrap()
178 }
179 fn cfg() -> InstanceConfig {
180 InstanceConfig {
181 default_caps: CapabilityMask::default(),
182 max_entities: 100,
183 max_scheduled: 1000,
184 max_inbox_per_route: 16,
185 memory_budget_bytes: 1 << 20,
186 parent: None,
187 quota_reduction: QuotaReductionPolicy::default(),
188 }
189 }
190
191 #[test]
192 fn apply_empty_stage_leaves_instance_untouched() {
193 let mut inst = Instance::new(id(1), cfg());
194 apply_stage(&mut inst, &mut StepStage::default());
195 assert_eq!(inst.entities_len(), 0);
196 assert_eq!(inst.components_len(), 0);
197 assert_eq!(inst.local_tick(), 0);
198 assert_eq!(inst.wall_remainder(), 0);
199 assert_eq!(inst.ledger().total_entities(), 0);
200 assert_eq!(inst.id_counters().next_entity, 0);
201 }
202
203 #[test]
204 fn apply_spawn_entity() {
205 let mut inst = Instance::new(id(1), cfg());
206 let mut stage = StepStage::default();
207 stage.state_ops.push(StagedStateDelta::SpawnEntity {
208 id: entity(1),
209 meta: EntityMeta {
210 owner: Principal::System,
211 created: Tick(0),
212 },
213 });
214 apply_stage(&mut inst, &mut stage);
215 assert_eq!(inst.entities_len(), 1);
216 }
217
218 #[test]
219 fn apply_despawn_entity() {
220 let mut inst = Instance::new(id(1), cfg());
221 let mut spawn = StepStage::default();
222 spawn.state_ops.push(StagedStateDelta::SpawnEntity {
223 id: entity(1),
224 meta: EntityMeta {
225 owner: Principal::System,
226 created: Tick(0),
227 },
228 });
229 apply_stage(&mut inst, &mut spawn);
230 assert_eq!(inst.entities_len(), 1);
231
232 let mut despawn = StepStage::default();
233 despawn
234 .state_ops
235 .push(StagedStateDelta::DespawnEntity { id: entity(1) });
236 apply_stage(&mut inst, &mut despawn);
237 assert_eq!(inst.entities_len(), 0);
238 }
239
240 #[test]
241 fn apply_set_and_remove_component() {
242 let mut inst = Instance::new(id(1), cfg());
243 let mut spawn = StepStage::default();
247 spawn.state_ops.push(StagedStateDelta::SpawnEntity {
248 id: entity(1),
249 meta: EntityMeta {
250 owner: Principal::System,
251 created: Tick(0),
252 },
253 });
254 apply_stage(&mut inst, &mut spawn);
255
256 let mut stage = StepStage::default();
257 stage.state_ops.push(StagedStateDelta::SetComponent {
258 entity: entity(1),
259 type_code: TypeCode(7),
260 bytes: Bytes::from_static(b"data"),
261 size: 4,
262 });
263 apply_stage(&mut inst, &mut stage);
264 assert_eq!(inst.components_len(), 1);
265
266 let mut rm = StepStage::default();
267 rm.state_ops.push(StagedStateDelta::RemoveComponent {
268 entity: entity(1),
269 type_code: TypeCode(7),
270 size: 4,
271 });
272 apply_stage(&mut inst, &mut rm);
273 assert_eq!(inst.components_len(), 0);
274 }
275
276 #[test]
277 fn apply_set_component_on_unknown_entity_is_noop() {
278 let mut inst = Instance::new(id(1), cfg());
283 let mut stage = StepStage::default();
284 stage.state_ops.push(StagedStateDelta::SetComponent {
285 entity: entity(999),
286 type_code: TypeCode(7),
287 bytes: Bytes::from_static(b"orphan"),
288 size: 6,
289 });
290 stage.ledger_delta.ops.push(LedgerOp::AddComponent {
291 entity: entity(999),
292 type_code: TypeCode(7),
293 size: 6,
294 });
295 apply_stage(&mut inst, &mut stage);
296 assert_eq!(inst.components_len(), 0, "orphan component must not be stored");
297 assert_eq!(
298 inst.ledger().total_bytes(),
299 0,
300 "ledger must not account an unknown-entity component"
301 );
302 assert!(
303 inst.component(entity(999), TypeCode(7)).is_none(),
304 "InstanceView must not surface the orphan component"
305 );
306 }
307
308 #[test]
309 fn apply_spawn_then_set_in_same_stage_stores_component() {
310 let mut inst = Instance::new(id(1), cfg());
314 let mut stage = StepStage::default();
315 stage.state_ops.push(StagedStateDelta::SpawnEntity {
316 id: entity(5),
317 meta: EntityMeta {
318 owner: Principal::System,
319 created: Tick(0),
320 },
321 });
322 stage.state_ops.push(StagedStateDelta::SetComponent {
323 entity: entity(5),
324 type_code: TypeCode(7),
325 bytes: Bytes::from_static(b"data"),
326 size: 4,
327 });
328 stage.ledger_delta.ops.push(LedgerOp::AddEntity(entity(5)));
329 stage.ledger_delta.ops.push(LedgerOp::AddComponent {
330 entity: entity(5),
331 type_code: TypeCode(7),
332 size: 4,
333 });
334 apply_stage(&mut inst, &mut stage);
335 assert_eq!(inst.components_len(), 1);
336 assert_eq!(inst.ledger().total_bytes(), 4);
337 }
338
339 #[test]
340 fn apply_ledger_delta_balanced() {
341 let mut inst = Instance::new(id(1), cfg());
342 let mut stage = StepStage::default();
343 stage.ledger_delta.ops.push(LedgerOp::AddEntity(entity(1)));
344 stage.ledger_delta.ops.push(LedgerOp::AddComponent {
345 entity: entity(1),
346 type_code: TypeCode(1),
347 size: 100,
348 });
349 apply_stage(&mut inst, &mut stage);
350 assert_eq!(inst.ledger().total_entities(), 1);
351 assert_eq!(inst.ledger().total_bytes(), 100);
352 assert_eq!(inst.ledger().entity_bytes(entity(1)), 100);
353 }
354
355 #[test]
356 fn apply_id_counters_advance() {
357 let mut inst = Instance::new(id(1), cfg());
358 let mut stage = StepStage {
359 id_counters: IdCountersDelta {
360 next_entity_advance: 5,
361 next_scheduled_advance: 3,
362 next_source_seq_advance: 7,
363 },
364 ..Default::default()
365 };
366 apply_stage(&mut inst, &mut stage);
367 assert_eq!(inst.id_counters().next_entity, 5);
368 assert_eq!(inst.id_counters().next_scheduled, 3);
369 assert_eq!(inst.id_counters().next_source_seq, 7);
370 }
371
372 #[test]
373 fn apply_wall_and_local_tick_advance() {
374 let mut inst = Instance::new(id(1), cfg());
375 let mut stage = StepStage {
376 wall_remainder_delta: 12345,
377 local_tick_delta: 7,
378 ..Default::default()
379 };
380 apply_stage(&mut inst, &mut stage);
381 assert_eq!(inst.wall_remainder(), 12345);
382 assert_eq!(inst.local_tick(), 7);
383 }
384
385 #[test]
386 fn apply_schedule_delta_add_inserts_into_scheduler() {
387 let mut inst = Instance::new(id(1), cfg());
388 let mut stage = StepStage {
389 schedule_deltas: vec![ScheduledEntryDelta::Add(ScheduledEntry {
390 id: ScheduledActionId::new(1).unwrap(),
391 at: Tick(10),
392 actor: None,
393 principal: Principal::System,
394 action_type_code: TypeCode(0),
395 action_bytes: vec![1, 2, 3],
396 caps_ceiling: CapabilityMask::all(),
397 })],
398 ..Default::default()
399 };
400 apply_stage(&mut inst, &mut stage);
401 assert_eq!(inst.scheduler().len(), 1);
402 }
403
404 #[test]
405 fn apply_then_clear_reuse_is_state_clean() {
406 let mut inst = Instance::new(id(1), cfg());
410 let mut scratch = StepStage::default();
411 scratch.state_ops.push(StagedStateDelta::SpawnEntity {
412 id: entity(1),
413 meta: EntityMeta {
414 owner: Principal::System,
415 created: Tick(0),
416 },
417 });
418 scratch.ledger_delta.ops.push(LedgerOp::AddEntity(entity(1)));
419 scratch.local_tick_delta = 1;
420 apply_stage(&mut inst, &mut scratch);
421 scratch.clear();
422
423 scratch.state_ops.push(StagedStateDelta::SpawnEntity {
424 id: entity(2),
425 meta: EntityMeta {
426 owner: Principal::System,
427 created: Tick(0),
428 },
429 });
430 scratch.ledger_delta.ops.push(LedgerOp::AddEntity(entity(2)));
431 scratch.local_tick_delta = 1;
432 apply_stage(&mut inst, &mut scratch);
433
434 assert_eq!(inst.entities_len(), 2);
436 assert_eq!(inst.ledger().total_entities(), 2);
437 assert_eq!(inst.local_tick(), 2);
438 }
439}