1use bytes::Bytes;
10use serde::{Deserialize, Serialize};
11use std::collections::BTreeMap;
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, Default, Clone, Serialize, Deserialize)]
28pub(crate) struct IdCounters {
29 pub next_entity: u64,
30 pub next_scheduled: u64,
31 pub next_source_seq: u64,
32}
33
34pub(crate) struct Instance {
35 #[cfg_attr(not(test), allow(dead_code))]
39 id: InstanceId,
40 config: InstanceConfig,
41 entities: BTreeMap<EntityId, EntityMeta>,
42 components: BTreeMap<(EntityId, TypeCode), Bytes>,
44 scheduler: Scheduler,
45 id_counters: IdCounters,
46 ledger: ResourceLedger,
47 inflight_refs: BTreeMap<RouteId, u32>,
49 wall_remainder: u128,
51 local_tick: u64,
53}
54
55impl Instance {
56 pub(crate) fn new(id: InstanceId, config: InstanceConfig) -> Self {
57 Self {
58 id,
59 config,
60 entities: BTreeMap::new(),
61 components: BTreeMap::new(),
62 scheduler: Scheduler::new(),
63 id_counters: IdCounters::default(),
64 ledger: ResourceLedger::new(),
65 inflight_refs: BTreeMap::new(),
66 wall_remainder: 0,
67 local_tick: 0,
68 }
69 }
70
71 #[inline]
77 pub(crate) fn id(&self) -> InstanceId {
78 self.id
79 }
80 #[inline]
81 pub(crate) fn config(&self) -> &InstanceConfig {
82 &self.config
83 }
84 #[inline]
85 pub(crate) fn entities_len(&self) -> usize {
86 self.entities.len()
87 }
88 #[inline]
89 pub(crate) fn components_len(&self) -> usize {
90 self.components.len()
91 }
92 #[inline]
93 pub(crate) fn local_tick(&self) -> u64 {
94 self.local_tick
95 }
96 #[cfg_attr(not(test), allow(dead_code))]
97 #[inline]
98 pub(crate) fn wall_remainder(&self) -> u128 {
99 self.wall_remainder
100 }
101 #[inline]
102 pub(crate) fn ledger(&self) -> &ResourceLedger {
103 &self.ledger
104 }
105 #[cfg_attr(not(test), allow(dead_code))]
106 #[inline]
107 pub(crate) fn scheduler(&self) -> &Scheduler {
108 &self.scheduler
109 }
110 #[cfg_attr(not(test), allow(dead_code))]
111 #[inline]
112 pub(crate) fn id_counters(&self) -> &IdCounters {
113 &self.id_counters
114 }
115 #[cfg_attr(not(test), allow(dead_code))]
116 #[inline]
117 pub(crate) fn inflight_refs_len(&self) -> usize {
118 self.inflight_refs.len()
119 }
120 #[cfg_attr(not(test), allow(dead_code))]
121 #[inline]
122 pub(crate) fn inflight_refs_for(&self, route: RouteId) -> u32 {
123 *self.inflight_refs.get(&route).unwrap_or(&0)
124 }
125
126 pub(crate) fn entity_meta(&self, entity: EntityId) -> Option<&EntityMeta> {
128 self.entities.get(&entity)
129 }
130
131 pub(crate) fn component(&self, entity: EntityId, type_code: TypeCode) -> Option<&Bytes> {
134 self.components.get(&(entity, type_code))
135 }
136
137 pub(crate) fn entities_iter(&self) -> impl Iterator<Item = (EntityId, &EntityMeta)> + '_ {
141 self.entities.iter().map(|(id, meta)| (*id, meta))
142 }
143
144 pub(crate) fn components_by_type_iter(
148 &self,
149 type_code: TypeCode,
150 ) -> impl Iterator<Item = (EntityId, &Bytes)> + '_ {
151 self.components
152 .iter()
153 .filter_map(move |((eid, tc), bytes)| {
154 if *tc == type_code {
155 Some((*eid, bytes))
156 } else {
157 None
158 }
159 })
160 }
161
162 pub(crate) fn insert_entity(&mut self, id: EntityId, meta: EntityMeta) {
171 self.entities.insert(id, meta);
172 }
173
174 pub(crate) fn remove_entity(&mut self, id: EntityId) -> Option<EntityMeta> {
175 let orphaned: Vec<(EntityId, TypeCode)> = self
180 .components
181 .range((id, TypeCode(0))..=(id, TypeCode(u32::MAX)))
182 .map(|(k, _)| *k)
183 .collect();
184 for k in orphaned {
185 self.components.remove(&k);
186 }
187 self.entities.remove(&id)
188 }
189
190 pub(crate) fn insert_component(&mut self, key: (EntityId, TypeCode), bytes: Bytes) {
191 self.components.insert(key, bytes);
192 }
193
194 pub(crate) fn remove_component(&mut self, key: (EntityId, TypeCode)) -> Option<Bytes> {
195 self.components.remove(&key)
196 }
197
198 pub(crate) fn scheduler_mut(&mut self) -> &mut Scheduler {
199 &mut self.scheduler
200 }
201
202 pub(crate) fn ledger_mut(&mut self) -> &mut ResourceLedger {
203 &mut self.ledger
204 }
205
206 pub(crate) fn id_counters_mut(&mut self) -> &mut IdCounters {
207 &mut self.id_counters
208 }
209
210 pub(crate) fn inflight_refs_mut(&mut self) -> &mut BTreeMap<RouteId, u32> {
211 &mut self.inflight_refs
212 }
213
214 pub(crate) fn advance_wall_remainder(&mut self, delta: u128) {
215 self.wall_remainder = self.wall_remainder.saturating_add(delta);
216 }
217
218 pub(crate) fn advance_local_tick(&mut self, delta: u64) {
219 self.local_tick = self.local_tick.saturating_add(delta);
220 }
221
222 pub(crate) fn id_counters_snapshot(&self) -> IdCounters {
225 self.id_counters.clone()
226 }
227
228 pub(crate) fn to_snapshot(&self) -> InstanceSnapshot {
231 InstanceSnapshot {
232 id: self.id,
233 config: self.config.clone(),
234 entities: self.entities.clone(),
235 components: self.components.clone(),
236 scheduler: self.scheduler.clone(),
237 id_counters: self.id_counters.clone(),
238 ledger: self.ledger.clone(),
239 inflight_refs: self.inflight_refs.clone(),
240 wall_remainder: self.wall_remainder,
241 local_tick: self.local_tick,
242 }
243 }
244
245 pub(crate) fn from_snapshot(snap: InstanceSnapshot) -> Self {
247 Self {
248 id: snap.id,
249 config: snap.config,
250 entities: snap.entities,
251 components: snap.components,
252 scheduler: snap.scheduler,
253 id_counters: snap.id_counters,
254 ledger: snap.ledger,
255 inflight_refs: snap.inflight_refs,
256 wall_remainder: snap.wall_remainder,
257 local_tick: snap.local_tick,
258 }
259 }
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct InstanceSnapshot {
268 pub(crate) id: InstanceId,
269 pub(crate) config: InstanceConfig,
270 pub(crate) entities: BTreeMap<EntityId, EntityMeta>,
271 pub(crate) components: BTreeMap<(EntityId, TypeCode), Bytes>,
272 pub(crate) scheduler: Scheduler,
273 pub(crate) id_counters: IdCounters,
274 pub(crate) ledger: ResourceLedger,
275 pub(crate) inflight_refs: BTreeMap<RouteId, u32>,
276 pub(crate) wall_remainder: u128,
277 pub(crate) local_tick: u64,
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use crate::abi::CapabilityMask;
284 use crate::state::quota::QuotaReductionPolicy;
285
286 fn id(n: u64) -> InstanceId {
287 InstanceId::new(n).unwrap()
288 }
289
290 fn cfg() -> InstanceConfig {
291 InstanceConfig {
292 default_caps: CapabilityMask::default(),
293 max_entities: 100,
294 max_scheduled: 1000,
295 memory_budget_bytes: 1 << 20,
296 parent: None,
297 quota_reduction: QuotaReductionPolicy::default(),
298 }
299 }
300
301 #[test]
302 fn instance_new_initial_state() {
303 let inst = Instance::new(id(1), cfg());
304 assert_eq!(inst.id(), id(1));
305 assert_eq!(inst.entities_len(), 0);
306 assert_eq!(inst.components_len(), 0);
307 assert_eq!(inst.local_tick(), 0);
308 assert_eq!(inst.wall_remainder(), 0);
309 assert_eq!(inst.ledger().total_entities(), 0);
310 assert!(inst.scheduler().is_empty());
311 assert_eq!(inst.inflight_refs_len(), 0);
312 }
313
314 #[test]
315 fn instance_id_counters_default_zero() {
316 let inst = Instance::new(id(1), cfg());
317 let c = inst.id_counters();
318 assert_eq!(c.next_entity, 0);
319 assert_eq!(c.next_scheduled, 0);
320 assert_eq!(c.next_source_seq, 0);
321 }
322
323 #[test]
324 fn instance_config_carried_through() {
325 let mut config = cfg();
326 config.max_entities = 7;
327 config.parent = id(99).into(); let inst = Instance::new(id(2), config);
329 assert_eq!(inst.config().max_entities, 7);
330 assert_eq!(inst.config().parent, Some(id(99)));
331 }
332
333 #[test]
334 fn multiple_instances_independent() {
335 let inst1 = Instance::new(id(1), cfg());
336 let inst2 = Instance::new(id(2), cfg());
337 assert_ne!(inst1.id(), inst2.id());
338 assert!(inst1.scheduler().is_empty());
339 assert!(inst2.scheduler().is_empty());
340 assert_eq!(inst1.ledger().total_bytes(), 0);
341 assert_eq!(inst2.ledger().total_bytes(), 0);
342 }
343
344 #[test]
345 fn entity_meta_clone_preserves_fields() {
346 let m1 = EntityMeta {
347 owner: Principal::System,
348 created: Tick(5),
349 };
350 let m2 = m1.clone();
351 assert!(matches!(m2.owner, Principal::System));
352 assert_eq!(m2.created, Tick(5));
353 }
354
355 #[test]
356 fn id_counters_default_clone_independent() {
357 let c1 = IdCounters::default();
358 let mut c2 = c1.clone();
359 c2.next_entity = 10;
360 assert_eq!(c1.next_entity, 0);
361 assert_eq!(c2.next_entity, 10);
362 }
363
364 }