1use bytes::Bytes;
10use serde::{Deserialize, Serialize};
11use std::collections::{BTreeMap, VecDeque};
12
13use crate::abi::{EntityId, InstanceId, Principal, RouteId, Tick, TypeCode};
14use crate::state::config::InstanceConfig;
15use crate::state::ledger::ResourceLedger;
16use crate::state::scheduler::Scheduler;
17
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
20pub struct EntityMeta {
21 pub owner: Principal,
23 pub created: Tick,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
31pub(crate) struct InboundSignal {
32 pub from: InstanceId,
34 pub principal: Principal,
36 pub payload: Bytes,
38 pub seq: u64,
40}
41
42#[derive(Debug, Default, Clone, Serialize, Deserialize)]
43pub(crate) struct IdCounters {
44 pub next_entity: u64,
45 pub next_scheduled: u64,
46 pub next_source_seq: u64,
47}
48
49pub(crate) struct Instance {
50 #[cfg_attr(not(test), allow(dead_code))]
54 id: InstanceId,
55 config: InstanceConfig,
56 entities: BTreeMap<EntityId, EntityMeta>,
57 components: BTreeMap<(EntityId, TypeCode), Bytes>,
59 scheduler: Scheduler,
60 id_counters: IdCounters,
61 ledger: ResourceLedger,
62 inflight_refs: BTreeMap<RouteId, u32>,
64 signal_inbox: BTreeMap<RouteId, VecDeque<InboundSignal>>,
68 inbox_seq: u64,
70 wall_remainder: u128,
72 local_tick: u64,
74}
75
76impl Instance {
77 pub(crate) fn new(id: InstanceId, config: InstanceConfig) -> Self {
78 Self {
79 id,
80 config,
81 entities: BTreeMap::new(),
82 components: BTreeMap::new(),
83 scheduler: Scheduler::new(),
84 id_counters: IdCounters::default(),
85 ledger: ResourceLedger::new(),
86 inflight_refs: BTreeMap::new(),
87 signal_inbox: BTreeMap::new(),
88 inbox_seq: 0,
89 wall_remainder: 0,
90 local_tick: 0,
91 }
92 }
93
94 #[inline]
100 pub(crate) fn id(&self) -> InstanceId {
101 self.id
102 }
103 #[inline]
104 pub(crate) fn config(&self) -> &InstanceConfig {
105 &self.config
106 }
107 #[inline]
108 pub(crate) fn entities_len(&self) -> usize {
109 self.entities.len()
110 }
111 #[inline]
112 pub(crate) fn components_len(&self) -> usize {
113 self.components.len()
114 }
115 #[inline]
116 pub(crate) fn local_tick(&self) -> u64 {
117 self.local_tick
118 }
119 #[cfg_attr(not(test), allow(dead_code))]
120 #[inline]
121 pub(crate) fn wall_remainder(&self) -> u128 {
122 self.wall_remainder
123 }
124 #[inline]
125 pub(crate) fn ledger(&self) -> &ResourceLedger {
126 &self.ledger
127 }
128 #[cfg_attr(not(test), allow(dead_code))]
129 #[inline]
130 pub(crate) fn scheduler(&self) -> &Scheduler {
131 &self.scheduler
132 }
133 #[cfg_attr(not(test), allow(dead_code))]
134 #[inline]
135 pub(crate) fn id_counters(&self) -> &IdCounters {
136 &self.id_counters
137 }
138 #[cfg_attr(not(test), allow(dead_code))]
139 #[inline]
140 pub(crate) fn inflight_refs_len(&self) -> usize {
141 self.inflight_refs.len()
142 }
143 #[cfg_attr(not(test), allow(dead_code))]
144 #[inline]
145 pub(crate) fn inflight_refs_for(&self, route: RouteId) -> u32 {
146 *self.inflight_refs.get(&route).unwrap_or(&0)
147 }
148
149 pub(crate) fn entity_meta(&self, entity: EntityId) -> Option<&EntityMeta> {
151 self.entities.get(&entity)
152 }
153
154 pub(crate) fn component(&self, entity: EntityId, type_code: TypeCode) -> Option<&Bytes> {
157 self.components.get(&(entity, type_code))
158 }
159
160 pub(crate) fn entities_iter(&self) -> impl Iterator<Item = (EntityId, &EntityMeta)> + '_ {
164 self.entities.iter().map(|(id, meta)| (*id, meta))
165 }
166
167 pub(crate) fn components_by_type_iter(
171 &self,
172 type_code: TypeCode,
173 ) -> impl Iterator<Item = (EntityId, &Bytes)> + '_ {
174 self.components
175 .iter()
176 .filter_map(move |((eid, tc), bytes)| {
177 if *tc == type_code {
178 Some((*eid, bytes))
179 } else {
180 None
181 }
182 })
183 }
184
185 pub(crate) fn insert_entity(&mut self, id: EntityId, meta: EntityMeta) {
194 self.entities.insert(id, meta);
195 }
196
197 pub(crate) fn remove_entity(&mut self, id: EntityId) -> Option<EntityMeta> {
198 let orphaned: Vec<(EntityId, TypeCode)> = self
203 .components
204 .range((id, TypeCode(0))..=(id, TypeCode(u32::MAX)))
205 .map(|(k, _)| *k)
206 .collect();
207 for k in orphaned {
208 self.components.remove(&k);
209 }
210 self.entities.remove(&id)
211 }
212
213 pub(crate) fn insert_component(&mut self, key: (EntityId, TypeCode), bytes: Bytes) {
214 self.components.insert(key, bytes);
215 }
216
217 pub(crate) fn remove_component(&mut self, key: (EntityId, TypeCode)) -> Option<Bytes> {
218 self.components.remove(&key)
219 }
220
221 pub(crate) fn scheduler_mut(&mut self) -> &mut Scheduler {
222 &mut self.scheduler
223 }
224
225 pub(crate) fn ledger_mut(&mut self) -> &mut ResourceLedger {
226 &mut self.ledger
227 }
228
229 pub(crate) fn id_counters_mut(&mut self) -> &mut IdCounters {
230 &mut self.id_counters
231 }
232
233 pub(crate) fn inflight_refs_mut(&mut self) -> &mut BTreeMap<RouteId, u32> {
234 &mut self.inflight_refs
235 }
236
237 pub(crate) fn advance_wall_remainder(&mut self, delta: u128) {
238 self.wall_remainder = self.wall_remainder.saturating_add(delta);
239 }
240
241 pub(crate) fn advance_local_tick(&mut self, delta: u64) {
242 self.local_tick = self.local_tick.saturating_add(delta);
243 }
244
245 pub(crate) fn id_counters_snapshot(&self) -> IdCounters {
248 self.id_counters.clone()
249 }
250
251 pub(crate) fn to_snapshot(&self) -> InstanceSnapshot {
254 InstanceSnapshot {
255 id: self.id,
256 config: self.config.clone(),
257 entities: self.entities.clone(),
258 components: self.components.clone(),
259 scheduler: self.scheduler.clone(),
260 id_counters: self.id_counters.clone(),
261 ledger: self.ledger.clone(),
262 inflight_refs: self.inflight_refs.clone(),
263 signal_inbox: self.signal_inbox.clone(),
264 inbox_seq: self.inbox_seq,
265 wall_remainder: self.wall_remainder,
266 local_tick: self.local_tick,
267 }
268 }
269
270 pub(crate) fn from_snapshot(snap: InstanceSnapshot) -> Self {
272 Self {
273 id: snap.id,
274 config: snap.config,
275 entities: snap.entities,
276 components: snap.components,
277 scheduler: snap.scheduler,
278 id_counters: snap.id_counters,
279 ledger: snap.ledger,
280 inflight_refs: snap.inflight_refs,
281 signal_inbox: snap.signal_inbox,
282 inbox_seq: snap.inbox_seq,
283 wall_remainder: snap.wall_remainder,
284 local_tick: snap.local_tick,
285 }
286 }
287
288 pub(crate) fn state_digest(&self) -> [u8; 32] {
302 match postcard::to_allocvec(&self.to_snapshot()) {
303 Ok(bytes) => *blake3::hash(&bytes).as_bytes(),
304 Err(_) => [0u8; 32],
305 }
306 }
307
308 pub(crate) fn deliver_signal(&mut self, route: RouteId, mut signal: InboundSignal) -> u64 {
311 let seq = self.inbox_seq;
312 self.inbox_seq = self.inbox_seq.saturating_add(1);
313 signal.seq = seq;
314 self.signal_inbox.entry(route).or_default().push_back(signal);
315 seq
316 }
317
318 #[cfg_attr(not(test), allow(dead_code))]
320 pub(crate) fn inbox_len(&self, route: RouteId) -> usize {
321 self.signal_inbox.get(&route).map_or(0, VecDeque::len)
322 }
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct InstanceSnapshot {
331 pub(crate) id: InstanceId,
332 pub(crate) config: InstanceConfig,
333 pub(crate) entities: BTreeMap<EntityId, EntityMeta>,
334 pub(crate) components: BTreeMap<(EntityId, TypeCode), Bytes>,
335 pub(crate) scheduler: Scheduler,
336 pub(crate) id_counters: IdCounters,
337 pub(crate) ledger: ResourceLedger,
338 pub(crate) inflight_refs: BTreeMap<RouteId, u32>,
339 pub(crate) signal_inbox: BTreeMap<RouteId, VecDeque<InboundSignal>>,
340 pub(crate) inbox_seq: u64,
341 pub(crate) wall_remainder: u128,
342 pub(crate) local_tick: u64,
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348 use crate::abi::CapabilityMask;
349 use crate::state::quota::QuotaReductionPolicy;
350
351 fn id(n: u64) -> InstanceId {
352 InstanceId::new(n).unwrap()
353 }
354
355 fn cfg() -> InstanceConfig {
356 InstanceConfig {
357 default_caps: CapabilityMask::default(),
358 max_entities: 100,
359 max_scheduled: 1000,
360 max_inbox_per_route: 16,
361 memory_budget_bytes: 1 << 20,
362 parent: None,
363 quota_reduction: QuotaReductionPolicy::default(),
364 }
365 }
366
367 #[test]
368 fn instance_new_initial_state() {
369 let inst = Instance::new(id(1), cfg());
370 assert_eq!(inst.id(), id(1));
371 assert_eq!(inst.entities_len(), 0);
372 assert_eq!(inst.components_len(), 0);
373 assert_eq!(inst.local_tick(), 0);
374 assert_eq!(inst.wall_remainder(), 0);
375 assert_eq!(inst.ledger().total_entities(), 0);
376 assert!(inst.scheduler().is_empty());
377 assert_eq!(inst.inflight_refs_len(), 0);
378 }
379
380 #[test]
381 fn instance_id_counters_default_zero() {
382 let inst = Instance::new(id(1), cfg());
383 let c = inst.id_counters();
384 assert_eq!(c.next_entity, 0);
385 assert_eq!(c.next_scheduled, 0);
386 assert_eq!(c.next_source_seq, 0);
387 }
388
389 #[test]
390 fn instance_config_carried_through() {
391 let mut config = cfg();
392 config.max_entities = 7;
393 config.parent = id(99).into(); let inst = Instance::new(id(2), config);
395 assert_eq!(inst.config().max_entities, 7);
396 assert_eq!(inst.config().parent, Some(id(99)));
397 }
398
399 #[test]
400 fn multiple_instances_independent() {
401 let inst1 = Instance::new(id(1), cfg());
402 let inst2 = Instance::new(id(2), cfg());
403 assert_ne!(inst1.id(), inst2.id());
404 assert!(inst1.scheduler().is_empty());
405 assert!(inst2.scheduler().is_empty());
406 assert_eq!(inst1.ledger().total_bytes(), 0);
407 assert_eq!(inst2.ledger().total_bytes(), 0);
408 }
409
410 #[test]
411 fn entity_meta_clone_preserves_fields() {
412 let m1 = EntityMeta {
413 owner: Principal::System,
414 created: Tick(5),
415 };
416 let m2 = m1.clone();
417 assert!(matches!(m2.owner, Principal::System));
418 assert_eq!(m2.created, Tick(5));
419 }
420
421 #[test]
422 fn id_counters_default_clone_independent() {
423 let c1 = IdCounters::default();
424 let mut c2 = c1.clone();
425 c2.next_entity = 10;
426 assert_eq!(c1.next_entity, 0);
427 assert_eq!(c2.next_entity, 10);
428 }
429
430 }