rune/runtime/globals.rs
1//! Storage for static items declared in a unit.
2
3use core::cell::RefCell;
4use core::fmt;
5
6use rust_alloc::rc::Rc;
7
8use crate::alloc::clone::TryClone;
9use crate::alloc::{self, Box, Vec};
10use crate::hash::ToTypeHash;
11use crate::runtime::{Unit, Value};
12use crate::sync::Arc;
13use crate::{Hash, ItemBuf};
14
15/// Storage for the static items declared by a [`Unit`].
16///
17/// A unit assigns every `static` item it declares a zero-indexed slot. The
18/// values living in those slots are *not* stored in the unit itself, they are
19/// stored here and handed to a virtual machine when it is called. This means a
20/// single compiled unit can back any number of independent states.
21///
22/// Every slot keeps track of whether it has been initialized. A slot which has
23/// an initializer is lazily initialized the first time a script reads it, but a
24/// caller can also assign a value up front, in which case the initializer is
25/// never evaluated.
26///
27/// The storage is a cheaply cloned handle. Cloning it does not copy the slots,
28/// it produces a second handle to the same storage, which is how a caller can
29/// keep reading and writing static items while a virtual machine using them is
30/// running.
31///
32/// # Examples
33///
34/// ```
35/// use rune::{Context, Diagnostics, Source, Sources, Vm};
36/// use rune::runtime::Globals;
37/// use rune::sync::Arc;
38///
39/// let context = Context::with_default_modules()?;
40/// let runtime = Arc::try_new(context.runtime()?)?;
41///
42/// let mut sources = Sources::new();
43/// sources.insert(Source::memory(r#"
44/// static COUNTER = 0;
45///
46/// pub fn main() {
47/// COUNTER += 1;
48/// }
49/// "#)?)?;
50///
51/// let mut diagnostics = Diagnostics::new();
52///
53/// let unit = rune::prepare(&mut sources)
54/// .with_context(&context)
55/// .with_diagnostics(&mut diagnostics)
56/// .build()?;
57///
58/// let unit = Arc::try_new(unit)?;
59/// let globals = Globals::new(unit.clone())?;
60///
61/// let mut vm = Vm::new(runtime, unit).with_globals(globals.clone());
62/// vm.call(["main"], ())?;
63/// vm.call(["main"], ())?;
64///
65/// let counter = globals.get(["COUNTER"])?.expect("COUNTER to be initialized");
66/// assert_eq!(rune::from_value::<i64>(counter)?, 2);
67/// # Ok::<_, rune::support::Error>(())
68/// ```
69#[derive(Default, Clone)]
70pub struct Globals {
71 /// `None` means that no storage has been configured. This exists so that
72 /// [`Globals::empty`] can be a `const fn`, which in turn keeps [`Vm::new`]
73 /// allocation free.
74 ///
75 /// The handle is non-atomically counted, since the storage holds [`Value`]s
76 /// and can therefore never be shared across threads anyway.
77 ///
78 /// [`Vm::new`]: crate::runtime::Vm::new
79 inner: Option<Rc<GlobalsInner>>,
80}
81
82/// The shared allocation backing a [`Globals`] handle.
83///
84/// This is only public to the crate so that the thread-local environment can
85/// store it as a raw pointer, the same way it stores the unit and the context.
86pub(crate) struct GlobalsInner {
87 unit: Arc<Unit>,
88 /// One slot per static declared by the unit. The number of statics is fixed
89 /// by the unit, so this is sized once and never grows.
90 ///
91 /// An uninitialized slot holds [`Value::empty`].
92 slots: RefCell<Box<[Value]>>,
93}
94
95impl Globals {
96 /// Construct an empty storage which has no slots.
97 ///
98 /// This is what a [`Vm`] is constructed with. Reading or writing a static
99 /// through it always errors, so a unit which declares statics needs real
100 /// storage through [`Vm::with_globals`].
101 ///
102 /// [`Vm`]: crate::runtime::Vm
103 /// [`Vm::with_globals`]: crate::runtime::Vm::with_globals
104 #[inline]
105 pub const fn empty() -> Self {
106 Self { inner: None }
107 }
108
109 /// Construct storage for all the statics declared by the given unit, with
110 /// every slot uninitialized.
111 pub fn new(unit: Arc<Unit>) -> alloc::Result<Self> {
112 let len = unit.globals_len();
113
114 let mut slots = Vec::try_with_capacity(len)?;
115
116 for _ in 0..len {
117 slots.try_push(Value::empty())?;
118 }
119
120 Ok(Self {
121 inner: Some(Rc::new(GlobalsInner {
122 unit,
123 slots: RefCell::new(slots.try_into_boxed_slice()?),
124 })),
125 })
126 }
127
128 /// Test if this storage has been configured for a unit.
129 ///
130 /// Note that this is not the same as [`is_empty`], since a unit which
131 /// declares no statics gets configured storage with no slots in it.
132 ///
133 /// [`is_empty`]: Globals::is_empty
134 #[inline]
135 pub fn is_configured(&self) -> bool {
136 self.inner.is_some()
137 }
138
139 /// The number of slots in this storage.
140 #[inline]
141 pub fn len(&self) -> usize {
142 match &self.inner {
143 Some(inner) => inner.slots.borrow().len(),
144 None => 0,
145 }
146 }
147
148 /// Test if this storage has no slots.
149 #[inline]
150 pub fn is_empty(&self) -> bool {
151 self.len() == 0
152 }
153
154 /// Test if two handles refer to the same storage.
155 #[inline]
156 pub fn is_same(&self, other: &Self) -> bool {
157 match (&self.inner, &other.inner) {
158 (Some(a), Some(b)) => Rc::ptr_eq(a, b),
159 (None, None) => true,
160 _ => false,
161 }
162 }
163
164 /// Test if this storage was constructed for the given unit.
165 #[inline]
166 pub fn is_same_unit(&self, unit: &Arc<Unit>) -> bool {
167 match &self.inner {
168 Some(inner) => Arc::ptr_eq(&inner.unit, unit),
169 None => false,
170 }
171 }
172
173 /// Look up the slot assigned to the given static item.
174 pub fn slot(&self, name: impl ToTypeHash) -> Result<usize, GlobalsError> {
175 let inner = self.inner()?;
176 let hash = name.to_type_hash();
177
178 inner
179 .unit
180 .global_slot(&hash)
181 .ok_or(GlobalsError::new(GlobalsErrorKind::MissingStatic {
182 hash,
183 item: name.to_item().ok().flatten(),
184 }))
185 }
186
187 /// Read the value of a static item, if it has been initialized.
188 ///
189 /// Note that this does not evaluate the initializer of an uninitialized
190 /// static, since doing so requires a runtime. A static which has an
191 /// initializer but has never been read by a script therefore reads as
192 /// `None` here.
193 #[inline]
194 pub fn get(&self, name: impl ToTypeHash) -> Result<Option<Value>, GlobalsError> {
195 let slot = self.slot(name)?;
196 Ok(self.get_at(slot))
197 }
198
199 /// Write the value of a static item.
200 #[inline]
201 pub fn set(&self, name: impl ToTypeHash, value: Value) -> Result<(), GlobalsError> {
202 let slot = self.slot(name)?;
203 self.set_at(slot, value)
204 }
205
206 /// Reset a static item back to being uninitialized.
207 ///
208 /// The next script which reads it will evaluate its initializer again, if
209 /// it has one.
210 #[inline]
211 pub fn clear(&self, name: impl ToTypeHash) -> Result<(), GlobalsError> {
212 let slot = self.slot(name)?;
213 self.clear_at(slot)
214 }
215
216 /// Read the value in the given slot, if it has been initialized.
217 #[inline]
218 pub fn get_at(&self, slot: usize) -> Option<Value> {
219 self.try_get_at(slot).ok().flatten()
220 }
221
222 /// Write the value in the given slot.
223 #[inline]
224 pub fn set_at(&self, slot: usize, value: Value) -> Result<(), GlobalsError> {
225 self.replace_at(slot, value)
226 }
227
228 /// Reset the given slot back to being uninitialized.
229 #[inline]
230 pub fn clear_at(&self, slot: usize) -> Result<(), GlobalsError> {
231 self.replace_at(slot, Value::empty())
232 }
233
234 /// Test if the given slot has been initialized.
235 #[inline]
236 pub fn is_initialized(&self, slot: usize) -> bool {
237 let Some(inner) = &self.inner else {
238 return false;
239 };
240
241 let slots = inner.slots.borrow();
242
243 match slots.get(slot) {
244 Some(value) => !value.is_empty(),
245 None => false,
246 }
247 }
248
249 /// Read the value in the given slot, distinguishing a slot which is out of
250 /// bounds from one which is merely uninitialized.
251 ///
252 /// The virtual machine needs to tell those apart, since the former means
253 /// the storage doesn't belong to the unit being run and the latter means
254 /// the initializer still has to be evaluated.
255 pub(crate) fn try_get_at(&self, slot: usize) -> Result<Option<Value>, GlobalsError> {
256 let inner = self.inner()?;
257 let slots = inner.slots.borrow();
258
259 let Some(value) = slots.get(slot) else {
260 return Err(GlobalsError::new(GlobalsErrorKind::SlotOutOfBounds {
261 slot,
262 len: slots.len(),
263 }));
264 };
265
266 if value.is_empty() {
267 return Ok(None);
268 }
269
270 Ok(Some(value.clone()))
271 }
272
273 /// Write a slot, returning an error if it is out of bounds.
274 fn replace_at(&self, slot: usize, value: Value) -> Result<(), GlobalsError> {
275 let inner = self.inner()?;
276 let mut slots = inner.slots.borrow_mut();
277 let len = slots.len();
278
279 let Some(existing) = slots.get_mut(slot) else {
280 return Err(GlobalsError::new(GlobalsErrorKind::SlotOutOfBounds {
281 slot,
282 len,
283 }));
284 };
285
286 *existing = value;
287 Ok(())
288 }
289
290 /// Deconstruct this handle into its shared allocation.
291 #[inline]
292 pub(crate) fn into_inner(self) -> Option<Rc<GlobalsInner>> {
293 self.inner
294 }
295
296 /// Construct a handle from a shared allocation.
297 #[inline]
298 pub(crate) fn from_inner(inner: Option<Rc<GlobalsInner>>) -> Self {
299 Self { inner }
300 }
301
302 #[inline]
303 fn inner(&self) -> Result<&GlobalsInner, GlobalsError> {
304 match &self.inner {
305 Some(inner) => Ok(inner),
306 None => Err(GlobalsError::new(GlobalsErrorKind::Missing)),
307 }
308 }
309}
310
311impl TryClone for Globals {
312 #[inline]
313 fn try_clone(&self) -> alloc::Result<Self> {
314 Ok(self.clone())
315 }
316}
317
318impl fmt::Debug for Globals {
319 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
320 let mut f = f.debug_struct("Globals");
321
322 match &self.inner {
323 Some(inner) => f.field("len", &inner.slots.borrow().len()),
324 None => f.field("len", &0usize),
325 };
326
327 f.finish_non_exhaustive()
328 }
329}
330
331/// An error raised when accessing static item storage.
332#[derive(Debug)]
333pub struct GlobalsError {
334 kind: GlobalsErrorKind,
335}
336
337impl GlobalsError {
338 #[inline]
339 const fn new(kind: GlobalsErrorKind) -> Self {
340 Self { kind }
341 }
342}
343
344impl fmt::Display for GlobalsError {
345 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346 match &self.kind {
347 GlobalsErrorKind::Missing => {
348 write!(f, "No static item storage has been configured")
349 }
350 GlobalsErrorKind::MissingStatic { hash, item } => match item {
351 Some(item) => write!(f, "Missing static item `{item}`"),
352 None => write!(f, "Missing static item with hash {hash}"),
353 },
354 GlobalsErrorKind::SlotOutOfBounds { slot, len } => {
355 write!(f, "Static slot {slot} is out of bounds 0-{len}")
356 }
357 }
358 }
359}
360
361impl core::error::Error for GlobalsError {}
362
363#[derive(Debug)]
364enum GlobalsErrorKind {
365 Missing,
366 MissingStatic { hash: Hash, item: Option<ItemBuf> },
367 SlotOutOfBounds { slot: usize, len: usize },
368}