Skip to main content

stet_core/
dual_array_store.rs

1// stet - A PostScript Interpreter
2// Copyright (c) 2026 Scott Bowman
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! Dual-arena array storage: routes to global or local `ArrayStore`
6//! based on the tag bit in `EntityId`.
7
8use crate::array_store::ArrayStore;
9use crate::entity_table::EntityMeta;
10use crate::object::{EntityId, PsObject};
11
12/// Dual-arena array store with separate global and local backing stores.
13pub struct DualArrayStore {
14    pub global: ArrayStore,
15    pub local: ArrayStore,
16}
17
18impl DualArrayStore {
19    pub fn new() -> Self {
20        Self {
21            global: ArrayStore::new(),
22            local: ArrayStore::new(),
23        }
24    }
25
26    /// Combined high-water mark of both arenas, in object slots.
27    /// See [`ArrayStore::allocated_objects`].
28    pub fn allocated_objects(&self) -> usize {
29        self.global.allocated_objects() + self.local.allocated_objects()
30    }
31
32    #[inline]
33    fn store(&self, entity: EntityId) -> &ArrayStore {
34        if entity.is_global() {
35            &self.global
36        } else {
37            &self.local
38        }
39    }
40
41    #[inline]
42    fn store_mut(&mut self, entity: EntityId) -> &mut ArrayStore {
43        if entity.is_global() {
44            &mut self.global
45        } else {
46            &mut self.local
47        }
48    }
49
50    // --- Allocation ---
51
52    /// Allocate `len` null-filled slots in local VM, stamped as if no `save` were outstanding.
53    ///
54    /// The entity is stamped `save_level = 0`, `global = false`,
55    /// `created_after_save = 0` — it claims to predate every outstanding
56    /// `save`. That is only true during bootstrap, before any `save` can have
57    /// happened. Everywhere else prefer the VM-aware helpers in
58    /// `stet_ops::vm_ops` (`alloc_dict` / `alloc_array` / `alloc_string`, or
59    /// their `_in` variants): an entity mis-stamped this way is invisible to
60    /// the `invalidrestore` check while still being released by the matching
61    /// `restore`.
62    pub fn allocate_at_level_zero(&mut self, len: usize) -> EntityId {
63        self.local.allocate(len)
64    }
65
66    /// Allocate and copy `items` into local VM, stamped as if no `save` were outstanding.
67    ///
68    /// The entity is stamped `save_level = 0`, `global = false`,
69    /// `created_after_save = 0` — it claims to predate every outstanding
70    /// `save`. That is only true during bootstrap, before any `save` can have
71    /// happened. Everywhere else prefer the VM-aware helpers in
72    /// `stet_ops::vm_ops` (`alloc_dict` / `alloc_array` / `alloc_string`, or
73    /// their `_in` variants): an entity mis-stamped this way is invisible to
74    /// the `invalidrestore` check while still being released by the matching
75    /// `restore`.
76    pub fn allocate_from_at_level_zero(&mut self, items: &[PsObject]) -> EntityId {
77        self.local.allocate_from(items)
78    }
79
80    /// Allocate and copy `items` with a specific save level and global flag.
81    pub fn allocate_from_with(
82        &mut self,
83        items: &[PsObject],
84        save_level: u16,
85        global: bool,
86        created_after_save: u32,
87    ) -> EntityId {
88        if global {
89            self.global
90                .allocate_from_with(items, save_level, global, created_after_save)
91        } else {
92            self.local
93                .allocate_from_with(items, save_level, global, created_after_save)
94        }
95    }
96
97    /// Allocate with a specific save level and global flag.
98    pub fn allocate_with(
99        &mut self,
100        len: usize,
101        save_level: u16,
102        global: bool,
103        created_after_save: u32,
104    ) -> EntityId {
105        if global {
106            self.global
107                .allocate_with(len, save_level, global, created_after_save)
108        } else {
109            self.local
110                .allocate_with(len, save_level, global, created_after_save)
111        }
112    }
113
114    // --- Access ---
115
116    /// Get a slice of array elements.
117    pub fn get(&self, entity: EntityId, start: u32, len: u32) -> &[PsObject] {
118        self.store(entity).get(entity, start, len)
119    }
120
121    /// Get a mutable slice of array elements.
122    pub fn get_mut(&mut self, entity: EntityId, start: u32, len: u32) -> &mut [PsObject] {
123        self.store_mut(entity).get_mut(entity, start, len)
124    }
125
126    /// Get a single element.
127    #[inline]
128    pub fn get_element(&self, entity: EntityId, index: u32) -> PsObject {
129        self.store(entity).get_element(entity, index)
130    }
131
132    /// Set a single element.
133    pub fn set_element(&mut self, entity: EntityId, index: u32, obj: PsObject) {
134        self.store_mut(entity).set_element(entity, index, obj);
135    }
136
137    // --- COW ---
138
139    /// COW copy (always local — global entities skip COW).
140    pub fn cow_copy(&mut self, entity: EntityId) -> EntityId {
141        debug_assert!(!entity.is_global(), "COW copy on global entity");
142        self.local.cow_copy(entity)
143    }
144
145    /// Swap offsets between two entities (used by restore, always local).
146    pub fn swap_offsets(&mut self, a: EntityId, b: EntityId) {
147        debug_assert!(
148            !a.is_global() && !b.is_global(),
149            "swap_offsets on global entity"
150        );
151        self.local.swap_offsets(a, b);
152    }
153
154    // --- Metadata access ---
155
156    /// Get entity metadata (read-only).
157    pub fn entity_meta(&self, entity: EntityId) -> &EntityMeta {
158        self.store(entity).entities.get(entity)
159    }
160
161    /// Get mutable entity metadata.
162    pub fn entity_meta_mut(&mut self, entity: EntityId) -> &mut EntityMeta {
163        self.store_mut(entity).entities.get_mut(entity)
164    }
165
166    // --- Stats ---
167
168    /// Total entity count across both stores.
169    pub fn entity_count(&self) -> usize {
170        self.local.entities.len() + self.global.entities.len()
171    }
172
173    /// Reset local VM (for job boundary cleanup).
174    pub fn reset_local(&mut self) {
175        self.local = ArrayStore::new();
176    }
177}
178
179impl Default for DualArrayStore {
180    fn default() -> Self {
181        Self::new()
182    }
183}