use super::stage::{LedgerOp, ScheduledEntryDelta, StagedStateDelta, StepStage};
use crate::state::Instance;
pub(crate) fn apply_stage(instance: &mut Instance, stage: StepStage) {
{
let c = instance.id_counters_mut();
c.next_entity = c
.next_entity
.saturating_add(stage.id_counters.next_entity_advance);
c.next_scheduled = c
.next_scheduled
.saturating_add(stage.id_counters.next_scheduled_advance);
c.next_source_seq = c
.next_source_seq
.saturating_add(stage.id_counters.next_source_seq_advance);
}
for op in stage.state_ops {
match op {
StagedStateDelta::SpawnEntity { id, meta } => {
instance.insert_entity(id, meta);
}
StagedStateDelta::DespawnEntity { id } => {
instance.remove_entity(id);
}
StagedStateDelta::SetComponent {
entity,
type_code,
bytes,
..
} => {
instance.insert_component((entity, type_code), bytes);
}
StagedStateDelta::RemoveComponent {
entity, type_code, ..
} => {
instance.remove_component((entity, type_code));
}
}
}
{
let ledger = instance.ledger_mut();
for lop in stage.ledger_delta.ops {
match lop {
LedgerOp::AddEntity(id) => {
let _ = ledger.add_entity(id);
}
LedgerOp::RemoveEntity(id) => {
let _ = ledger.remove_entity(id);
}
LedgerOp::AddComponent {
entity,
type_code,
size,
} => {
let _ = ledger.add_component(entity, type_code, size);
}
LedgerOp::RemoveComponent {
entity,
type_code,
size,
} => {
let _ = ledger.remove_component(entity, type_code, size);
}
}
}
}
{
let refs = instance.inflight_refs_mut();
for (route_id, delta) in stage.inflight_refs_delta {
let entry = refs.entry(route_id).or_insert(0);
if delta >= 0 {
*entry = entry.saturating_add(delta as u32);
} else {
*entry = entry.saturating_sub(delta.unsigned_abs());
}
if *entry == 0 {
refs.remove(&route_id);
}
}
}
{
let scheduler = instance.scheduler_mut();
for sd in stage.schedule_deltas {
match sd {
ScheduledEntryDelta::Add(entry) => {
let _ = scheduler.schedule(
entry.at,
entry.actor,
entry.principal,
entry.action_type_code,
entry.action_bytes,
);
}
ScheduledEntryDelta::Remove(id) => {
let _ = scheduler.cancel(id);
}
}
}
}
let _ = stage.pending_signals;
let _ = stage.events;
let _ = stage.observer_eviction_pending;
instance.advance_wall_remainder(stage.wall_remainder_delta);
instance.advance_local_tick(stage.local_tick_delta);
}
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn discard_stage(_instance: &mut Instance, _stage: StepStage) {
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
use crate::abi::{CapabilityMask, EntityId, InstanceId, Principal, RouteId, Tick, TypeCode};
use crate::runtime::stage::{IdCountersDelta, LedgerOp, ScheduledEntryDelta, StagedStateDelta};
use crate::state::{
EntityMeta, Instance, InstanceConfig, QuotaReductionPolicy, ScheduledActionId,
ScheduledEntry,
};
fn id(n: u64) -> InstanceId {
InstanceId::new(n).unwrap()
}
fn entity(n: u64) -> EntityId {
EntityId::new(n).unwrap()
}
fn cfg() -> InstanceConfig {
InstanceConfig {
default_caps: CapabilityMask::default(),
max_entities: 100,
max_scheduled: 1000,
memory_budget_bytes: 1 << 20,
parent: None,
quota_reduction: QuotaReductionPolicy::default(),
}
}
#[test]
fn apply_empty_stage_leaves_instance_untouched() {
let mut inst = Instance::new(id(1), cfg());
apply_stage(&mut inst, StepStage::default());
assert_eq!(inst.entities_len(), 0);
assert_eq!(inst.components_len(), 0);
assert_eq!(inst.local_tick(), 0);
assert_eq!(inst.wall_remainder(), 0);
assert_eq!(inst.ledger().total_entities(), 0);
assert_eq!(inst.id_counters().next_entity, 0);
}
#[test]
fn apply_spawn_entity() {
let mut inst = Instance::new(id(1), cfg());
let mut stage = StepStage::default();
stage.state_ops.push(StagedStateDelta::SpawnEntity {
id: entity(1),
meta: EntityMeta {
owner: Principal::System,
created: Tick(0),
},
});
apply_stage(&mut inst, stage);
assert_eq!(inst.entities_len(), 1);
}
#[test]
fn apply_despawn_entity() {
let mut inst = Instance::new(id(1), cfg());
let mut spawn = StepStage::default();
spawn.state_ops.push(StagedStateDelta::SpawnEntity {
id: entity(1),
meta: EntityMeta {
owner: Principal::System,
created: Tick(0),
},
});
apply_stage(&mut inst, spawn);
assert_eq!(inst.entities_len(), 1);
let mut despawn = StepStage::default();
despawn
.state_ops
.push(StagedStateDelta::DespawnEntity { id: entity(1) });
apply_stage(&mut inst, despawn);
assert_eq!(inst.entities_len(), 0);
}
#[test]
fn apply_set_and_remove_component() {
let mut inst = Instance::new(id(1), cfg());
let mut stage = StepStage::default();
stage.state_ops.push(StagedStateDelta::SetComponent {
entity: entity(1),
type_code: TypeCode(7),
bytes: Bytes::from_static(b"data"),
size: 4,
});
apply_stage(&mut inst, stage);
assert_eq!(inst.components_len(), 1);
let mut rm = StepStage::default();
rm.state_ops.push(StagedStateDelta::RemoveComponent {
entity: entity(1),
type_code: TypeCode(7),
size: 4,
});
apply_stage(&mut inst, rm);
assert_eq!(inst.components_len(), 0);
}
#[test]
fn apply_ledger_delta_balanced() {
let mut inst = Instance::new(id(1), cfg());
let mut stage = StepStage::default();
stage.ledger_delta.ops.push(LedgerOp::AddEntity(entity(1)));
stage.ledger_delta.ops.push(LedgerOp::AddComponent {
entity: entity(1),
type_code: TypeCode(1),
size: 100,
});
apply_stage(&mut inst, stage);
assert_eq!(inst.ledger().total_entities(), 1);
assert_eq!(inst.ledger().total_bytes(), 100);
assert_eq!(inst.ledger().entity_bytes(entity(1)), 100);
}
#[test]
fn apply_id_counters_advance() {
let mut inst = Instance::new(id(1), cfg());
let stage = StepStage {
id_counters: IdCountersDelta {
next_entity_advance: 5,
next_scheduled_advance: 3,
next_source_seq_advance: 7,
},
..Default::default()
};
apply_stage(&mut inst, stage);
assert_eq!(inst.id_counters().next_entity, 5);
assert_eq!(inst.id_counters().next_scheduled, 3);
assert_eq!(inst.id_counters().next_source_seq, 7);
}
#[test]
fn apply_inflight_refs_positive_then_negative_to_zero() {
let mut inst = Instance::new(id(1), cfg());
let route = RouteId(42);
let up = StepStage {
inflight_refs_delta: [(route, 3)].into_iter().collect(),
..Default::default()
};
apply_stage(&mut inst, up);
assert_eq!(inst.inflight_refs_for(route), 3);
let down = StepStage {
inflight_refs_delta: [(route, -3)].into_iter().collect(),
..Default::default()
};
apply_stage(&mut inst, down);
assert_eq!(inst.inflight_refs_for(route), 0);
assert_eq!(inst.inflight_refs_len(), 0);
}
#[test]
fn apply_wall_and_local_tick_advance() {
let mut inst = Instance::new(id(1), cfg());
let stage = StepStage {
wall_remainder_delta: 12345,
local_tick_delta: 7,
..Default::default()
};
apply_stage(&mut inst, stage);
assert_eq!(inst.wall_remainder(), 12345);
assert_eq!(inst.local_tick(), 7);
}
#[test]
fn apply_schedule_delta_add_inserts_into_scheduler() {
let mut inst = Instance::new(id(1), cfg());
let stage = StepStage {
schedule_deltas: vec![ScheduledEntryDelta::Add(ScheduledEntry {
id: ScheduledActionId::new(1).unwrap(),
at: Tick(10),
actor: None,
principal: Principal::System,
action_type_code: TypeCode(0),
action_bytes: vec![1, 2, 3],
})],
..Default::default()
};
apply_stage(&mut inst, stage);
assert_eq!(inst.scheduler().len(), 1);
}
#[test]
fn discard_stage_is_noop() {
let mut inst = Instance::new(id(1), cfg());
let stage = StepStage {
state_ops: vec![StagedStateDelta::SpawnEntity {
id: entity(1),
meta: EntityMeta {
owner: Principal::System,
created: Tick(0),
},
}],
local_tick_delta: 99,
..Default::default()
};
discard_stage(&mut inst, stage);
assert_eq!(inst.entities_len(), 0);
assert_eq!(inst.local_tick(), 0);
}
}