netsblox_vm/
runtime.rs

1//! Miscellaneous types representing runtime state.
2
3use alloc::collections::{BTreeSet, VecDeque};
4use alloc::rc::{Rc, Weak};
5use alloc::borrow::ToOwned;
6use alloc::vec::Vec;
7
8use core::marker::PhantomData;
9use core::{iter, fmt, mem};
10use core::ops::Deref;
11use core::cell::Ref;
12
13use rand::distr::uniform::{SampleUniform, SampleRange};
14use checked_float::{FloatChecker, CheckedFloat};
15
16#[cfg(feature = "serde")]
17use serde::{Serialize, Deserialize};
18
19use crate::*;
20use crate::gc::*;
21use crate::json::*;
22use crate::real_time::*;
23use crate::bytecode::*;
24use crate::process::*;
25use crate::compact_str::*;
26use crate::vecmap::*;
27
28/// Error type used by [`NumberChecker`].
29#[derive(Debug)]
30pub enum NumberError {
31    Nan,
32    Infinity,
33}
34
35/// [`FloatChecker`] type used for validating a [`Number`].
36pub struct NumberChecker;
37impl FloatChecker<f64> for NumberChecker {
38    type Error = NumberError;
39    fn check(value: f64) -> Result<f64, Self::Error> {
40        if value.is_nan() { return Err(NumberError::Nan); }
41        if value.is_infinite() { return Err(NumberError::Infinity); }
42        Ok(if value.to_bits() == 0x8000000000000000 { 0.0 } else { value }) // we don't support signed zero - only useful in conjunction with infinity
43    }
44}
45
46/// The type used to represent numbers in the runtime.
47pub type Number = CheckedFloat<f64, NumberChecker>;
48
49const INLINE_SIZE: usize = {
50    #[cfg(target_pointer_width = "64")] { 14 }
51    #[cfg(not(target_pointer_width = "64"))] { 10 }
52};
53
54/// The type used to represent text in the runtime.
55///
56/// `Text` values are shared reference types and are cheap to clone.
57pub type Text = our_string::OurString<our_string::comrades::RcBytes, INLINE_SIZE>;
58
59#[derive(Debug)]
60pub enum FromAstError<'a> {
61    BadNumber { error: NumberError },
62    BadKeycode { key: CompactString },
63    UnsupportedEvent { kind: &'a ast::HatKind },
64    CompileError { error: CompileError<'a> },
65}
66impl From<NumberError> for FromAstError<'_> { fn from(error: NumberError) -> Self { Self::BadNumber { error } } }
67impl<'a> From<CompileError<'a>> for FromAstError<'a> { fn from(error: CompileError<'a>) -> Self { Self::CompileError { error } } }
68
69/// The type of a [`Value`].
70#[derive(Educe)]
71#[educe(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum Type<C: CustomTypes<S>, S: System<C>> {
73    Bool, Number, Text, Image, Audio, List, Closure, Entity, Native(<C::NativeValue as GetType>::Output),
74}
75
76/// A type conversion error on a [`Value`].
77#[derive(Educe)]
78#[educe(Debug)]
79pub struct ConversionError<C: CustomTypes<S>, S: System<C>> {
80    pub got: Type<C, S>,
81    pub expected: Type<C, S>,
82}
83
84/// The cause/explanation of an execution error.
85#[derive(Educe)]
86#[educe(Debug)]
87pub enum ErrorCause<C: CustomTypes<S>, S: System<C>> {
88    /// A variable lookup operation failed. `name` holds the name of the variable that was expected.
89    UndefinedVariable { name: CompactString },
90    /// A name-based costume lookup operation failed.
91    UndefinedCostume { name: CompactString },
92    /// A name-based sound lookup operation failed.
93    UndefinedSound { name: CompactString },
94    /// A name-based entity lookup operation failed.
95    UndefinedEntity { name: CompactString },
96    /// An upvar was created at the root scope, which is not allowed (it has nothing to refer up to).
97    UpvarAtRoot,
98    /// The result of a failed type conversion.
99    ConversionError { got: Type<C, S>, expected: Type<C, S> },
100    /// The result of a failed variadic type conversion (expected type `T` or a list of type `T`).
101    VariadicConversionError { got: Type<C, S>, expected: Type<C, S> },
102    /// Attempt to compare incomparable types.
103    Incomparable { left: Type<C, S>, right: Type<C, S> },
104    /// An operation that expected a non-empty list received an empty list as input.
105    EmptyList,
106    /// An operation that expected a list with a certain size received an incorrect size.
107    InvalidListLength { expected: usize, got: usize },
108    /// An indexing operation on a list/string had an out of bounds index, `index`, on a list/string of size `len`. Note that Snap!/NetsBlox use 1-based indexing.
109    IndexOutOfBounds { index: i64, len: usize },
110    /// Attempt to index a list with a non-integer numeric value, `index`.
111    IndexNotInteger { index: f64 },
112    /// Attempt to create a MIDI note value which was not an integer.
113    NoteNotInteger { note: f64 },
114    /// Attempt to create a MIDI note with a value outside the allowed MIDI range.
115    NoteNotMidi { note: CompactString },
116    /// Attempt to use a number which was not a valid size (must be convertible to [`usize`]).
117    InvalidSize { value: f64 },
118    /// Attempt to interpret an invalid unicode code point (number) as a character.
119    InvalidUnicode { value: f64 },
120    /// Exceeded the maximum call depth.
121    CallDepthLimit { limit: usize },
122    /// Attempt to call a closure which required `expected` arguments, but `got` arguments were supplied.
123    ClosureArgCount { expected: usize, got: usize },
124    /// An acyclic operation received a cyclic input value.
125    CyclicValue,
126    /// Attempt to parse an invalid CSV-encoded string.
127    NotCsv { value: CompactString },
128    /// Attempt to parse an invalid JSON-encoded string.
129    NotJson { value: CompactString },
130    /// A failed attempt to convert a native vm [`Value`] to [`SimpleValue`].
131    ToSimpleError { error: ToSimpleError<C, S> },
132    /// A failed attempt to convert a [`SimpleValue`] to [`Json`].
133    IntoJsonError { error: IntoJsonError<C, S> },
134    /// A failed attempt to convert a [`Json`] value into a [`SimpleValue`] for use in the vm.
135    FromJsonError { error: FromJsonError },
136    /// A failed attempt to convert a [`Json`] value from NetsBlox into a [`SimpleValue`] for use in the vm.
137    FromNetsBloxJsonError { error: FromNetsBloxJsonError },
138    /// A numeric value took on an invalid value such as NaN.
139    NumberError { error: NumberError },
140    /// Attempt to use an unsupported feature.
141    NotSupported { feature: Feature },
142    /// A soft error (e.g., RPC or syscall failure) was promoted to a hard error.
143    Promoted { error: CompactString },
144    /// A custom error generated explicitly from user code.
145    Custom { msg: CompactString },
146}
147impl<C: CustomTypes<S>, S: System<C>> From<ConversionError<C, S>> for ErrorCause<C, S> { fn from(e: ConversionError<C, S>) -> Self { Self::ConversionError { got: e.got, expected: e.expected } } }
148impl<C: CustomTypes<S>, S: System<C>> From<ToSimpleError<C, S>> for ErrorCause<C, S> { fn from(error: ToSimpleError<C, S>) -> Self { Self::ToSimpleError { error } } }
149impl<C: CustomTypes<S>, S: System<C>> From<IntoJsonError<C, S>> for ErrorCause<C, S> { fn from(error: IntoJsonError<C, S>) -> Self { Self::IntoJsonError { error } } }
150impl<C: CustomTypes<S>, S: System<C>> From<FromJsonError> for ErrorCause<C, S> { fn from(error: FromJsonError) -> Self { Self::FromJsonError { error } } }
151impl<C: CustomTypes<S>, S: System<C>> From<FromNetsBloxJsonError> for ErrorCause<C, S> { fn from(error: FromNetsBloxJsonError) -> Self { Self::FromNetsBloxJsonError { error } } }
152impl<C: CustomTypes<S>, S: System<C>> From<NumberError> for ErrorCause<C, S> { fn from(error: NumberError) -> Self { Self::NumberError { error } } }
153
154/// A 32-bit RGBA color with color space conversion utils.
155#[derive(Clone, Copy, PartialEq, Eq, Debug)]
156pub struct Color { pub r: u8, pub g: u8, pub b: u8, pub a: u8 }
157impl Color {
158    pub fn from_hsva(mut h: f32, mut s: f32, mut v: f32, mut a: f32) -> Self {
159        h = util::modulus(h, 360.0);
160        s = s.clamp(0.0, 1.0);
161        v = v.clamp(0.0, 1.0);
162        a = a.clamp(0.0, 1.0);
163
164        let c = v * s;
165        let hp = h / 60.0;
166        let x = c * (1.0 - libm::fabsf(hp % 2.0 - 1.0));
167        let m = v - c;
168
169        let (r, g, b) = match hp as usize {
170            0 | 6 => (c, x, 0.0), // (0 mod 6) - needed because rem_euclid is not perfect
171            1 => (x, c, 0.0),
172            2 => (0.0, c, x),
173            3 => (0.0, x, c),
174            4 => (x, 0.0, c),
175            5 => (c, 0.0, x),
176            _ => unreachable!(),
177        };
178
179        fn f(x: f32) -> u8 { libm::roundf(x * 255.0) as u8 }
180
181        Self { r: f(r + m), g: f(g + m), b: f(b + m), a: f(a) }
182    }
183    pub fn to_hsva(self) -> (f32, f32, f32, f32) {
184        fn f(x: u8) -> f32 { x as f32 / 255.0 }
185
186        let vals = [self.r, self.g, self.b];
187        let (c_max_i, c_max) = vals.iter().copied().enumerate().max_by_key(|x| x.1).map(|(i, v)| (i, f(v))).unwrap();
188        let c_min = vals.iter().copied().min().map(f).unwrap();
189        let delta = c_max - c_min;
190
191        let h = if delta == 0.0 { 0.0 } else {
192            match c_max_i {
193                0 => 60.0 * util::modulus((f(self.g) - f(self.b)) / delta, 6.0),
194                1 => 60.0 * ((f(self.b) - f(self.r)) / delta + 2.0),
195                2 => 60.0 * ((f(self.r) - f(self.g)) / delta + 4.0),
196                _ => unreachable!(),
197            }
198        };
199        let s = if c_max == 0.0 { 0.0 } else { delta / c_max };
200        let v = c_max;
201        let a = f(self.a);
202
203        (h, s, v, a)
204    }
205}
206
207#[test]
208fn test_color_hsv_to_rgb() {
209    assert_eq!(Color::from_hsva(0.0, 0.0, 0.0, 1.0), Color { r: 0x00, g: 0x00, b: 0x00, a: 0xFF });
210    assert_eq!(Color::from_hsva(0.0, -0.5, 0.0, 1.0), Color { r: 0x00, g: 0x00, b: 0x00, a: 0xFF });
211    assert_eq!(Color::from_hsva(0.0, 0.07, 0.36, 1.0), Color { r: 0x5C, g: 0x55, b: 0x55, a: 0xFF });
212    assert_eq!(Color::from_hsva(0.0, 1.0, 0.36, 1.0), Color { r: 92, g: 0, b: 0, a: 0xFF });
213    assert_eq!(Color::from_hsva(0.0, 1.5, 0.36, 1.0), Color { r: 92, g: 0, b: 0, a: 0xFF });
214    assert_eq!(Color::from_hsva(0.0, 1.3, 0.36, 1.0), Color { r: 92, g: 0, b: 0, a: 0xFF });
215    assert_eq!(Color::from_hsva(0.0, 14.5, 0.36, 1.0), Color { r: 92, g: 0, b: 0, a: 0xFF });
216    assert_eq!(Color::from_hsva(0.0, 0.0, 0.36, 1.0), Color { r: 92, g: 92, b: 92, a: 0xFF });
217    assert_eq!(Color::from_hsva(0.0, -2.4, 0.36, 1.0), Color { r: 92, g: 92, b: 92, a: 0xFF });
218    assert_eq!(Color::from_hsva(0.0, -0.4, 0.36, 1.0), Color { r: 92, g: 92, b: 92, a: 0xFF });
219    assert_eq!(Color::from_hsva(360.0, 0.07, 0.36, 1.0), Color { r: 0x5C, g: 0x55, b: 0x55, a: 0xFF });
220    assert_eq!(Color::from_hsva(-360.0, 0.07, 0.36, 1.0), Color { r: 0x5C, g: 0x55, b: 0x55, a: 0xFF });
221    assert_eq!(Color::from_hsva(25.0, 0.5, 0.25, 1.0), Color { r: 0x40, g: 0x2D, b: 0x20, a: 0xFF });
222    assert_eq!(Color::from_hsva(25.0 + 360.0, 0.5, 0.25, 1.0), Color { r: 0x40, g: 0x2D, b: 0x20, a: 0xFF });
223    assert_eq!(Color::from_hsva(25.0 - 360.0, 0.5, 0.25, 1.0), Color { r: 0x40, g: 0x2D, b: 0x20, a: 0xFF });
224    assert_eq!(Color::from_hsva(49.0, 0.75, 0.12, 1.0), Color { r: 0x1F, g: 0x1A, b: 0x08, a: 0xFF });
225    assert_eq!(Color::from_hsva(65.0, 0.12, 0.87, 1.0), Color { r: 0xDC, g: 0xDE, b: 0xC3, a: 0xFF });
226    assert_eq!(Color::from_hsva(65.0, 0.12, 1.0, 1.0), Color { r: 252, g: 255, b: 224, a: 0xFF });
227    assert_eq!(Color::from_hsva(65.0, 0.12, 1.4, 1.0), Color { r: 252, g: 255, b: 224, a: 0xFF });
228    assert_eq!(Color::from_hsva(90.0, 0.22, 0.55, 1.0), Color { r: 0x7D, g: 0x8C, b: 0x6D, a: 0xFF });
229    assert_eq!(Color::from_hsva(90.0 + 360.0, 0.22, 0.55, 1.0), Color { r: 0x7D, g: 0x8C, b: 0x6D, a: 0xFF });
230    assert_eq!(Color::from_hsva(90.0, 0.22, 0.55, 1.0), Color { r: 0x7D, g: 0x8C, b: 0x6D, a: 0xFF });
231    assert_eq!(Color::from_hsva(120.0, 0.26, 0.91, 1.0), Color { r: 0xAC, g: 0xE8, b: 0xAC, a: 0xFF });
232    assert_eq!(Color::from_hsva(175.0, 0.97, 0.04, 1.0), Color { r: 0x00, g: 0x0A, b: 0x09, a: 0xFF });
233    assert_eq!(Color::from_hsva(175.0 + 360.0, 0.97, 0.04, 1.0), Color { r: 0x00, g: 0x0A, b: 0x09, a: 0xFF });
234    assert_eq!(Color::from_hsva(175.0 - 360.0, 0.97, 0.04, 1.0), Color { r: 0x00, g: 0x0A, b: 0x09, a: 0xFF });
235    assert_eq!(Color::from_hsva(180.0, 1.0, 1.0, 1.0), Color { r: 0x00, g: 0xFF, b: 0xFF, a: 0xFF });
236    assert_eq!(Color::from_hsva(211.0, 0.11, 0.59, 1.0), Color { r: 0x86, g: 0x8E, b: 0x96, a: 0xFF });
237    assert_eq!(Color::from_hsva(299.0, 0.58, 0.91, 1.0), Color { r: 0xE6, g: 0x61, b: 0xE8, a: 0xFF });
238    assert_eq!(Color::from_hsva(299.0 + 360.0, 0.58, 0.91, 1.0), Color { r: 0xE6, g: 0x61, b: 0xE8, a: 0xFF });
239    assert_eq!(Color::from_hsva(299.0 - 360.0, 0.58, 0.91, 1.0), Color { r: 0xE6, g: 0x61, b: 0xE8, a: 0xFF });
240    assert_eq!(Color::from_hsva(310.0, 0.33, 0.77, 1.0), Color { r: 0xC4, g: 0x84, b: 0xBA, a: 0xFF });
241    assert_eq!(Color::from_hsva(310.0, 0.33, 0.77, 1.5), Color { r: 0xC4, g: 0x84, b: 0xBA, a: 0xFF });
242    assert_eq!(Color::from_hsva(310.0, 0.33, 0.77, 0.5), Color { r: 0xC4, g: 0x84, b: 0xBA, a: 0x80 });
243    assert_eq!(Color::from_hsva(310.0, 0.33, 0.77, 0.0), Color { r: 0xC4, g: 0x84, b: 0xBA, a: 0x00 });
244    assert_eq!(Color::from_hsva(310.0, 0.33, 0.77, -0.2), Color { r: 0xC4, g: 0x84, b: 0xBA, a: 0x00 });
245}
246#[test]
247fn test_color_rgb_to_hsv() {
248    macro_rules! assert_close {
249        ($c1:expr, $c2:expr) => {{
250            let (h1, s1, v1, a1) = $c1;
251            let (h2, s2, v2, a2) = $c2;
252            let thresh = 1.0 / 255.0;
253            assert!((h1 - h2).abs() < thresh, "{h1} vs {h2}");
254            assert!((s1 - s2).abs() < thresh, "{s1} vs {s2}");
255            assert!((v1 - v2).abs() < thresh, "{v1} vs {v2}");
256            assert!((a1 - a2).abs() < thresh, "{a1} vs {a2}");
257        }}
258    }
259    assert_close!(Color { r: 0x00, g: 0x00, b: 0x00, a: 0xFF }.to_hsva(), (0.0, 0.0, 0.0, 1.0));
260    assert_close!(Color { r: 0x5C, g: 0x55, b: 0x55, a: 0xFF }.to_hsva(), (0.0, 0.076, 0.361, 1.0));
261    assert_close!(Color { r: 92, g: 0, b: 0, a: 0xFF }.to_hsva(), (0.0, 1.0, 0.361, 1.0));
262    assert_close!(Color { r: 92, g: 92, b: 92, a: 0xFF }.to_hsva(), (0.0, 0.0, 0.361, 1.0));
263    assert_close!(Color { r: 0x40, g: 0x2D, b: 0x20, a: 0xFF }.to_hsva(), (24.375, 0.5, 0.251, 1.0));
264    assert_close!(Color { r: 0x1F, g: 0x1A, b: 0x08, a: 0xFF }.to_hsva(), (46.956, 0.742, 0.122, 1.0));
265    assert_close!(Color { r: 0xDC, g: 0xDE, b: 0xC3, a: 0xFF }.to_hsva(), (64.444, 0.122, 0.871, 1.0));
266    assert_close!(Color { r: 252, g: 255, b: 224, a: 0xFF }.to_hsva(), (65.806, 0.122, 1.0, 1.0));
267    assert_close!(Color { r: 0x7D, g: 0x8C, b: 0x6D, a: 0xFF }.to_hsva(), (89.032, 0.221, 0.549, 1.0));
268    assert_close!(Color { r: 0xAC, g: 0xE8, b: 0xAC, a: 0xFF }.to_hsva(), (120.0, 0.259, 0.91, 1.0));
269    assert_close!(Color { r: 0x00, g: 0x0A, b: 0x09, a: 0xFF }.to_hsva(), (174.0, 1.0, 0.039, 1.0));
270    assert_close!(Color { r: 0x00, g: 0xFF, b: 0xFF, a: 0xFF }.to_hsva(), (180.0, 1.0, 1.0, 1.0));
271    assert_close!(Color { r: 0x86, g: 0x8E, b: 0x96, a: 0xFF }.to_hsva(), (210.0, 0.107, 0.588, 1.0));
272    assert_close!(Color { r: 0xE6, g: 0x61, b: 0xE8, a: 0xFF }.to_hsva(), (299.111, 0.582, 0.91, 1.0));
273    assert_close!(Color { r: 0xC4, g: 0x84, b: 0xBA, a: 0xFF }.to_hsva(), (309.375, 0.327, 0.769, 1.0));
274    assert_close!(Color { r: 0xC4, g: 0x84, b: 0xBA, a: 0x80 }.to_hsva(), (309.375, 0.327, 0.769, 0.5));
275    assert_close!(Color { r: 0xC4, g: 0x84, b: 0xBA, a: 0x00 }.to_hsva(), (309.375, 0.327, 0.769, 0.0));
276    assert_close!(Color { r: 255, g: 67, b: 14, a: 255 }.to_hsva(), (13.195, 0.945, 1.0, 1.0));
277    assert_close!(Color { r: 255, g: 14, b: 67, a: 255 }.to_hsva(), (346.805, 0.945, 1.0, 1.0));
278    assert_close!(Color { r: 87, g: 255, b: 33, a: 255 }.to_hsva(), (105.4054, 0.871, 1.0, 1.0));
279    assert_close!(Color { r: 33, g: 255, b: 87, a: 255 }.to_hsva(), (134.594, 0.871, 1.0, 1.0));
280    assert_close!(Color { r: 12, g: 54, b: 255, a: 255 }.to_hsva(), (229.629, 0.953, 1.0, 1.0));
281    assert_close!(Color { r: 54, g: 12, b: 255, a: 255 }.to_hsva(), (250.37, 0.953, 1.0, 1.0));
282
283    macro_rules! assert_round_trip {
284        ($v:expr) => {{
285            let rgba = $v;
286            let hsva = rgba.to_hsva();
287            let back = Color::from_hsva(hsva.0, hsva.1, hsva.2, hsva.3);
288            assert_eq!(rgba, back);
289        }}
290    }
291    assert_round_trip!(Color { r: 12, g: 65, b: 23, a: 87 });
292    assert_round_trip!(Color { r: 128, g: 0, b: 23, a: 186 });
293    assert_round_trip!(Color { r: 0, g: 0, b: 0, a: 0 });
294    assert_round_trip!(Color { r: 0, g: 0, b: 0, a: 255 });
295    assert_round_trip!(Color { r: 255, g: 0, b: 0, a: 255 });
296    assert_round_trip!(Color { r: 0, g: 255, b: 0, a: 255 });
297    assert_round_trip!(Color { r: 0, g: 0, b: 255, a: 255 });
298    assert_round_trip!(Color { r: 255, g: 0, b: 0, a: 0 });
299    assert_round_trip!(Color { r: 0, g: 255, b: 0, a: 0 });
300    assert_round_trip!(Color { r: 0, g: 0, b: 255, a: 0 });
301    assert_round_trip!(Color { r: 57, g: 0, b: 0, a: 0 });
302    assert_round_trip!(Color { r: 0, g: 198, b: 0, a: 0 });
303    assert_round_trip!(Color { r: 0, g: 0, b: 10, a: 0 });
304}
305
306#[derive(Debug, Clone, Copy, FromPrimitive)]
307#[repr(u8)]
308pub enum ImageProperty {
309    Name,
310}
311#[derive(Debug, Clone, Copy, FromPrimitive)]
312#[repr(u8)]
313pub enum AudioProperty {
314    Name,
315}
316
317#[derive(Debug, Clone, Copy, FromPrimitive)]
318#[repr(u8)]
319pub enum Property {
320    XPos, YPos, Heading,
321
322    Visible, Size,
323
324    PenDown, PenSize, PenColor,
325    PenColorH, PenColorS, PenColorV, PenColorT,
326
327    Tempo, Volume, Balance,
328
329    ColorH, ColorS, ColorV, ColorT,
330    Fisheye, Whirl, Pixelate, Mosaic, Negative,
331}
332impl Property {
333    pub(crate) fn from_effect(kind: &ast::EffectKind) -> Self {
334        match kind {
335            ast::EffectKind::Color => Property::ColorH,
336            ast::EffectKind::Saturation => Property::ColorS,
337            ast::EffectKind::Brightness => Property::ColorV,
338            ast::EffectKind::Ghost => Property::ColorT,
339            ast::EffectKind::Fisheye => Property::Fisheye,
340            ast::EffectKind::Whirl => Property::Whirl,
341            ast::EffectKind::Pixelate => Property::Pixelate,
342            ast::EffectKind::Mosaic => Property::Mosaic,
343            ast::EffectKind::Negative => Property::Negative,
344        }
345    }
346    pub(crate) fn from_pen_attr(attr: &ast::PenAttribute) -> Self {
347        match attr {
348            ast::PenAttribute::Size => Property::PenSize,
349            ast::PenAttribute::Hue => Property::PenColorH,
350            ast::PenAttribute::Saturation => Property::PenColorS,
351            ast::PenAttribute::Brightness => Property::PenColorV,
352            ast::PenAttribute::Transparency => Property::PenColorT,
353        }
354    }
355}
356
357/// A collection of graphical effects related to an entity
358#[derive(Clone, Copy)]
359pub struct Effects {
360    pub color_h: Number,
361    pub color_s: Number,
362    pub color_v: Number,
363    pub color_t: Number,
364
365    pub fisheye: Number,
366    pub whirl: Number,
367    pub pixelate: Number,
368    pub mosaic: Number,
369    pub negative: Number,
370}
371impl Default for Effects {
372    fn default() -> Self {
373        let zero = Number::new(0.0).unwrap();
374        Self {
375            color_h: zero,
376            color_s: zero,
377            color_v: zero,
378            color_t: zero,
379
380            fisheye: zero,
381            whirl: zero,
382            pixelate: zero,
383            mosaic: zero,
384            negative: zero,
385        }
386    }
387}
388
389/// A collection of properties related to an entity.
390#[derive(Clone, Copy)]
391pub struct Properties {
392    pub pos: (Number, Number),
393    pub heading: Number,
394
395    pub visible: bool,
396    pub size: Number,
397
398    pub pen_down: bool,
399    pub pen_size: Number,
400
401    pub pen_color_h: Number,
402    pub pen_color_s: Number,
403    pub pen_color_v: Number,
404    pub pen_color_t: Number,
405
406    pub tempo: Number,
407    pub volume: Number,
408    pub balance: Number,
409
410    pub effects: Effects,
411}
412impl Default for Properties {
413    fn default() -> Self {
414        let zero = Number::new(0.0).unwrap();
415        let hundred = Number::new(100.0).unwrap();
416
417        Self {
418            pos: (zero, zero),
419            heading: zero,
420
421            visible: true,
422            size: hundred,
423
424            pen_down: false,
425            pen_size: Number::new(1.0).unwrap(),
426
427            pen_color_h: zero,
428            pen_color_s: zero,
429            pen_color_v: zero,
430            pen_color_t: zero,
431
432            tempo: Number::new(60.0).unwrap(),
433            volume: hundred,
434            balance: zero,
435
436            effects: Default::default(),
437        }
438    }
439}
440impl Properties {
441    fn with_value<C: CustomTypes<S>, S: System<C>, T>(&mut self, key: S::CommandKey, value: Result<T, ErrorCause<C, S>>, f: fn(&mut Self, T)) {
442        match value {
443            Ok(x) => {
444                f(self, x);
445                key.complete(Ok(()));
446            }
447            Err(e) => key.complete(Err(format_compact!("{e:?}"))),
448        }
449    }
450
451    pub fn perform_get_property<'gc, C: CustomTypes<S>, S: System<C>>(&self, key: S::RequestKey, prop: Property) -> RequestStatus<'gc, C, S> {
452        let value: SimpleValue = match prop {
453            Property::XPos => self.pos.0.into(),
454            Property::YPos => self.pos.1.into(),
455            Property::Heading => self.heading.into(),
456
457            Property::Visible => self.visible.into(),
458            Property::Size => self.size.into(),
459
460            Property::PenDown => self.pen_down.into(),
461            Property::PenSize => self.pen_size.into(),
462
463            Property::PenColor => {
464                let Color { a, r, g, b } = Color::from_hsva(
465                    self.pen_color_h.get() as f32,
466                    self.pen_color_s.get() as f32 / 100.0,
467                    self.pen_color_v.get() as f32 / 100.0,
468                    1.0 - self.pen_color_t.get() as f32 / 100.0
469                );
470                Number::new(u32::from_be_bytes([a, r, g, b]) as f64).unwrap().into()
471            }
472
473            Property::PenColorH => self.pen_color_h.into(),
474            Property::PenColorS => self.pen_color_s.into(),
475            Property::PenColorV => self.pen_color_v.into(),
476            Property::PenColorT => self.pen_color_t.into(),
477
478            Property::Tempo => self.tempo.into(),
479            Property::Volume => self.volume.into(),
480            Property::Balance => self.balance.into(),
481
482            Property::ColorH => self.effects.color_h.into(),
483            Property::ColorS => self.effects.color_s.into(),
484            Property::ColorV => self.effects.color_v.into(),
485            Property::ColorT => self.effects.color_t.into(),
486
487            Property::Fisheye => self.effects.fisheye.into(),
488            Property::Whirl => self.effects.whirl.into(),
489            Property::Pixelate => self.effects.pixelate.into(),
490            Property::Mosaic => self.effects.mosaic.into(),
491            Property::Negative => self.effects.negative.into(),
492        };
493        key.complete(Ok(value.into()));
494        RequestStatus::Handled
495    }
496    pub fn perform_set_property<'gc, 'a, C: CustomTypes<S>, S: System<C>>(&mut self, key: S::CommandKey, prop: Property, value: Value<'gc, C, S>) -> CommandStatus<'gc, 'a, C, S> {
497        match prop {
498            Property::XPos => self.with_value(key, value.as_number().map_err(Into::into), |props, value| props.pos.0 = value),
499            Property::YPos => self.with_value(key, value.as_number().map_err(Into::into), |props, value| props.pos.1 = value),
500            Property::Heading => self.with_value(key, value.as_number().map_err(Into::into).and_then(|x| Number::new(util::modulus(x.get(), 360.0)).map_err(Into::into)), |props, value| props.heading = value),
501
502            Property::Visible => self.with_value(key, value.as_bool().map_err(Into::into), |props, value| props.visible = value),
503            Property::Size => self.with_value(key, value.as_number().map_err(Into::into).and_then(|x| Number::new(x.get().max(0.0)).map_err(Into::into)), |props, value| props.size = value),
504
505            Property::PenDown => self.with_value(key, value.as_bool().map_err(Into::into), |props, value| props.pen_down = value),
506            Property::PenSize => self.with_value(key, value.as_number().map_err(Into::into).and_then(|x| Number::new(x.get().max(0.0)).map_err(Into::into)), |props, value| props.pen_size = value),
507
508            Property::PenColor => self.with_value(key, value.as_number().map_err(Into::into), |props, value| {
509                let [a, r, g, b] = (value.get() as u32).to_be_bytes();
510                let (h, s, v, a) = Color { a, r, g, b }.to_hsva();
511                props.pen_color_h = Number::new(h as f64).unwrap();
512                props.pen_color_s = Number::new(s as f64 * 100.0).unwrap();
513                props.pen_color_v = Number::new(v as f64 * 100.0).unwrap();
514                props.pen_color_t = Number::new((1.0 - a as f64) * 100.0).unwrap();
515            }),
516
517            Property::PenColorH => self.with_value(key, value.as_number().map_err(Into::into).and_then(|x| Number::new(util::modulus(x.get(), 360.0)).map_err(Into::into)), |props, value| props.pen_color_h = value),
518            Property::PenColorS => self.with_value(key, value.as_number().map_err(Into::into).and_then(|x| Number::new(x.get().clamp(0.0, 100.0)).map_err(Into::into)), |props, value| props.pen_color_s = value),
519            Property::PenColorV => self.with_value(key, value.as_number().map_err(Into::into).and_then(|x| Number::new(x.get().clamp(0.0, 100.0)).map_err(Into::into)), |props, value| props.pen_color_v = value),
520            Property::PenColorT => self.with_value(key, value.as_number().map_err(Into::into).and_then(|x| Number::new(x.get().clamp(0.0, 100.0)).map_err(Into::into)), |props, value| props.pen_color_t = value),
521
522            Property::Tempo => self.with_value(key, value.as_number().map_err(Into::into), |props, value| props.tempo = value),
523            Property::Volume => self.with_value(key, value.as_number().map_err(Into::into), |props, value| props.volume = value),
524            Property::Balance => self.with_value(key, value.as_number().map_err(Into::into), |props, value| props.balance = value),
525
526            Property::ColorH => self.with_value(key, value.as_number().map_err(Into::into).and_then(|x| Number::new(util::modulus(x.get(), 360.0)).map_err(Into::into)), |props, value| props.effects.color_h = value),
527            Property::ColorS => self.with_value(key, value.as_number().map_err(Into::into).and_then(|x| Number::new(x.get().clamp(0.0, 100.0)).map_err(Into::into)), |props, value| props.effects.color_s = value),
528            Property::ColorV => self.with_value(key, value.as_number().map_err(Into::into).and_then(|x| Number::new(x.get().clamp(0.0, 100.0)).map_err(Into::into)), |props, value| props.effects.color_v = value),
529            Property::ColorT => self.with_value(key, value.as_number().map_err(Into::into).and_then(|x| Number::new(x.get().clamp(0.0, 100.0)).map_err(Into::into)), |props, value| props.effects.color_t = value),
530
531            Property::Fisheye => self.with_value(key, value.as_number().map_err(Into::into), |props, value| props.effects.fisheye = value),
532            Property::Whirl => self.with_value(key, value.as_number().map_err(Into::into), |props, value| props.effects.whirl = value),
533            Property::Pixelate => self.with_value(key, value.as_number().map_err(Into::into), |props, value| props.effects.pixelate = value),
534            Property::Mosaic => self.with_value(key, value.as_number().map_err(Into::into), |props, value| props.effects.mosaic = value),
535            Property::Negative => self.with_value(key, value.as_number().map_err(Into::into), |props, value| props.effects.negative = value),
536        }
537        CommandStatus::Handled
538    }
539    pub fn perform_change_property<'gc, 'a, C: CustomTypes<S>, S: System<C>>(&mut self, key: S::CommandKey, prop: Property, delta: Value<'gc, C, S>) -> CommandStatus<'gc, 'a, C, S> {
540        match prop {
541            Property::XPos => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| self.pos.0.add(x).map_err(Into::into)), |props, value| props.pos.0 = value),
542            Property::YPos => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| self.pos.1.add(x).map_err(Into::into)), |props, value| props.pos.1 = value),
543            Property::Heading => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| Number::new(util::modulus(self.heading.get() + x.get(), 360.0)).map_err(Into::into)), |props, value| props.heading = value),
544
545            Property::Visible => self.with_value(key, delta.as_bool().map_err(Into::into), |props, value| props.visible ^= value),
546            Property::Size => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| Number::new((self.size.get() + x.get()).max(0.0)).map_err(Into::into)), |props, value| props.size = value),
547
548            Property::PenDown => self.with_value(key, delta.as_bool().map_err(Into::into), |props, value| props.pen_down ^= value),
549            Property::PenSize => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| Number::new((self.pen_size.get() + x.get()).max(0.0)).map_err(Into::into)), |props, value| props.pen_size = value),
550
551            Property::PenColor => key.complete(Err("attempt to apply relative change to a color".into())),
552
553            Property::PenColorH => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| Number::new(util::modulus(self.pen_color_h.get() + x.get(), 360.0)).map_err(Into::into)), |props, value| props.pen_color_h = value),
554            Property::PenColorS => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| Number::new((self.pen_color_s.get() + x.get()).clamp(0.0, 100.0)).map_err(Into::into)), |props, value| props.pen_color_s = value),
555            Property::PenColorV => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| Number::new((self.pen_color_v.get() + x.get()).clamp(0.0, 100.0)).map_err(Into::into)), |props, value| props.pen_color_v = value),
556            Property::PenColorT => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| Number::new((self.pen_color_t.get() + x.get()).clamp(0.0, 100.0)).map_err(Into::into)), |props, value| props.pen_color_t = value),
557
558            Property::Tempo => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| self.tempo.add(x).map_err(Into::into)), |props, value| props.tempo = value),
559            Property::Volume => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| self.volume.add(x).map_err(Into::into)), |props, value| props.volume = value),
560            Property::Balance => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| self.balance.add(x).map_err(Into::into)), |props, value| props.balance = value),
561
562            Property::ColorH => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| Number::new(util::modulus(self.effects.color_h.get() + x.get(), 360.0)).map_err(Into::into)), |props, value| props.effects.color_h = value),
563            Property::ColorS => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| Number::new((self.effects.color_s.get() + x.get()).clamp(0.0, 100.0)).map_err(Into::into)), |props, value| props.effects.color_s = value),
564            Property::ColorV => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| Number::new((self.effects.color_v.get() + x.get()).clamp(0.0, 100.0)).map_err(Into::into)), |props, value| props.effects.color_v = value),
565            Property::ColorT => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| Number::new((self.effects.color_t.get() + x.get()).clamp(0.0, 100.0)).map_err(Into::into)), |props, value| props.effects.color_t = value),
566
567            Property::Fisheye => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| self.effects.fisheye.add(x).map_err(Into::into)), |props, value| props.effects.fisheye = value),
568            Property::Whirl => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| self.effects.whirl.add(x).map_err(Into::into)), |props, value| props.effects.whirl = value),
569            Property::Pixelate => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| self.effects.pixelate.add(x).map_err(Into::into)), |props, value| props.effects.pixelate = value),
570            Property::Mosaic => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| self.effects.mosaic.add(x).map_err(Into::into)), |props, value| props.effects.mosaic = value),
571            Property::Negative => self.with_value(key, delta.as_number().map_err(Into::into).and_then(|x| self.effects.negative.add(x).map_err(Into::into)), |props, value| props.effects.negative = value),
572        }
573        CommandStatus::Handled
574    }
575
576    pub fn perform_clear_effects<'gc, 'a, C: CustomTypes<S>, S: System<C>>(&mut self, key: S::CommandKey) -> CommandStatus<'gc, 'a, C, S> {
577        self.effects = Default::default();
578        key.complete(Ok(()));
579        CommandStatus::Handled
580    }
581
582    pub fn perform_goto_xy<'gc, 'a, C: CustomTypes<S>, S: System<C>>(&mut self, key: S::CommandKey, x: Number, y: Number) -> CommandStatus<'gc, 'a, C, S> {
583        self.pos = (x, y);
584        key.complete(Ok(()));
585        CommandStatus::Handled
586    }
587
588    pub fn perform_point_towards_xy<'gc, 'a, C: CustomTypes<S>, S: System<C>>(&mut self, key: S::CommandKey, x: Number, y: Number) -> CommandStatus<'gc, 'a, C, S> {
589        let (dx, dy) = (x.get() - self.pos.0.get(), y.get() - self.pos.1.get());
590        let heading = util::modulus(libm::atan2(dx, dy).to_degrees(), 360.0);
591        self.with_value::<C, S, _>(key, Number::new(heading).map_err(Into::into), |props, value| props.heading = value);
592        CommandStatus::Handled
593    }
594
595    pub fn perform_forward<'gc, 'a, C: CustomTypes<S>, S: System<C>>(&mut self, key: S::CommandKey, dist: Number) -> CommandStatus<'gc, 'a, C, S> {
596        let (sin, cos) = libm::sincos(self.heading.get().to_radians());
597        let (x, y) = (self.pos.0.get() + sin * dist.get(), self.pos.1.get() + cos * dist.get());
598        self.with_value::<C, S, _>(key, Number::new(x).map_err(Into::into).and_then(|x| Number::new(y).map(|y| (x, y)).map_err(Into::into)), |props, pos| props.pos = pos);
599        CommandStatus::Handled
600    }
601}
602
603/// A key from the keyboard.
604#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
605#[derive(Debug, Clone, Copy, PartialEq, Eq)]
606pub enum KeyCode {
607    /// A normal character key, such as a letter, number, or special symbol.
608    Char(char),
609    /// The up arrow key.
610    Up,
611    /// The down arrow key.
612    Down,
613    /// The left arrow key.
614    Left,
615    /// The right arrow key.
616    Right,
617    /// Either enter/return key.
618    Enter,
619}
620
621/// An event type which can be set to trigger the execution of a script.
622#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
623#[derive(Clone)]
624pub enum Event {
625    /// Fire when a green flag click event is issued.
626    OnFlag,
627    /// Fire when a new clone of this entity is created.
628    OnClone,
629    /// Fire when a message is received locally (Control message blocks). `None` is used to denote any message type.
630    LocalMessage { msg_type: Option<CompactString> },
631    /// Fire when a message is received over the network (Network message blocks).
632    NetworkMessage { msg_type: CompactString, fields: Vec<CompactString> },
633    /// Fire when a key is pressed. [`None`] is used to denote any key press.
634    OnKey { key_filter: Option<KeyCode> },
635    /// Fire when explicitly requested from an input command.
636    Custom { name: CompactString, fields: Vec<CompactString> },
637}
638
639#[derive(Debug, Clone, Copy, FromPrimitive)]
640#[repr(u8)]
641pub enum PrintStyle {
642    Say, Think,
643}
644
645/// A value representing the identity of a [`Value`].
646#[derive(Educe)]
647#[educe(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
648pub struct Identity<'gc, C: CustomTypes<S>, S: System<C>>(*const (), PhantomData<&'gc Value<'gc, C, S>>);
649
650/// Gets the type of value that is stored.
651pub trait GetType {
652    type Output: Clone + Copy + PartialEq + Eq + fmt::Debug;
653    /// Gets the type of value that is stored.
654    fn get_type(&self) -> Self::Output;
655}
656
657pub trait Key<T> {
658    fn complete(self, value: T);
659}
660
661/// An image type that can be used in the VM.
662#[derive(Debug, Clone, PartialEq, Eq)]
663pub struct Image {
664    /// The raw binary content of the image
665    pub content: Vec<u8>,
666    /// The center `(x, y)` of the image as used for NetsBlox sprites.
667    /// [`None`] is implied to represent `(w / 2, h / 2)` based on the true image size (size decoding cannot be done in no-std).
668    pub center: Option<(Number, Number)>,
669    /// The user-level name of the image
670    pub name: CompactString,
671}
672
673/// An audio clip type that can be used in the VM.
674#[derive(Debug, Clone, PartialEq, Eq)]
675pub struct Audio {
676    /// The raw binary content of the audio clip
677    pub content: Vec<u8>,
678    /// The user-level name of the audio clip
679    pub name: CompactString,
680}
681
682/// An error produced by [`Value::to_simple`]
683#[derive(Educe)]
684#[educe(Debug)]
685pub enum ToSimpleError<C: CustomTypes<S>, S: System<C>> {
686    Cyclic,
687    ComplexType(Type<C, S>),
688}
689/// An error produced by [`SimpleValue::into_json`]
690#[derive(Educe)]
691#[educe(Debug)]
692pub enum IntoJsonError<C: CustomTypes<S>, S: System<C>> {
693    ComplexType(Type<C, S>),
694}
695
696/// An error produced by [`SimpleValue::from_json`]
697#[derive(Debug)]
698pub enum FromJsonError {
699    Null,
700}
701/// An error produced by [`SimpleValue::from_netsblox_json`]
702#[derive(Debug)]
703pub enum FromNetsBloxJsonError {
704    Null,
705    BadImage,
706    BadAudio,
707}
708
709/// An acyclic and [`Send`] version of [`Value`]
710#[derive(Debug, Clone, PartialEq, Eq)]
711pub enum SimpleValue {
712    Bool(bool),
713    Number(Number),
714    Text(CompactString),
715    Image(Image),
716    Audio(Audio),
717    List(Vec<SimpleValue>),
718}
719
720impl From<bool> for SimpleValue { fn from(x: bool) -> Self { Self::Bool(x) } }
721impl From<Number> for SimpleValue { fn from(x: Number) -> Self { Self::Number(x) } }
722impl From<CompactString> for SimpleValue { fn from(x: CompactString) -> Self { Self::Text(x) } }
723impl From<alloc::string::String> for SimpleValue { fn from(x: alloc::string::String) -> Self { Self::Text(x.into()) } }
724impl From<Image> for SimpleValue { fn from(x: Image) -> Self { Self::Image(x) } }
725impl From<Audio> for SimpleValue { fn from(x: Audio) -> Self { Self::Audio(x) } }
726impl From<Vec<SimpleValue>> for SimpleValue { fn from(x: Vec<SimpleValue>) -> Self { Self::List(x) } }
727
728impl SimpleValue {
729    /// Converts this [`SimpleValue`] into its equivalent JSON form.
730    /// Note that [`SimpleValue::Image`] and [`SimpleValue::Audio`] cannot be encoded as standard JSON;
731    /// for this, you may instead use [`SimpleValue::into_netsblox_json`].
732    pub fn into_json<C: CustomTypes<S>, S: System<C>>(self) -> Result<Json, IntoJsonError<C, S>> {
733        Ok(match self {
734            SimpleValue::Bool(x) => Json::Bool(x),
735            SimpleValue::Number(x) => Json::Number(JsonNumber::from_f64(x.get()).unwrap()), // Json and Number forbid NaN and Infinity, so this is infallible
736            SimpleValue::Text(x) => Json::String(x.as_str().to_owned()),
737            SimpleValue::List(x) => Json::Array(x.into_iter().map(SimpleValue::into_json).collect::<Result<_,_>>()?),
738            SimpleValue::Image(_) => return Err(IntoJsonError::ComplexType(Type::Image)),
739            SimpleValue::Audio(_) => return Err(IntoJsonError::ComplexType(Type::Audio)),
740        })
741    }
742    /// Converts a JSON object into its equivalent [`SimpleValue`].
743    /// Note that [`SimpleValue::Image`] and [`SimpleValue::Audio`] cannot be decoded from standard JSON;
744    /// for this, you may instead use [`SimpleValue::from_netsblox_json`].
745    pub fn from_json(value: Json) -> Result<Self, FromJsonError> {
746        Ok(match value {
747            Json::Null => return Err(FromJsonError::Null),
748            Json::Bool(x) => SimpleValue::Bool(x),
749            Json::Number(x) => SimpleValue::Number(Number::new(x.as_f64().unwrap()).unwrap()), // Json and Number forbid NaN and Infinity, so this is infallible
750            Json::String(x) => SimpleValue::Text(x.into()),
751            Json::Array(x) => SimpleValue::List(x.into_iter().map(SimpleValue::from_json).collect::<Result<_,_>>()?),
752            Json::Object(x) => SimpleValue::List(x.into_iter().map(|(k, v)| {
753                Ok(SimpleValue::List(vec![SimpleValue::Text(k.into()), SimpleValue::from_json(v)?]))
754            }).collect::<Result<_,_>>()?),
755        })
756    }
757
758    /// Converts this [`SimpleValue`] into an encoded JSON equivalent suitable for communication with NetsBlox.
759    pub fn into_netsblox_json(self) -> Json {
760        match self {
761            SimpleValue::Bool(x) => Json::Bool(x),
762            SimpleValue::Number(x) => Json::Number(JsonNumber::from_f64(x.get()).unwrap()), // Json and Number forbid NaN and Infinity, so this is infallible
763            SimpleValue::Text(x) => Json::String(x.into()),
764            SimpleValue::List(x) => Json::Array(x.into_iter().map(SimpleValue::into_netsblox_json).collect()),
765            SimpleValue::Image(img) => {
766                let center_attrs = img.center.map(|(x, y)| format!(" center-x=\"{x}\" center-y=\"{y}\"")).unwrap_or_default();
767                Json::String(format!("<costume name=\"{}\"{center_attrs} image=\"data:image/png;base64,{}\" />", ast::util::xml_escape(&img.name), crate::util::base64_encode(&img.content)))
768            },
769            SimpleValue::Audio(audio) => Json::String(format!("<sound name=\"{}\" sound=\"data:audio/mpeg;base64,{}\" />", ast::util::xml_escape(&audio.name), crate::util::base64_encode(&audio.content))),
770        }
771    }
772    /// Converts a JSON object returned from NetsBlox into its equivalent [`SimpleValue`] form.
773    pub fn from_netsblox_json(value: Json) -> Result<Self, FromNetsBloxJsonError> {
774        Ok(match value {
775            Json::Null => return Err(FromNetsBloxJsonError::Null),
776            Json::Bool(x) => SimpleValue::Bool(x),
777            Json::Number(x) => SimpleValue::Number(Number::new(x.as_f64().unwrap()).unwrap()), // Json and Number forbid NaN and Infinity, so this is infallible
778            Json::Array(x) => SimpleValue::List(x.into_iter().map(SimpleValue::from_netsblox_json).collect::<Result<_,_>>()?),
779            Json::Object(x) => SimpleValue::List(x.into_iter().map(|(k, v)| {
780                Ok(SimpleValue::List(vec![SimpleValue::Text(k.into()), SimpleValue::from_netsblox_json(v)?]))
781            }).collect::<Result<_,_>>()?),
782            Json::String(x) => {
783                let mut tokenizer = xmlparser::Tokenizer::from(x.as_str());
784                match tokenizer.next() {
785                    Some(Ok(xmlparser::Token::ElementStart { local, .. })) => match local.as_str() {
786                        "costume" => {
787                            let mut center_x = None;
788                            let mut center_y = None;
789                            let mut content = None;
790                            let mut name = "untitled".into();
791                            loop {
792                                match tokenizer.next() {
793                                    Some(Ok(xmlparser::Token::Attribute { local, value, .. })) => match local.as_str() {
794                                        "center-x" => center_x = Some(value.as_str().parse().ok().and_then(|x| Number::new(x).ok()).ok_or(FromNetsBloxJsonError::BadImage)?),
795                                        "center-y" => center_y = Some(value.as_str().parse().ok().and_then(|y| Number::new(y).ok()).ok_or(FromNetsBloxJsonError::BadImage)?),
796                                        "name" => name = value.as_str().into(),
797                                        "image" => match value.as_str().split(";base64,").nth(1) {
798                                            Some(raw) if value.as_str().starts_with("data:image/") => content = Some(crate::util::base64_decode(raw).map_err(|_| FromNetsBloxJsonError::BadImage)?),
799                                            _ => return Err(FromNetsBloxJsonError::BadImage),
800                                        }
801                                        _ => (),
802                                    }
803                                    Some(Ok(xmlparser::Token::ElementEnd { .. })) => match content {
804                                        Some(content) => return Ok(SimpleValue::Image(Image { content, center: center_x.zip(center_y), name })),
805                                        None => return Ok(SimpleValue::Text(x.into())),
806                                    }
807                                    Some(Ok(_)) => (),
808                                    None | Some(Err(_)) => return Ok(SimpleValue::Text(x.into())),
809                                }
810                            }
811                        }
812                        "sound" => {
813                            let mut content = None;
814                            let mut name = "untitled".into();
815                            loop {
816                                match tokenizer.next() {
817                                    Some(Ok(xmlparser::Token::Attribute { local, value, .. })) => match local.as_str() {
818                                        "name" => name = value.as_str().into(),
819                                        "sound" => match value.as_str().split(";base64,").nth(1) {
820                                            Some(raw) if value.as_str().starts_with("data:audio/") => content = Some(crate::util::base64_decode(raw).map_err(|_| FromNetsBloxJsonError::BadAudio)?),
821                                            _ => return Err(FromNetsBloxJsonError::BadAudio),
822                                        }
823                                        _ => (),
824                                    }
825                                    Some(Ok(xmlparser::Token::ElementEnd { .. })) => match content {
826                                        Some(content) => return Ok(SimpleValue::Audio(Audio { content, name })),
827                                        None => return Ok(SimpleValue::Text(x.into())),
828                                    }
829                                    Some(Ok(_)) => (),
830                                    None | Some(Err(_)) => return Ok(SimpleValue::Text(x.into())),
831                                }
832                            }
833                        }
834                        _ => SimpleValue::Text(x.into()),
835                    }
836                    _ => SimpleValue::Text(x.into()),
837                }
838            }
839        })
840    }
841
842    /// Parses a string into a number just as the runtime would do natively.
843    pub fn parse_number(s: &str) -> Option<Number> {
844        let s = s.trim();
845        let parsed = match s.get(..2) {
846            Some("0x" | "0X") => i64::from_str_radix(&s[2..], 16).ok().map(|x| x as f64),
847            Some("0o" | "0O") => i64::from_str_radix(&s[2..], 8).ok().map(|x| x as f64),
848            Some("0b" | "0B") => i64::from_str_radix(&s[2..], 2).ok().map(|x| x as f64),
849            _ => s.parse::<f64>().ok(),
850        };
851        parsed.and_then(|x| Number::new(x).ok())
852    }
853    /// Stringifies a number just as the runtime would do natively.
854    pub fn stringify_number(v: Number) -> Text {
855        debug_assert!(v.get().is_finite());
856        let mut buf = ryu::Buffer::new();
857        let res = buf.format_finite(v.get());
858        Text::from(res.strip_suffix(".0").unwrap_or(res))
859    }
860}
861
862#[test]
863fn test_number_to_string() {
864    assert_eq!(SimpleValue::stringify_number(Number::new(0.0).unwrap()), "0");
865    assert_eq!(SimpleValue::stringify_number(Number::new(-0.0).unwrap()), "0");
866    assert_eq!(SimpleValue::stringify_number(Number::new(1.0).unwrap()), "1");
867    assert_eq!(SimpleValue::stringify_number(Number::new(7.0).unwrap()), "7");
868    assert_eq!(SimpleValue::stringify_number(Number::new(-1.0).unwrap()), "-1");
869    assert_eq!(SimpleValue::stringify_number(Number::new(-13.0).unwrap()), "-13");
870    assert_eq!(SimpleValue::stringify_number(Number::new(123456789.0).unwrap()), "123456789");
871    assert_eq!(SimpleValue::stringify_number(Number::new(5.67e50).unwrap()), "5.67e50");
872    assert_eq!(SimpleValue::stringify_number(Number::new(-8.35e30).unwrap()), "-8.35e30");
873    assert_eq!(SimpleValue::stringify_number(Number::new(6e-24).unwrap()), "6e-24");
874    assert_eq!(SimpleValue::stringify_number(Number::new(1e24).unwrap()), "1e24");
875}
876
877#[test]
878fn test_netsblox_json() {
879    let val = SimpleValue::List(vec![
880        SimpleValue::Bool(false),
881        SimpleValue::Bool(true),
882        SimpleValue::Number(Number::new(0.0).unwrap()),
883        SimpleValue::Number(Number::new(12.5).unwrap()),
884        SimpleValue::Number(Number::new(-6.0).unwrap()),
885        SimpleValue::Text("".into()),
886        SimpleValue::Text("hello world".into()),
887        SimpleValue::Text("<sound>".into()),
888        SimpleValue::Text("<sound/>".into()),
889        SimpleValue::Text("<sound />".into()),
890        SimpleValue::Text("<costume>".into()),
891        SimpleValue::Text("<costume/>".into()),
892        SimpleValue::Text("<costume />".into()),
893        SimpleValue::Image(Image { content: vec![], center: None, name: "test".into() }),
894        SimpleValue::Image(Image { content: vec![], center: Some((Number::new(0.0).unwrap(), Number::new(4.5).unwrap())), name: "another one".into() }),
895        SimpleValue::Image(Image { content: vec![0, 1, 2, 255, 254, 253, 127, 128], center: None, name: "untitled".into() }),
896        SimpleValue::Image(Image { content: vec![0, 1, 2, 255, 254, 253, 127, 128, 6, 9], center: Some((Number::new(12.5).unwrap(), Number::new(-54.0).unwrap())), name: "last one i swear".into() }),
897        SimpleValue::Audio(Audio { content: vec![], name: "something".into() }),
898        SimpleValue::Audio(Audio { content: vec![1, 2, 3], name: "killer move".into() }),
899        SimpleValue::Audio(Audio { content: vec![1, 2, 255, 43, 23, 254], name: "finish".into() }),
900    ]);
901    let js = val.clone().into_netsblox_json();
902    let back = SimpleValue::from_netsblox_json(js).unwrap();
903    assert_eq!(val, back);
904}
905
906/// Any primitive value.
907#[derive(Educe, Collect)]
908#[educe(Clone)]
909#[collect(no_drop, bound = "")]
910pub enum Value<'gc, C: CustomTypes<S>, S: System<C>> {
911    /// A primitive boolean value.
912    Bool(#[collect(require_static)] bool),
913    /// A primitive numeric value. Snap! and NetsBlox use 64-bit floating point values for all numbers.
914    Number(#[collect(require_static)] Number),
915    /// A primitive text value, which is an immutable reference type.
916    Text(#[collect(require_static)] Text),
917    /// An image stored as a binary buffer.
918    Image(#[collect(require_static)] Rc<Image>),
919    /// An audio clip stored as as a binary buffer.
920    Audio(#[collect(require_static)] Rc<Audio>),
921    /// A reference to a native object handle produced by [`System`].
922    Native(#[collect(require_static)] Rc<C::NativeValue>),
923    /// A primitive list type, which is a mutable reference type.
924    List(Gc<'gc, RefLock<VecDeque<Value<'gc, C, S>>>>),
925    /// A closure/lambda function. This contains information about the closure's bytecode location, parameters, and captures from the parent scope.
926    Closure(Gc<'gc, RefLock<Closure<'gc, C, S>>>),
927    /// A reference to an [`Entity`] in the environment.
928    Entity(Gc<'gc, RefLock<Entity<'gc, C, S>>>),
929}
930
931impl<'gc, C: CustomTypes<S>, S: System<C>> GetType for Value<'gc, C, S> {
932    type Output = Type<C, S>;
933    fn get_type(&self) -> Self::Output {
934        match self {
935            Value::Bool(_) => Type::Bool,
936            Value::Number(_) => Type::Number,
937            Value::Text(_) => Type::Text,
938            Value::Image(_) => Type::Image,
939            Value::Audio(_) => Type::Audio,
940            Value::List(_) => Type::List,
941            Value::Closure(_) => Type::Closure,
942            Value::Entity(_) => Type::Entity,
943            Value::Native(x) => Type::Native(x.get_type()),
944        }
945    }
946}
947
948#[derive(Clone, Copy)]
949enum FormatStyle {
950    Debug, Display,
951}
952impl<C: CustomTypes<S>, S: System<C>> fmt::Debug for Value<'_, C, S> {
953    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
954        format_value(self, f, FormatStyle::Debug)
955    }
956}
957impl<C: CustomTypes<S>, S: System<C>> fmt::Display for Value<'_, C, S> {
958    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
959        format_value(self, f, FormatStyle::Display)
960    }
961}
962fn format_value<C: CustomTypes<S>, S: System<C>>(value: &Value<'_, C, S>, f: &mut fmt::Formatter<'_>, style: FormatStyle) -> fmt::Result {
963    fn print<'gc, C: CustomTypes<S>, S: System<C>>(value: &Value<'gc, C, S>, f: &mut fmt::Formatter<'_>, style: FormatStyle, cache: &mut BTreeSet<Identity<'gc, C, S>>) -> fmt::Result {
964        match value {
965            Value::Bool(x) => write!(f, "{x}"),
966            Value::Number(x) => write!(f, "{x}"),
967            Value::Text(x) => match style {
968                FormatStyle::Debug => write!(f, "{:?}", x.as_str()),
969                FormatStyle::Display => write!(f, "{}", x.as_str()),
970            }
971            Value::Closure(x) => write!(f, "{:?}", &*x.borrow()),
972            Value::Entity(x) => write!(f, "{:?}", &*x.borrow()),
973            Value::Native(x) => write!(f, "{:?}", &**x),
974            Value::Image(x) => write!(f, "[Image {:?}]", Rc::as_ptr(x)),
975            Value::Audio(x) => write!(f, "[Audio {:?}]", Rc::as_ptr(x)),
976            Value::List(x) => {
977                let identity = value.identity();
978                if !cache.insert(identity) { return write!(f, "[...]") }
979
980                let x = x.borrow();
981                write!(f, "[")?;
982                for (i, val) in x.iter().enumerate() {
983                    print(val, f, style, cache)?;
984                    if i != x.len() - 1 { write!(f, ",")? }
985                }
986                write!(f, "]")?;
987
988                debug_assert!(cache.contains(&identity));
989                cache.remove(&identity);
990                Ok(())
991            }
992        }
993    }
994    let mut cache = Default::default();
995    let res = print(value, f, style, &mut cache);
996    if res.is_ok() { debug_assert_eq!(cache.len(), 0); }
997    res
998}
999
1000impl<'gc, C: CustomTypes<S>, S: System<C>> From<bool> for Value<'gc, C, S> { fn from(v: bool) -> Self { Value::Bool(v) } }
1001impl<'gc, C: CustomTypes<S>, S: System<C>> From<Number> for Value<'gc, C, S> { fn from(v: Number) -> Self { Value::Number(v) } }
1002impl<'gc, C: CustomTypes<S>, S: System<C>> From<Text> for Value<'gc, C, S> { fn from(v: Text) -> Self { Value::Text(v) } }
1003impl<'gc, C: CustomTypes<S>, S: System<C>> From<Gc<'gc, RefLock<VecDeque<Value<'gc, C, S>>>>> for Value<'gc, C, S> { fn from(v: Gc<'gc, RefLock<VecDeque<Value<'gc, C, S>>>>) -> Self { Value::List(v) } }
1004impl<'gc, C: CustomTypes<S>, S: System<C>> From<Gc<'gc, RefLock<Closure<'gc, C, S>>>> for Value<'gc, C, S> { fn from(v: Gc<'gc, RefLock<Closure<'gc, C, S>>>) -> Self { Value::Closure(v) } }
1005impl<'gc, C: CustomTypes<S>, S: System<C>> From<Gc<'gc, RefLock<Entity<'gc, C, S>>>> for Value<'gc, C, S> { fn from(v: Gc<'gc, RefLock<Entity<'gc, C, S>>>) -> Self { Value::Entity(v) } }
1006impl<'gc, C: CustomTypes<S>, S: System<C>> Value<'gc, C, S> {
1007    /// Converts this [`Value`] into a [`SimpleValue`] for use outside the VM.
1008    pub fn to_simple(&self) -> Result<SimpleValue, ToSimpleError<C, S>> {
1009        fn simplify<'gc, C: CustomTypes<S>, S: System<C>>(value: &Value<'gc, C, S>, cache: &mut BTreeSet<Identity<'gc, C, S>>) -> Result<SimpleValue, ToSimpleError<C, S>> {
1010            Ok(match value {
1011                Value::Bool(x) => SimpleValue::Bool(*x),
1012                Value::Number(x) => SimpleValue::Number(*x),
1013                Value::Text(x) => SimpleValue::Text((**x).into()),
1014                Value::Image(x) => SimpleValue::Image((**x).clone()),
1015                Value::Audio(x) => SimpleValue::Audio((**x).clone()),
1016                Value::List(x) => {
1017                    let identity = value.identity();
1018                    if !cache.insert(identity) { return Err(ToSimpleError::Cyclic) }
1019                    let res = SimpleValue::List(x.borrow().iter().map(|x| simplify(x, cache)).collect::<Result<_,_>>()?);
1020                    debug_assert!(cache.contains(&identity));
1021                    cache.remove(&identity);
1022                    res
1023                }
1024                Value::Closure(_) | Value::Entity(_) | Value::Native(_) => return Err(ToSimpleError::ComplexType(value.get_type())),
1025            })
1026        }
1027        let mut cache = Default::default();
1028        let res = simplify(self, &mut cache);
1029        if res.is_ok() { debug_assert_eq!(cache.len(), 0); }
1030        res
1031    }
1032    /// Converts a [`SimpleValue`] into a [`Value`] for use inside the VM.
1033    pub fn from_simple(mc: &Mutation<'gc>, value: SimpleValue) -> Self {
1034        match value {
1035            SimpleValue::Bool(x) => Value::Bool(x),
1036            SimpleValue::Number(x) => Value::Number(x),
1037            SimpleValue::Text(x) => Value::Text(x.as_str().into()),
1038            SimpleValue::Audio(x) => Value::Audio(Rc::new(x)),
1039            SimpleValue::Image(x) => Value::Image(Rc::new(x)),
1040            SimpleValue::List(x) => Value::List(Gc::new(mc, RefLock::new(x.into_iter().map(|x| Value::from_simple(mc, x)).collect()))),
1041        }
1042    }
1043
1044    /// Returns a value representing this object that implements [`Eq`] such that
1045    /// two values are equal if and only if they are references to the same object.
1046    /// This is primarily useful for testing for reference equality of lists.
1047    pub fn identity(&self) -> Identity<'gc, C, S> {
1048        match self {
1049            Value::Bool(x) => Identity(x as *const bool as *const (), PhantomData),
1050            Value::Number(x) => Identity(x as *const Number as *const (), PhantomData),
1051            Value::Text(x) => Identity(x.as_ptr() as *const (), PhantomData),
1052            Value::Image(x) => Identity(Rc::as_ptr(x) as *const (), PhantomData),
1053            Value::Audio(x) => Identity(Rc::as_ptr(x) as *const (), PhantomData),
1054            Value::List(x) => Identity(Gc::as_ptr(*x) as *const (), PhantomData),
1055            Value::Closure(x) => Identity(Gc::as_ptr(*x) as *const (), PhantomData),
1056            Value::Entity(x) => Identity(Gc::as_ptr(*x) as *const (), PhantomData),
1057            Value::Native(x) => Identity(Rc::as_ptr(x) as *const (), PhantomData),
1058        }
1059    }
1060
1061    /// Attempts to interpret this value as a bool.
1062    pub fn as_bool(&self) -> Result<bool, ConversionError<C, S>> {
1063        Ok(match self {
1064            Value::Bool(x) => *x,
1065            x => return Err(ConversionError { got: x.get_type(), expected: Type::Bool }),
1066        })
1067    }
1068    /// Attempts to interpret this value as a number.
1069    pub fn as_number(&self) -> Result<Number, ConversionError<C, S>> {
1070        match self {
1071            Value::Number(x) => Ok(*x),
1072            Value::Text(x) => SimpleValue::parse_number(x).ok_or(ConversionError { got: Type::Text, expected: Type::Number }),
1073            x => Err(ConversionError { got: x.get_type(), expected: Type::Number }),
1074        }
1075    }
1076    /// Attempts to interpret this value as a string.
1077    pub fn as_text(&self) -> Result<Text, ConversionError<C, S>> {
1078        Ok(match self {
1079            Value::Text(x) => x.clone(),
1080            Value::Number(x) => SimpleValue::stringify_number(*x),
1081            x => return Err(ConversionError { got: x.get_type(), expected: Type::Text }),
1082        })
1083    }
1084    /// Attempts to interpret this value as an image.
1085    pub fn as_image(&self) -> Result<&Rc<Image>, ConversionError<C, S>> {
1086        match self {
1087            Value::Image(x) => Ok(x),
1088            x => Err(ConversionError { got: x.get_type(), expected: Type::Image }),
1089        }
1090    }
1091    /// Attempts to interpret this value as an audio clip.
1092    pub fn as_audio(&self) -> Result<&Rc<Audio>, ConversionError<C, S>> {
1093        match self {
1094            Value::Audio(x) => Ok(x),
1095            x => Err(ConversionError { got: x.get_type(), expected: Type::Audio }),
1096        }
1097    }
1098    /// Attempts to interpret this value as a list.
1099    pub fn as_list(&self) -> Result<Gc<'gc, RefLock<VecDeque<Value<'gc, C, S>>>>, ConversionError<C, S>> {
1100        match self {
1101            Value::List(x) => Ok(*x),
1102            x => Err(ConversionError { got: x.get_type(), expected: Type::List }),
1103        }
1104    }
1105    /// Attempts to interpret this value as a closure.
1106    pub fn as_closure(&self) -> Result<Gc<'gc, RefLock<Closure<'gc, C, S>>>, ConversionError<C, S>> {
1107        match self {
1108            Value::Closure(x) => Ok(*x),
1109            x => Err(ConversionError { got: x.get_type(), expected: Type::Closure }),
1110        }
1111    }
1112    /// Attempts to interpret this value as an entity.
1113    pub fn as_entity(&self) -> Result<Gc<'gc, RefLock<Entity<'gc, C, S>>>, ConversionError<C, S>> {
1114        match self {
1115            Value::Entity(x) => Ok(*x),
1116            x => Err(ConversionError { got: x.get_type(), expected: Type::Entity }),
1117        }
1118    }
1119}
1120
1121/// Information about a closure/lambda function.
1122#[derive(Collect)]
1123#[collect(no_drop, bound = "")]
1124pub struct Closure<'gc, C: CustomTypes<S>, S: System<C>> {
1125    #[collect(require_static)] pub(crate) pos: usize,
1126    #[collect(require_static)] pub(crate) params: Vec<CompactString>,
1127                               pub(crate) captures: SymbolTable<'gc, C, S>,
1128}
1129impl<C: CustomTypes<S>, S: System<C>> fmt::Debug for Closure<'_, C, S> {
1130    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1131        write!(f, "Closure {:#08x}", self.pos)
1132    }
1133}
1134
1135/// The kind of [`Entity`] being represented.
1136pub enum EntityKind<'gc, 'a, C: CustomTypes<S>, S: System<C>> {
1137    Stage { props: Properties },
1138    Sprite { props: Properties },
1139    Clone { parent: &'a Entity<'gc, C, S> },
1140}
1141/// The kind of [`Process`] being represented.
1142pub struct ProcessKind<'gc, 'a, C: CustomTypes<S>, S: System<C>> {
1143    /// The entity associated with the new process.
1144    pub entity: Gc<'gc, RefLock<Entity<'gc, C, S>>>,
1145    /// The existing process, if any, which triggered the creation of the new process.
1146    pub dispatcher: Option<&'a Process<'gc, C, S>>,
1147}
1148
1149/// Information about an entity (sprite or stage).
1150#[derive(Collect)]
1151#[collect(no_drop, bound = "")]
1152pub struct Entity<'gc, C: CustomTypes<S>, S: System<C>> {
1153    #[collect(require_static)] pub name: Text,
1154    #[collect(require_static)] pub sound_list: Rc<VecMap<CompactString, Rc<Audio>, false>>,
1155    #[collect(require_static)] pub costume_list: Rc<VecMap<CompactString, Rc<Image>, false>>,
1156    #[collect(require_static)] pub costume: Option<Rc<Image>>,
1157    #[collect(require_static)] pub state: C::EntityState,
1158                               pub original: Option<Gc<'gc, RefLock<Entity<'gc, C, S>>>>,
1159                               pub fields: SymbolTable<'gc, C, S>,
1160}
1161impl<C: CustomTypes<S>, S: System<C>> fmt::Debug for Entity<'_, C, S> {
1162    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1163        write!(f, "Entity {:?}", self.name)
1164    }
1165}
1166
1167/// Represents a variable whose value is being monitored.
1168/// 
1169/// Users can "show" or "hide" variables to create or remove watchers.
1170/// These can be used by students for a number of purposes, including debugging.
1171#[derive(Collect)]
1172#[collect(no_drop)]
1173pub struct Watcher<'gc, C: CustomTypes<S>, S: System<C>> {
1174    /// The entity associated with the variable being watched.
1175    pub entity: GcWeak<'gc, RefLock<Entity<'gc, C, S>>>,
1176    /// The name of the variable being watched.
1177    #[collect(require_static)]
1178    pub name: CompactString,
1179    /// The value of the variable being watched.
1180    pub value: GcWeak<'gc, RefLock<Value<'gc, C, S>>>,
1181}
1182impl<'gc, C: CustomTypes<S>, S: System<C>> fmt::Debug for Watcher<'gc, C, S> {
1183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1184        f.debug_struct("Watcher").field("name", &self.name).finish_non_exhaustive()
1185    }
1186}
1187
1188/// Represents a shared mutable resource.
1189/// 
1190/// This is effectively equivalent to [`Gc<T>`] except that it performs no dynamic allocation
1191/// for the [`Shared::Unique`] case, which is assumed to be significantly more likely than [`Shared::Aliased`].
1192#[derive(Collect, Debug)]
1193#[collect(no_drop)]
1194pub enum Shared<'gc, T: 'gc + Collect> {
1195    /// A shared resource which has only (this) single unique handle.
1196    Unique(T),
1197    /// One of several handles to a single shared resource.
1198    Aliased(Gc<'gc, RefLock<T>>),
1199}
1200impl<'gc, T: 'gc + Collect> Shared<'gc, T> {
1201    /// Sets the value of the shared resource.
1202    pub fn set(&mut self, mc: &Mutation<'gc>, value: T) {
1203        match self {
1204            Shared::Unique(x) => *x = value,
1205            Shared::Aliased(x) => *x.borrow_mut(mc) = value,
1206        }
1207    }
1208    /// Gets a reference to the shared resource's currently stored value.
1209    pub fn get(&self) -> SharedRef<T> {
1210        match self {
1211            Shared::Unique(x) => SharedRef::Unique(x),
1212            Shared::Aliased(x) => SharedRef::Aliased(x.borrow()),
1213        }
1214    }
1215    /// Transitions the shared value from [`Shared::Unique`] to [`Shared::Aliased`] if it has not already,
1216    /// and returns an additional alias to the underlying value.
1217    pub fn alias_inner(&mut self, mc: &Mutation<'gc>) -> Gc<'gc, RefLock<T>> {
1218        replace_with::replace_with(self, || unreachable!(), |myself| match myself {
1219            Shared::Unique(x) => Shared::Aliased(Gc::new(mc, RefLock::new(x))),
1220            Shared::Aliased(_) => myself,
1221        });
1222
1223        match self {
1224            Shared::Unique(_) => unreachable!(),
1225            Shared::Aliased(x) => *x,
1226        }
1227    }
1228    /// Creates a new instance of [`Shared`] that references the same underlying value.
1229    /// This is equivalent to constructing an instance of [`Shared::Aliased`] with the result of [`Shared::alias_inner`].
1230    pub fn alias(&mut self, mc: &Mutation<'gc>) -> Self {
1231        Shared::Aliased(self.alias_inner(mc))
1232    }
1233}
1234impl<'gc, T: Collect> From<T> for Shared<'gc, T> { fn from(value: T) -> Self { Shared::Unique(value) } }
1235
1236pub enum SharedRef<'a, T> {
1237    Unique(&'a T),
1238    Aliased(Ref<'a, T>)
1239}
1240impl<'a, T> Deref for SharedRef<'a, T> {
1241    type Target = T;
1242    fn deref(&self) -> &Self::Target {
1243        match self {
1244            SharedRef::Unique(x) => x,
1245            SharedRef::Aliased(x) => x,
1246        }
1247    }
1248}
1249
1250/// Holds a collection of variables in an execution context.
1251/// 
1252/// [`SymbolTable`] has utilities to extract variables from an abstract syntax tree, or to explicitly define variables.
1253/// Simple methods are provided to perform value lookups in the table.
1254#[derive(Collect, Educe)]
1255#[collect(no_drop, bound = "")]
1256#[educe(Default, Debug)]
1257pub struct SymbolTable<'gc, C: CustomTypes<S>, S: System<C>>(VecMap<CompactString, Shared<'gc, Value<'gc, C, S>>, true>);
1258impl<'gc, C: CustomTypes<S>, S: System<C>> Clone for SymbolTable<'gc, C, S> {
1259    /// Creates a shallow (non-aliasing) copy of all variables currently stored in this symbol table.
1260    fn clone(&self) -> Self {
1261        let mut res = SymbolTable::default();
1262        for (k, v) in self.iter() {
1263            res.define_or_redefine(k, Shared::Unique(v.get().clone()));
1264        }
1265        res
1266    }
1267}
1268impl<'gc, C: CustomTypes<S>, S: System<C>> SymbolTable<'gc, C, S> {
1269    /// Defines or redefines a value in the symbol table to a new instance of [`Shared<Value>`].
1270    /// If a variable named `var` already existed and was [`Shared::Aliased`], its value is not modified.
1271    pub fn define_or_redefine(&mut self, var: &str, value: Shared<'gc, Value<'gc, C, S>>) {
1272        self.0.insert(CompactString::new(var), value);
1273    }
1274    /// Looks up the given variable in the symbol table.
1275    /// If a variable with the given name does not exist, returns [`None`].
1276    pub fn lookup<'a>(&'a self, var: &str) -> Option<&'a Shared<'gc, Value<'gc, C, S>>> {
1277        self.0.get(var)
1278    }
1279    /// Equivalent to [`SymbolTable::lookup`] except that it returns a mutable reference.
1280    pub fn lookup_mut<'a>(&'a mut self, var: &str) -> Option<&'a mut Shared<'gc, Value<'gc, C, S>>> {
1281        self.0.get_mut(var)
1282    }
1283    /// Gets the number of symbols currently stored in the symbol table.
1284    pub fn len(&self) -> usize {
1285        self.0.len()
1286    }
1287    /// Checks if the symbol table is currently empty (no defined symbols).
1288    pub fn is_empty(&self) -> bool {
1289        self.0.is_empty()
1290    }
1291    /// Iterates over the key value pairs stored in the symbol table.
1292    pub fn iter(&self) -> crate::vecmap::Iter<CompactString, Shared<'gc, Value<'gc, C, S>>> {
1293        self.0.iter()
1294    }
1295    /// Iterates over the key value pairs stored in the symbol table.
1296    pub fn iter_mut(&mut self) -> crate::vecmap::IterMut<CompactString, Shared<'gc, Value<'gc, C, S>>> {
1297        self.0.iter_mut()
1298    }
1299}
1300
1301/// A collection of symbol tables with hierarchical context searching.
1302pub(crate) struct LookupGroup<'gc, 'a, 'b, C: CustomTypes<S>, S: System<C>>(&'a mut [&'b mut SymbolTable<'gc, C, S>]);
1303impl<'gc, 'a: 'b, 'b, C: CustomTypes<S>, S: System<C>> LookupGroup<'gc, 'a, 'b, C, S> {
1304    /// Creates a new lookup group.
1305    /// The first symbol table is intended to be the most-global, and subsequent tables are increasingly more-local.
1306    pub fn new(tables: &'a mut [&'b mut SymbolTable<'gc, C, S>]) -> Self {
1307        debug_assert!(!tables.is_empty());
1308        Self(tables)
1309    }
1310    /// Searches for the given variable in this group of lookup tables,
1311    /// starting with the last (most-local) table and working towards the first (most-global) table.
1312    /// Returns a reference to the value if it is found, otherwise returns [`None`].
1313    pub fn lookup<'s: 'a>(&'s self, var: &str) -> Option<&'b Shared<'gc, Value<'gc, C, S>>> {
1314        for src in self.0.iter().rev() {
1315            if let Some(val) = src.lookup(var) {
1316                return Some(val);
1317            }
1318        }
1319        None
1320    }
1321    /// As [`LookupGroup::lookup`], but returns a mutable reference.
1322    pub fn lookup_mut<'s: 'a>(&'s mut self, var: &str) -> Option<&'b mut Shared<'gc, Value<'gc, C, S>>> {
1323        for src in self.0.iter_mut().rev() {
1324            if let Some(val) = src.lookup_mut(var) {
1325                return Some(val);
1326            }
1327        }
1328        None
1329    }
1330}
1331
1332/// The error promotion paradigm to use for certain types of runtime errors.
1333#[derive(Clone, Copy)]
1334pub enum ErrorScheme {
1335    /// Emit errors as soft errors. This causes the error message to be returned as a [`Value::Text`] object,
1336    /// as well as being stored in a corresponding last-error process-local variable.
1337    Soft,
1338    /// Emit errors as hard errors. This treats certain classes of typically soft errors as hard errors that
1339    /// must be caught or else terminate the [`Process`] (not the entire VM).
1340    Hard,
1341}
1342
1343/// Settings to use for a [`Process`].
1344#[derive(Clone, Copy)]
1345pub struct Settings {
1346    /// The maximum depth of the call stack (default `1024`).
1347    pub max_call_depth: usize,
1348    /// The error pattern to use for rpc errors (default [`ErrorScheme::Hard`]).
1349    pub rpc_error_scheme: ErrorScheme,
1350    /// The error pattern to use for syscall errors (default [`ErrorScheme::Hard`]).
1351    pub syscall_error_scheme: ErrorScheme,
1352}
1353impl Default for Settings {
1354    fn default() -> Self {
1355        Self {
1356            max_call_depth: 1024,
1357            rpc_error_scheme: ErrorScheme::Hard,
1358            syscall_error_scheme: ErrorScheme::Hard,
1359        }
1360    }
1361}
1362
1363/// Global information about the execution state of an entire project.
1364#[derive(Collect)]
1365#[collect(no_drop, bound = "")]
1366pub struct GlobalContext<'gc, C: CustomTypes<S>, S: System<C>> {
1367    #[collect(require_static)] pub bytecode: Rc<ByteCode>,
1368    #[collect(require_static)] pub settings: Settings,
1369    #[collect(require_static)] pub system: Rc<S>,
1370    #[collect(require_static)] pub timer_start: u64,
1371    #[collect(require_static)] pub proj_name: Text,
1372                               pub globals: SymbolTable<'gc, C, S>,
1373                               pub entities: VecMap<Text, Gc<'gc, RefLock<Entity<'gc, C, S>>>, false>,
1374}
1375impl<'gc, C: CustomTypes<S>, S: System<C>> GlobalContext<'gc, C, S> {
1376    pub fn from_init(mc: &Mutation<'gc>, init_info: &InitInfo, bytecode: Rc<ByteCode>, settings: Settings, system: Rc<S>) -> Self {
1377        let allocated_refs = init_info.ref_values.iter().map(|ref_value| match ref_value {
1378            RefValue::Text(value) => Value::Text(value.as_str().into()),
1379            RefValue::Image(content, center, name) => Value::Image(Rc::new(Image {content: content.clone(), center: *center, name: name.clone() })),
1380            RefValue::Audio(content, name) => Value::Audio(Rc::new(Audio { content: content.clone(), name: name.clone() })),
1381            RefValue::List(_) => Value::List(Gc::new(mc, Default::default())),
1382        }).collect::<Vec<_>>();
1383
1384        fn get_value<'gc, C: CustomTypes<S>, S: System<C>>(value: &InitValue, allocated_refs: &[Value<'gc, C, S>]) -> Value<'gc, C, S> {
1385            match value {
1386                InitValue::Bool(x) => Value::Bool(*x),
1387                InitValue::Number(x) => Value::Number(*x),
1388                InitValue::Ref(x) => allocated_refs[*x].clone(),
1389            }
1390        }
1391
1392        for (allocated_ref, ref_value) in iter::zip(&allocated_refs, &init_info.ref_values) {
1393            match ref_value {
1394                RefValue::Text(_) | RefValue::Image(_, _, _) | RefValue::Audio(_, _) => continue, // we already populated these values in the first pass
1395                RefValue::List(values) => {
1396                    let allocated_ref = match allocated_ref {
1397                        Value::List(x) => x,
1398                        _ => unreachable!(),
1399                    };
1400                    let mut allocated_ref = allocated_ref.borrow_mut(mc);
1401                    for value in values {
1402                        allocated_ref.push_back(get_value(value, &allocated_refs));
1403                    }
1404                }
1405            }
1406        }
1407
1408        let mut globals = SymbolTable::default();
1409        for (global, value) in init_info.globals.iter() {
1410            globals.define_or_redefine(global, Shared::Unique(get_value(value, &allocated_refs)));
1411        }
1412
1413        let mut entities = VecMap::with_capacity(init_info.entities.len());
1414        for (i, entity_info) in init_info.entities.iter().enumerate() {
1415            let mut fields = SymbolTable::default();
1416            for (field, value) in entity_info.fields.iter() {
1417                fields.define_or_redefine(field, Shared::Unique(get_value(value, &allocated_refs)));
1418            }
1419
1420            let sound_list = {
1421                let mut res = VecMap::with_capacity(entity_info.sounds.len());
1422                for (name, value) in entity_info.sounds.iter() {
1423                    let sound = match get_value(value, &allocated_refs) {
1424                        Value::Audio(x) => x.clone(),
1425                        _ => unreachable!(),
1426                    };
1427                    res.insert(name.clone(), sound);
1428                }
1429                Rc::new(res)
1430            };
1431
1432            let costume_list = {
1433                let mut res = VecMap::with_capacity(entity_info.costumes.len());
1434                for (name, value) in entity_info.costumes.iter() {
1435                    let image = match get_value(value, &allocated_refs) {
1436                        Value::Image(x) => x.clone(),
1437                        _ => unreachable!(),
1438                    };
1439                    res.insert(name.clone(), image);
1440                }
1441                Rc::new(res)
1442            };
1443
1444            let costume = entity_info.active_costume.and_then(|x| costume_list.as_slice().get(x)).map(|x| x.value.clone());
1445
1446            let mut props = Properties::default();
1447            props.visible = entity_info.visible;
1448            props.size = entity_info.size;
1449            props.pos = entity_info.pos;
1450            props.heading = entity_info.heading;
1451
1452            let (r, g, b, a) = entity_info.color;
1453            let (h, s, v, a) = Color { r, g, b, a }.to_hsva();
1454            props.pen_color_h = Number::new(h as f64).unwrap();
1455            props.pen_color_s = Number::new(s as f64 * 100.0).unwrap();
1456            props.pen_color_v = Number::new(v as f64 * 100.0).unwrap();
1457            props.pen_color_t = Number::new((1.0 - a as f64) * 100.0).unwrap();
1458
1459            let state = C::EntityState::from(if i == 0 { EntityKind::Stage { props } } else { EntityKind::Sprite { props } });
1460            let name = Text::from(entity_info.name.as_str());
1461
1462            entities.insert(name.clone(), Gc::new(mc, RefLock::new(Entity { original: None, name, fields, sound_list, costume_list, costume, state })));
1463        }
1464
1465        let proj_name = init_info.proj_name.as_str().into();
1466        let timer_start = system.time(Precision::Medium).to_arbitrary_ms::<C, S>().unwrap_or(0);
1467
1468        Self { proj_name, globals, entities, timer_start, system, settings, bytecode }
1469    }
1470}
1471
1472pub enum OutgoingMessage {
1473    Normal {
1474        msg_type: CompactString,
1475        values: VecMap<CompactString, Json, false>,
1476        targets: Vec<CompactString>,
1477    },
1478    Blocking {
1479        msg_type: CompactString,
1480        values: VecMap<CompactString, Json, false>,
1481        targets: Vec<CompactString>,
1482        reply_key: ExternReplyKey,
1483    },
1484    Reply {
1485        value: Json,
1486        reply_key: InternReplyKey,
1487    },
1488}
1489pub struct IncomingMessage {
1490    pub msg_type: CompactString,
1491    pub values: VecMap<CompactString, SimpleValue, false>,
1492    pub reply_key: Option<InternReplyKey>,
1493}
1494
1495/// A blocking handle for a [`BarrierCondition`].
1496#[derive(Debug, Default, Clone)]
1497pub struct Barrier(Rc<()>);
1498/// Waits for the destruction of all associated [`Barrier`] handles.
1499#[derive(Debug, Clone)]
1500pub struct BarrierCondition(Weak<()>);
1501impl Barrier {
1502    /// Creates a new [`Barrier`] which is not related to any other barrier.
1503    /// A barrier can be cloned to create additional associated, blocking handles for the same condition.
1504    pub fn new() -> Self {
1505        Barrier(Rc::new(()))
1506    }
1507    /// Constructs a [`BarrierCondition`] object which waits for this barrier handle and all of its associated handles
1508    /// (created before or after this point) to be destroyed.
1509    pub fn get_condition(&self) -> BarrierCondition {
1510        BarrierCondition(Rc::downgrade(&self.0))
1511    }
1512}
1513impl BarrierCondition {
1514    /// Checks if the condition has been completed, i.e., that all the associated barriers have been destroyed.
1515    pub fn is_completed(&self) -> bool {
1516        self.0.strong_count() == 0
1517    }
1518}
1519
1520/// The result of a successful call to an async poller operation such as in [`System`].
1521pub enum AsyncResult<T> {
1522    /// The async operation is still pending and has not completed.
1523    Pending,
1524    /// The async operation completed with the given value.
1525    Completed(T),
1526    /// The async operation was completed and the result was already consumed.
1527    Consumed,
1528}
1529impl<T> AsyncResult<T> {
1530    /// Constructs a new async result handle in the [`AsyncResult::Pending`] state.
1531    pub fn new() -> Self {
1532        Self::Pending
1533    }
1534    /// Transitions from the [`AsyncResult::Pending`] state to [`AsyncResult::Completed`] with the provided result value.
1535    /// If this async result handle has already been completed, [`Err`] is returned with the passed value.
1536    pub fn complete(&mut self, value: T) -> Result<(), T> {
1537        match self {
1538            AsyncResult::Pending => {
1539                *self = AsyncResult::Completed(value);
1540                Ok(())
1541            }
1542            AsyncResult::Completed(_) | AsyncResult::Consumed => Err(value),
1543        }
1544    }
1545    /// Polls the status of the async operation.
1546    /// A [`AsyncResult::Completed`] result transitions permanently to the [`AsyncResult::Consumed`] state.
1547    pub fn poll(&mut self) -> Self {
1548        match self {
1549            AsyncResult::Pending => AsyncResult::Pending,
1550            AsyncResult::Completed(_) | AsyncResult::Consumed => mem::replace(self, AsyncResult::Consumed),
1551        }
1552    }
1553}
1554
1555/// Types of [`System`] resources, grouped into feature categories.
1556#[derive(Debug)]
1557pub enum Feature {
1558    /// The ability of a process to get the current time with respect to an arbitrary starting point.
1559    ArbitraryTime,
1560    /// The ability of a process to get the current real time.
1561    RealTime,
1562
1563    /// The ability of a process to request keyboard input from the user.
1564    Input,
1565    /// The ability of a process to display information.
1566    Print,
1567
1568    /// The ability of a process to perform a syscall of the given name.
1569    Syscall { name: CompactString },
1570    /// The ability of a process to perform an RPC call.
1571    Rpc { service: CompactString, rpc: CompactString },
1572
1573    /// The ability of an entity to get a certain property.
1574    GetProperty { prop: Property },
1575    /// The ability of an entity to set a certain property.
1576    SetProperty { prop: Property },
1577    /// The ability of an entity to apply a relative change to a certain property.
1578    ChangeProperty { prop: Property },
1579
1580    /// The ability of an entity to change the current costume.
1581    SetCostume,
1582
1583    /// The ability of an entity to play a sound, optionally blocking until completion.
1584    PlaySound { blocking: bool },
1585    /// The ability of an entity to play musical notes, optionally blocking until completion.
1586    PlayNotes { blocking: bool },
1587    /// The ability of an entity to stop playback of currently-playing sounds.
1588    StopSounds,
1589
1590    /// The ability to clear all graphic effects on an entity. This is equivalent to setting all the graphic effect properties to zero.
1591    ClearEffects,
1592    /// The ability to clear all drawings made by all sprites.
1593    ClearDrawings,
1594
1595    /// The ability of an entity to set both its x and y positions simultaneously.
1596    GotoXY,
1597    /// The ability of an entity to go the the same location as another entity.
1598    GotoEntity,
1599
1600    /// The ability of an entity to turn to face a specific location.
1601    PointTowardsXY,
1602    /// The ability of an entity to turn to face another entity.
1603    PointTowardsEntity,
1604
1605    /// The ability of an entity to move forward or backwards by a distance.
1606    Forward,
1607
1608    /// The ability of an entity to execute a specific block that was not built in to the ast parser or bytecode compiler (e.g., extension blocks).
1609    UnknownBlock { name: CompactString },
1610}
1611
1612/// A value-returning request issued from the runtime.
1613pub enum Request<'gc, C: CustomTypes<S>, S: System<C>> {
1614    /// Request input from the user. The `prompt` argument is either [`Some`] prompt to display, or [`None`] for no prompt.
1615    Input { prompt: Option<Value<'gc, C, S>> },
1616    /// Performs a system call on the local hardware to access device resources.
1617    Syscall { name: CompactString, args: Vec<Value<'gc, C, S>> },
1618    /// Requests the system to execute the given RPC.
1619    Rpc { host: Option<CompactString>, service: CompactString, rpc: CompactString, args: VecMap<CompactString, Value<'gc, C, S>, false> },
1620    /// Request to get the current value of an entity property.
1621    Property { prop: Property },
1622    /// Request to run a block which was not known by the ast parser or bytecode compiler.
1623    /// This is typically used for implementing extension blocks in the VM, which cannot be handled otherwise.
1624    UnknownBlock { name: CompactString, args: Vec<Value<'gc, C, S>> },
1625}
1626impl<'gc, C: CustomTypes<S>, S: System<C>> Request<'gc, C, S> {
1627    /// Gets the [`Feature`] associated with this request.
1628    pub fn feature(&self) -> Feature {
1629        match self {
1630            Request::Input { .. } => Feature::Input,
1631            Request::Syscall { name, .. } => Feature::Syscall { name: name.clone() },
1632            Request::Rpc { service, rpc, .. } => Feature::Rpc { service: service.clone(), rpc: rpc.clone() },
1633            Request::Property { prop } => Feature::GetProperty { prop: *prop },
1634            Request::UnknownBlock { name, .. } => Feature::UnknownBlock { name: name.clone() },
1635        }
1636    }
1637}
1638
1639/// A non-value-returning command issued from the runtime.
1640pub enum Command<'gc, 'a, C: CustomTypes<S>, S: System<C>> {
1641    /// Output [`Some`] [`Value`] or [`None`] to perform a Snap!-style clear.
1642    Print { style: PrintStyle, value: Option<Value<'gc, C, S>> },
1643
1644    /// Set an entity property to a specific value.
1645    SetProperty { prop: Property, value: Value<'gc, C, S> },
1646    /// Apply a relative change to the value of an entity property.
1647    ChangeProperty { prop: Property, delta: Value<'gc, C, S> },
1648
1649    /// Clear all graphic effects on the entity. This is equivalent to setting all the graphic effect properties to zero.
1650    ClearEffects,
1651    /// Clear all drawings made by all sprites.
1652    ClearDrawings,
1653
1654    /// Sets the costume on the entity.
1655    /// At this point the costume has already been assigned to [`Entity::costume`],
1656    /// so this is just a hook for any custom update code that is needed for external purposes.
1657    SetCostume,
1658
1659    /// Plays a sound, optionally with a request to block until the sound is finished playing.
1660    /// It is up to the receiver to actually satisfy this blocking aspect, if desired.
1661    PlaySound { sound: Rc<Audio>, blocking: bool },
1662    /// Plays zero or more notes, optionally with a request to block until the notes are finished playing.
1663    /// It is up to the receiver to actually satisfy this blocking aspect, if desired.
1664    PlayNotes { notes: Vec<Note>, beats: Number, blocking: bool },
1665    /// Requests to stop playback of all currently-playing sounds.
1666    StopSounds,
1667
1668    /// Moves the entity to a specific location.
1669    GotoXY { x: Number, y: Number },
1670    /// Moves the current entity to the same position as the target entity.
1671    GotoEntity { target: &'a Entity<'gc, C, S> },
1672
1673    /// Points the entity towards a specific location.
1674    PointTowardsXY { x: Number, y: Number },
1675    /// Points the current entity towards a target entity.
1676    PointTowardsEntity { target: &'a Entity<'gc, C, S> },
1677
1678    /// Move forward by a given distance. If the distance is negative, move backwards instead.
1679    Forward { distance: Number },
1680}
1681impl<'gc, C: CustomTypes<S>, S: System<C>> Command<'gc, '_, C, S> {
1682    /// Gets the [`Feature`] associated with this command.
1683    pub fn feature(&self) -> Feature {
1684        match self {
1685            Command::Print { .. } => Feature::Print,
1686            Command::SetProperty { prop, .. } => Feature::SetProperty { prop: *prop },
1687            Command::ChangeProperty { prop, .. } => Feature::ChangeProperty { prop: *prop },
1688            Command::SetCostume => Feature::SetCostume,
1689            Command::PlaySound { blocking, .. } => Feature::PlaySound { blocking: *blocking },
1690            Command::PlayNotes { blocking, .. } => Feature::PlayNotes { blocking: *blocking },
1691            Command::StopSounds => Feature::StopSounds,
1692            Command::ClearEffects => Feature::ClearEffects,
1693            Command::ClearDrawings => Feature::ClearDrawings,
1694            Command::GotoXY { .. } => Feature::GotoXY,
1695            Command::GotoEntity { .. } => Feature::GotoEntity,
1696            Command::PointTowardsXY { .. } => Feature::PointTowardsXY,
1697            Command::PointTowardsEntity { .. } => Feature::PointTowardsEntity,
1698            Command::Forward { .. } => Feature::Forward,
1699        }
1700    }
1701}
1702
1703/// The status of a potentially-handled request.
1704pub enum RequestStatus<'gc, C: CustomTypes<S>, S: System<C>> {
1705    /// The request was handled by the overriding client.
1706    Handled,
1707    /// The request was not handled by the overriding client,
1708    /// and the default system implementation should be used instead.
1709    UseDefault { key: S::RequestKey, request: Request<'gc, C, S> },
1710}
1711/// The status of a potentially-handled command.
1712pub enum CommandStatus<'gc, 'a, C: CustomTypes<S>, S: System<C>> {
1713    /// The command was handled by the overriding client.
1714    Handled,
1715    /// The command was not handled by the overriding client,
1716    /// and the default system implementation should be used instead.
1717    UseDefault { key: S::CommandKey, command: Command<'gc, 'a, C, S> },
1718}
1719
1720/// A collection of implementation options that could be used for implementing a customizable [`System`].
1721#[derive(Educe)]
1722#[educe(Clone)]
1723pub struct Config<C: CustomTypes<S>, S: System<C>> {
1724    /// A function used to perform asynchronous requests that yield a value back to the runtime.
1725    pub request: Option<Rc<dyn for<'gc> Fn(&Mutation<'gc>, S::RequestKey, Request<'gc, C, S>, &mut Process<'gc, C, S>) -> RequestStatus<'gc, C, S>>>,
1726    /// A function used to perform asynchronous tasks whose completion is awaited by the runtime.
1727    pub command: Option<Rc<dyn for<'gc, 'a> Fn(&Mutation<'gc>, S::CommandKey, Command<'gc, 'a, C, S>, &mut Process<'gc, C, S>) -> CommandStatus<'gc, 'a, C, S>>>,
1728}
1729impl<C: CustomTypes<S>, S: System<C>> Default for Config<C, S> {
1730    fn default() -> Self {
1731        Self {
1732            request: None,
1733            command: None,
1734        }
1735    }
1736}
1737impl<C: CustomTypes<S>, S: System<C>> Config<C, S> {
1738    /// Composes two [`Config`] objects, prioritizing the implementation of `self`.
1739    pub fn fallback(&self, other: &Self) -> Self {
1740        Self {
1741            request: match (self.request.clone(), other.request.clone()) {
1742                (Some(a), Some(b)) => Some(Rc::new(move |mc, key, request, proc| {
1743                    match a(mc, key, request, proc) {
1744                        RequestStatus::Handled => RequestStatus::Handled,
1745                        RequestStatus::UseDefault { key, request } => b(mc, key, request, proc),
1746                    }
1747                })),
1748                (Some(a), None) | (None, Some(a)) => Some(a),
1749                (None, None) => None,
1750            },
1751            command: match (self.command.clone(), other.command.clone()) {
1752                (Some(a), Some(b)) => Some(Rc::new(move |mc, key, command, proc| {
1753                    match a(mc, key, command, proc) {
1754                        CommandStatus::Handled => CommandStatus::Handled,
1755                        CommandStatus::UseDefault { key, command } => b(mc, key, command, proc),
1756                    }
1757                })),
1758                (Some(a), None) | (None, Some(a)) => Some(a),
1759                (None, None) => None,
1760            },
1761        }
1762    }
1763}
1764
1765/// Represents a stack-like object that can unwind back to an arbitrary earlier point.
1766pub trait Unwindable {
1767    /// A type to hold any information necessary to unwind the [`Unwindable`] type to an arbitrary earlier point.
1768    type UnwindPoint: 'static;
1769
1770    /// Gets a new unwind point that can later be used to unwind the [`Unwindable`] type to the current point.
1771    fn get_unwind_point(&self) -> Self::UnwindPoint;
1772    /// Unwinds the [`Unwindable`] type to the given earlier point.
1773    fn unwind_to(&mut self, unwind_point: &Self::UnwindPoint);
1774}
1775
1776/// A collection of static settings for using custom native types.
1777pub trait CustomTypes<S: System<Self>>: 'static + Sized {
1778    /// A native type that can be exposed directly to the VM as a value of type [`Value::Native`].
1779    /// This could, for example, be used to allow a process to hold on to a file handle stored in a variable.
1780    /// For type checking, this is required to implement [`GetType`], which can use a custom type enum.
1781    /// Native types have reference semantics in the vm.
1782    type NativeValue: 'static + GetType + fmt::Debug;
1783
1784    /// An intermediate type used to produce a [`Value`] for the [`System`] under async context.
1785    /// The reason this is needed is that [`Value`] can only be used during the lifetime of an associated [`Mutation`] handle,
1786    /// which cannot be extended into the larger lifetime required for async operations.
1787    /// Conversions are automatically performed from this type to [`Value`] via [`CustomTypes::from_intermediate`].
1788    type Intermediate: 'static + Send + From<SimpleValue>;
1789
1790    /// Type used to represent an entity's system-specific state.
1791    /// This should include any details outside of core process functionality (e.g., graphics, position, orientation).
1792    /// This type should be constructable from [`EntityKind`], which is used to initialize a new entity in the runtime.
1793    type EntityState: 'static + for<'gc, 'a> From<EntityKind<'gc, 'a, Self, S>>;
1794
1795    /// Type used to represent a process's system-specific state.
1796    /// This should include any details outside of core process functionality (e.g., external script-locals).
1797    /// This type should be constructable from [`ProcessKind`], which is used to initialize a new process in the runtime.
1798    type ProcessState: 'static + Unwindable + for<'gc, 'a> From<ProcessKind<'gc, 'a, Self, S>>;
1799
1800    /// Converts a [`Value`] into a [`CustomTypes::Intermediate`] for use outside of gc context.
1801    fn from_intermediate<'gc>(mc: &Mutation<'gc>, value: Self::Intermediate) -> Value<'gc, Self, S>;
1802}
1803
1804/// The time as determined by an implementation of [`System`].
1805pub enum SysTime {
1806    /// No concept of time. This should only be produced as a last resort, as it disables all time-based features.
1807    Timeless,
1808    /// A time measurement from an arbitrary (but consistent during runtime) starting point, which must be measured in milliseconds.
1809    /// For instance, this could be used to measure uptime on systems that do not support reading real time.
1810    Arbitrary { ms: u64 },
1811    /// A real-world time measurement.
1812    /// This is always preferable over [`SysTime::Arbitrary`].
1813    /// The value is assumed to already be transformed to local time.
1814    Real { local: OffsetDateTime },
1815}
1816impl SysTime {
1817    /// Attempt to convert this time into milliseconds after some arbitrary starting point.
1818    pub fn to_arbitrary_ms<C: CustomTypes<S>, S: System<C>>(&self) -> Result<u64, ErrorCause<C, S>> {
1819        match self {
1820            Self::Timeless => Err(ErrorCause::NotSupported { feature: Feature::ArbitraryTime }),
1821            Self::Arbitrary { ms } => Ok(*ms),
1822            Self::Real { local } => Ok((local.unix_timestamp_nanos() / 1000000) as u64),
1823        }
1824    }
1825    /// Attempt to convert this time into a real world time in the local timezone.
1826    pub fn to_real_local<C: CustomTypes<S>, S: System<C>>(&self) -> Result<time::OffsetDateTime, ErrorCause<C, S>> {
1827        match self {
1828            Self::Timeless | Self::Arbitrary { .. } => Err(ErrorCause::NotSupported { feature: Feature::RealTime }),
1829            Self::Real { local } => Ok(*local),
1830        }
1831    }
1832}
1833
1834/// The required precision of a measurement.
1835#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1836pub enum Precision {
1837    Low,
1838    Medium,
1839    High,
1840}
1841
1842const SHARP_NOTES: &'static str = "A A# B C C# D D# E F F# G G#";
1843const FLAT_NOTES: &'static str = "A Bb B C Db D Eb E F Gb G Ab";
1844
1845/// A musical note represented as midi.
1846#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1847#[repr(transparent)]
1848pub struct Note(u8);
1849impl Note {
1850    /// Attempts to construct a new note from a raw midi value, failing if the value is out of range.
1851    pub fn from_midi(val: u8, natural: bool) -> Option<Self> {
1852        if val < 0x80 { Some(Self(if natural { 0x80 } else { 0 } | val)) } else { None }
1853    }
1854    /// Attempts to construct a new note from the name of a note, accepting a range of valid formats.
1855    pub fn from_name(name: &str) -> Option<Self> {
1856        let mut c = name.trim().chars().peekable();
1857
1858        let note = c.next().unwrap_or('\0').to_ascii_uppercase();
1859        let note = SHARP_NOTES.split(' ').enumerate().find(|x| x.1.len() == 1 && x.1.starts_with(note))?.0 as i32;
1860
1861        let mut octave = None;
1862        let mut delta = 0;
1863        let mut natural = false;
1864        loop {
1865            match c.next() {
1866                Some(ch) => match ch {
1867                    '+' | '-' | '0'..='9' => {
1868                        if octave.is_some() { return None }
1869
1870                        let mut v = CompactString::default();
1871                        v.push(ch);
1872                        loop {
1873                            match c.peek() {
1874                                Some('0'..='9') => v.push(c.next().unwrap()),
1875                                _ => break,
1876                            }
1877                        }
1878                        octave = Some(v.parse::<i32>().ok()?);
1879                    }
1880                    's' | '#' | '♯' => { delta += 1; natural = true; }
1881                    'b' | '♭' => { delta -= 1; natural = true; }
1882                    'n' | '♮' => natural = true,
1883                    _ => return None,
1884                }
1885                None => break,
1886            }
1887        }
1888        let mut octave = octave?;
1889        if note >= 3 {
1890            octave -= 1;
1891        }
1892
1893        let value = 21 + note + 12 * octave + delta;
1894        if value >= 0 { Self::from_midi(value as u8, natural) } else { None }
1895    }
1896    /// Gets the stored midi value.
1897    pub fn get_midi(self) -> (u8, bool) {
1898        (self.0 & 0x7f, self.0 & 0x80 != 0)
1899    }
1900    /// Computes the frequency of the note in Hz.
1901    pub fn get_frequency(self) -> Number {
1902        Number::new(440.0 * libm::exp2((self.0 as f64 - 69.0) / 12.0)).unwrap()
1903    }
1904    /// Gets the (English) name of the note.
1905    pub fn get_name(self, prefer_sharps: bool) -> CompactString {
1906        let octave = self.0 as i32 / 12 - 1;
1907        let note = if prefer_sharps { SHARP_NOTES } else { FLAT_NOTES }.split(' ').nth((self.0 as usize + 3) % 12).unwrap();
1908        format_compact!("{note}{octave}")
1909    }
1910}
1911
1912/// A key type used to await a reply message from an external source.
1913#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
1914pub struct ExternReplyKey {
1915    pub request_id: CompactString,
1916}
1917/// A key type required for this client to send a reply message.
1918#[derive(Debug, Clone)]
1919pub struct InternReplyKey {
1920    pub src_id: CompactString,
1921    pub request_id: CompactString,
1922}
1923
1924/// Represents all the features of an implementing system.
1925/// 
1926/// This type encodes any features that cannot be performed without platform-specific resources.
1927/// 
1928/// When implementing [`System`] for some type, you may prefer to not support one or more features.
1929/// This can be accomplished by returning the [`ErrorCause::NotSupported`] variant for the relevant [`Feature`].
1930pub trait System<C: CustomTypes<Self>>: 'static + Sized {
1931    /// Key type used to await the result of an asynchronous request.
1932    type RequestKey: 'static + Key<Result<C::Intermediate, CompactString>>;
1933    /// Key type used to await the completion of an asynchronous command.
1934    type CommandKey: 'static + Key<Result<(), CompactString>>;
1935
1936    /// Gets a random value sampled from the given `range`, which is assumed to be non-empty.
1937    /// The input for this generic function is such that it is compatible with [`rand::Rng::gen_range`],
1938    /// which makes it possible to implement this function with any random provider under the [`rand`] crate standard.
1939    fn rand<T: SampleUniform, R: SampleRange<T>>(&self, range: R) -> T;
1940
1941    /// Gets the current system time.
1942    fn time(&self, precision: Precision) -> SysTime;
1943
1944    /// Performs a general request which returns a value to the system.
1945    /// Ideally, this function should be non-blocking, and the requestor will await the result asynchronously.
1946    /// The [`Entity`] that made the request is provided for context.
1947    fn perform_request<'gc>(&self, mc: &Mutation<'gc>, request: Request<'gc, C, Self>, proc: &mut Process<'gc, C, Self>) -> Result<Self::RequestKey, ErrorCause<C, Self>>;
1948    /// Poll for the completion of an asynchronous request.
1949    /// The [`Entity`] that made the request is provided for context.
1950    fn poll_request<'gc>(&self, mc: &Mutation<'gc>, key: &Self::RequestKey, proc: &mut Process<'gc, C, Self>) -> Result<AsyncResult<Result<Value<'gc, C, Self>, CompactString>>, ErrorCause<C, Self>>;
1951
1952    /// Performs a general command which does not return a value to the system.
1953    /// Ideally, this function should be non-blocking, and the commander will await the task's completion asynchronously.
1954    /// The [`Entity`] that issued the command is provided for context.
1955    fn perform_command<'gc>(&self, mc: &Mutation<'gc>, command: Command<'gc, '_, C, Self>, proc: &mut Process<'gc, C, Self>) -> Result<Self::CommandKey, ErrorCause<C, Self>>;
1956    /// Poll for the completion of an asynchronous command.
1957    /// The [`Entity`] that issued the command is provided for context.
1958    fn poll_command<'gc>(&self, mc: &Mutation<'gc>, key: &Self::CommandKey, proc: &mut Process<'gc, C, Self>) -> Result<AsyncResult<Result<(), CompactString>>, ErrorCause<C, Self>>;
1959
1960    /// Sends a message containing a set of named `values` to each of the specified `targets`.
1961    /// The `expect_reply` value controls whether or not to use a reply mechanism to asynchronously receive a response from the target(s).
1962    /// In the case that there are multiple targets, only the first reply (if any) should be used.
1963    fn send_message(&self, msg_type: CompactString, values: VecMap<CompactString, Json, false>, targets: Vec<CompactString>, expect_reply: bool) -> Result<Option<ExternReplyKey>, ErrorCause<C, Self>>;
1964    /// Polls for a response from a client initiated by [`System::send_message`].
1965    /// If the client responds, a value of [`Some(x)`] is returned.
1966    /// The system may elect to impose a timeout for reply results, in which case [`None`] is returned instead.
1967    fn poll_reply(&self, key: &ExternReplyKey) -> AsyncResult<Option<Json>>;
1968    /// Sends a reply to the sender of a blocking message this client received.
1969    fn send_reply(&self, key: InternReplyKey, value: Json) -> Result<(), ErrorCause<C, Self>>;
1970    /// Attempts to receive a message from the message buffer.
1971    /// This operation is always non-blocking and returns [`None`] if there are no messages in the buffer.
1972    /// If a message is received, a tuple of form `(msg_type, values, reply_key)` is returned.
1973    fn receive_message(&self) -> Option<IncomingMessage>;
1974}