use std::collections::VecDeque;
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use crate::abi::{EntityId, InstanceId, Principal, RouteId, TypeCode};
use crate::state::ledger::ResourceLedger;
use crate::state::{EntityMeta, ScheduledActionId, ScheduledEntry};
use super::event::{KernelEvent, ObserverHandle};
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub(crate) struct StepStage {
pub state_ops: Vec<StagedStateDelta>,
pub events: VecDeque<KernelEvent>,
pub schedule_deltas: Vec<ScheduledEntryDelta>,
pub pending_signals: Vec<PendingSignal>,
pub id_counters: IdCountersDelta,
pub ledger_delta: ResourceLedgerDelta,
pub wall_remainder_delta: u128,
pub local_tick_delta: u64,
pub observer_eviction_pending: Vec<ObserverHandle>,
}
impl StepStage {
pub(crate) fn clear(&mut self) {
let Self {
state_ops,
events,
schedule_deltas,
pending_signals,
id_counters,
ledger_delta,
wall_remainder_delta,
local_tick_delta,
observer_eviction_pending,
} = self;
state_ops.clear();
events.clear();
schedule_deltas.clear();
pending_signals.clear();
*id_counters = IdCountersDelta::default();
ledger_delta.ops.clear();
*wall_remainder_delta = 0;
*local_tick_delta = 0;
observer_eviction_pending.clear();
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) enum StagedStateDelta {
SpawnEntity {
id: EntityId,
meta: EntityMeta,
},
DespawnEntity {
id: EntityId,
},
SetComponent {
entity: EntityId,
type_code: TypeCode,
bytes: Bytes,
size: u64,
},
RemoveComponent {
entity: EntityId,
type_code: TypeCode,
size: u64,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) enum ScheduledEntryDelta {
Add(ScheduledEntry),
Remove(ScheduledActionId),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct PendingSignal {
pub target: InstanceId,
pub route: RouteId,
pub payload: Bytes,
pub principal: Principal,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub(crate) struct IdCountersDelta {
pub next_entity_advance: u64,
pub next_scheduled_advance: u64,
pub next_source_seq_advance: u64,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub(crate) struct ResourceLedgerDelta {
pub ops: Vec<LedgerOp>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) enum LedgerOp {
AddEntity(EntityId),
RemoveEntity(EntityId),
AddComponent {
entity: EntityId,
type_code: TypeCode,
size: u64,
},
RemoveComponent {
entity: EntityId,
type_code: TypeCode,
size: u64,
},
}
pub(crate) fn projected_component_bytes(
baseline: u64,
stage: &StepStage,
ledger: &ResourceLedger,
) -> u64 {
let mut total = baseline;
for op in &stage.ledger_delta.ops {
match op {
LedgerOp::AddComponent { size, .. } => {
total = total.saturating_add(*size);
}
LedgerOp::RemoveComponent {
entity, type_code, ..
} => {
let freed = ledger.component_size(*entity, *type_code).unwrap_or(0);
total = total.saturating_sub(freed);
}
LedgerOp::AddEntity(_) | LedgerOp::RemoveEntity(_) => {}
}
}
total
}
#[cfg(test)]
mod tests {
use super::*;
use crate::abi::{CapabilityMask, Tick};
#[test]
fn step_stage_default_all_buckets_empty() {
let s = StepStage::default();
assert!(s.state_ops.is_empty());
assert!(s.events.is_empty());
assert!(s.schedule_deltas.is_empty());
assert!(s.pending_signals.is_empty());
assert_eq!(s.id_counters.next_entity_advance, 0);
assert_eq!(s.id_counters.next_scheduled_advance, 0);
assert_eq!(s.id_counters.next_source_seq_advance, 0);
assert!(s.ledger_delta.ops.is_empty());
assert_eq!(s.wall_remainder_delta, 0);
assert_eq!(s.local_tick_delta, 0);
assert!(s.observer_eviction_pending.is_empty());
}
#[test]
fn clear_resets_all_buckets() {
let id = EntityId::new(1).unwrap();
let mut s = StepStage::default();
s.state_ops.push(StagedStateDelta::DespawnEntity { id });
s.events.push_back(KernelEvent::ActionExecuted {
instance: InstanceId::new(1).unwrap(),
action_type: TypeCode(1),
at: Tick(1),
});
s.schedule_deltas
.push(ScheduledEntryDelta::Remove(ScheduledActionId::new(1).unwrap()));
s.pending_signals.push(PendingSignal {
target: InstanceId::new(1).unwrap(),
route: RouteId(1),
payload: Bytes::from_static(b"x"),
principal: Principal::System,
});
s.id_counters.next_entity_advance = 9;
s.id_counters.next_scheduled_advance = 9;
s.id_counters.next_source_seq_advance = 9;
s.ledger_delta.ops.push(LedgerOp::AddEntity(id));
s.wall_remainder_delta = 12345;
s.local_tick_delta = 7;
s.clear();
assert!(s.state_ops.is_empty());
assert!(s.events.is_empty());
assert!(s.schedule_deltas.is_empty());
assert!(s.pending_signals.is_empty());
assert_eq!(s.id_counters.next_entity_advance, 0);
assert_eq!(s.id_counters.next_scheduled_advance, 0);
assert_eq!(s.id_counters.next_source_seq_advance, 0);
assert!(s.ledger_delta.ops.is_empty());
assert_eq!(s.wall_remainder_delta, 0);
assert_eq!(s.local_tick_delta, 0);
assert!(s.observer_eviction_pending.is_empty());
}
#[test]
fn staged_state_delta_variants_clone() {
let id = EntityId::new(1).unwrap();
let meta = EntityMeta {
owner: Principal::System,
created: Tick(0),
};
let _ = StagedStateDelta::SpawnEntity { id, meta }.clone();
let _ = StagedStateDelta::DespawnEntity { id }.clone();
let _ = StagedStateDelta::SetComponent {
entity: id,
type_code: TypeCode(1),
bytes: Bytes::from_static(b"x"),
size: 1,
}
.clone();
let _ = StagedStateDelta::RemoveComponent {
entity: id,
type_code: TypeCode(1),
size: 1,
}
.clone();
}
#[test]
fn scheduled_entry_delta_variants() {
let entry = ScheduledEntry {
id: ScheduledActionId::new(1).unwrap(),
at: Tick(0),
actor: None,
principal: Principal::System,
action_type_code: TypeCode(0),
action_bytes: vec![],
caps_ceiling: CapabilityMask::all(),
};
let _ = ScheduledEntryDelta::Add(entry).clone();
let _ = ScheduledEntryDelta::Remove(ScheduledActionId::new(1).unwrap()).clone();
}
#[test]
fn projected_oversized_add_saturates_no_negative_wrap() {
let id = EntityId::new(1).unwrap();
let mut stage = StepStage::default();
stage.ledger_delta.ops.push(LedgerOp::AddComponent {
entity: id,
type_code: TypeCode(1),
size: u64::MAX,
});
let ledger = ResourceLedger::new();
assert_eq!(projected_component_bytes(0, &stage, &ledger), u64::MAX);
}
#[test]
fn projected_remove_credits_stored_size_not_caller_size() {
let id = EntityId::new(1).unwrap();
let mut ledger = ResourceLedger::new();
ledger.add_entity(id);
ledger.add_component(id, TypeCode(1), 100);
let mut stage = StepStage::default();
stage.ledger_delta.ops.push(LedgerOp::RemoveComponent {
entity: id,
type_code: TypeCode(1),
size: u64::MAX, });
assert_eq!(projected_component_bytes(100, &stage, &ledger), 0);
}
#[test]
fn projected_remove_of_unaccounted_component_credits_zero() {
let id = EntityId::new(1).unwrap();
let ledger = ResourceLedger::new();
let mut stage = StepStage::default();
stage.ledger_delta.ops.push(LedgerOp::RemoveComponent {
entity: id,
type_code: TypeCode(1),
size: 50,
});
assert_eq!(projected_component_bytes(0, &stage, &ledger), 0);
}
#[test]
fn ledger_op_variants() {
let id = EntityId::new(1).unwrap();
let _ = LedgerOp::AddEntity(id).clone();
let _ = LedgerOp::RemoveEntity(id).clone();
let _ = LedgerOp::AddComponent {
entity: id,
type_code: TypeCode(1),
size: 100,
}
.clone();
let _ = LedgerOp::RemoveComponent {
entity: id,
type_code: TypeCode(1),
size: 100,
}
.clone();
}
#[test]
fn pending_signal_construction() {
let s = PendingSignal {
target: InstanceId::new(1).unwrap(),
route: RouteId(1),
payload: Bytes::from_static(b"hello"),
principal: Principal::System,
};
assert_eq!(s.payload.len(), 5);
}
}