Skip to main content

stet_core/
dual_string_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 string storage: routes to global or local `StringStore`
6//! based on the tag bit in `EntityId`.
7
8use crate::entity_table::EntityMeta;
9use crate::object::EntityId;
10use crate::string_store::StringStore;
11
12/// Dual-arena string store with separate global and local backing stores.
13pub struct DualStringStore {
14    pub global: StringStore,
15    pub local: StringStore,
16}
17
18impl DualStringStore {
19    pub fn new() -> Self {
20        Self {
21            global: StringStore::new(),
22            local: StringStore::new(),
23        }
24    }
25
26    /// Select the store for a given entity.
27    #[inline]
28    fn store(&self, entity: EntityId) -> &StringStore {
29        if entity.is_global() {
30            &self.global
31        } else {
32            &self.local
33        }
34    }
35
36    /// Select the mutable store for a given entity.
37    #[inline]
38    fn store_mut(&mut self, entity: EntityId) -> &mut StringStore {
39        if entity.is_global() {
40            &mut self.global
41        } else {
42            &mut self.local
43        }
44    }
45
46    // --- Allocation (defaults to local) ---
47
48    /// Allocate `len` zero-filled bytes in local VM.
49    pub fn allocate(&mut self, len: usize) -> EntityId {
50        self.local.allocate(len)
51    }
52
53    /// Allocate and copy `bytes` into local VM.
54    pub fn allocate_from(&mut self, bytes: &[u8]) -> EntityId {
55        self.local.allocate_from(bytes)
56    }
57
58    /// Allocate and copy `bytes` with a specific save level and global flag.
59    pub fn allocate_from_with(
60        &mut self,
61        bytes: &[u8],
62        save_level: u16,
63        global: bool,
64        created_after_save: u32,
65    ) -> EntityId {
66        if global {
67            self.global
68                .allocate_from_with(bytes, save_level, global, created_after_save)
69        } else {
70            self.local
71                .allocate_from_with(bytes, save_level, global, created_after_save)
72        }
73    }
74
75    /// Allocate with a specific save level and global flag.
76    pub fn allocate_with(
77        &mut self,
78        len: usize,
79        save_level: u16,
80        global: bool,
81        created_after_save: u32,
82    ) -> EntityId {
83        if global {
84            self.global
85                .allocate_with(len, save_level, global, created_after_save)
86        } else {
87            self.local
88                .allocate_with(len, save_level, global, created_after_save)
89        }
90    }
91
92    // --- Access ---
93
94    /// Get a slice of the string data.
95    pub fn get(&self, entity: EntityId, start: u32, len: u32) -> &[u8] {
96        self.store(entity).get(entity, start, len)
97    }
98
99    /// Get a mutable slice of the string data.
100    pub fn get_mut(&mut self, entity: EntityId, start: u32, len: u32) -> &mut [u8] {
101        self.store_mut(entity).get_mut(entity, start, len)
102    }
103
104    /// Set a single byte.
105    pub fn put_byte(&mut self, entity: EntityId, offset: u32, byte: u8) {
106        self.store_mut(entity).put_byte(entity, offset, byte);
107    }
108
109    /// Get a single byte.
110    pub fn get_byte(&self, entity: EntityId, offset: u32) -> u8 {
111        self.store(entity).get_byte(entity, offset)
112    }
113
114    // --- COW ---
115
116    /// COW copy (always local — global entities skip COW).
117    pub fn cow_copy(&mut self, entity: EntityId) -> EntityId {
118        debug_assert!(!entity.is_global(), "COW copy on global entity");
119        self.local.cow_copy(entity)
120    }
121
122    /// Swap offsets between two entities (used by restore, always local).
123    pub fn swap_offsets(&mut self, a: EntityId, b: EntityId) {
124        debug_assert!(
125            !a.is_global() && !b.is_global(),
126            "swap_offsets on global entity"
127        );
128        self.local.swap_offsets(a, b);
129    }
130
131    // --- Metadata access ---
132
133    /// Get entity metadata (read-only).
134    pub fn entity_meta(&self, entity: EntityId) -> &EntityMeta {
135        self.store(entity).entities.get(entity)
136    }
137
138    /// Get mutable entity metadata.
139    pub fn entity_meta_mut(&mut self, entity: EntityId) -> &mut EntityMeta {
140        self.store_mut(entity).entities.get_mut(entity)
141    }
142
143    // --- Stats ---
144
145    /// Access local backing data (for vmstatus approximation).
146    pub fn data(&self) -> &[u8] {
147        self.local.data()
148    }
149
150    /// Total entity count across both stores.
151    pub fn entity_count(&self) -> usize {
152        self.local.entities.len() + self.global.entities.len()
153    }
154
155    /// Reset local VM (for job boundary cleanup).
156    pub fn reset_local(&mut self) {
157        self.local = StringStore::new();
158    }
159}
160
161impl Default for DualStringStore {
162    fn default() -> Self {
163        Self::new()
164    }
165}