use alloc::collections::{BTreeMap, BTreeSet, VecDeque};
use alloc::rc::{Rc, Weak};
use alloc::borrow::{Cow, ToOwned};
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::marker::PhantomData;
use core::{iter, fmt, mem};
use core::ops::Deref;
use core::cell::Ref;
use rand::distributions::uniform::{SampleUniform, SampleRange};
use checked_float::{FloatChecker, CheckedFloat};
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
use crate::*;
use crate::gc::*;
use crate::json::*;
use crate::real_time::*;
use crate::bytecode::*;
#[derive(Debug)]
pub enum NumberError {
Nan,
}
pub struct NumberChecker;
impl FloatChecker<f64> for NumberChecker {
type Error = NumberError;
fn check(value: f64) -> Result<(), Self::Error> {
if value.is_nan() { return Err(NumberError::Nan); }
Ok(())
}
}
pub type Number = CheckedFloat<f64, NumberChecker>;
#[derive(Debug)]
pub enum FromAstError<'a> {
BadNumber { error: NumberError },
BadKeycode { key: String },
UnsupportedEvent { kind: &'a ast::HatKind },
CompileError { error: CompileError<'a> },
}
impl From<NumberError> for FromAstError<'_> { fn from(error: NumberError) -> Self { Self::BadNumber { error } } }
impl<'a> From<CompileError<'a>> for FromAstError<'a> { fn from(error: CompileError<'a>) -> Self { Self::CompileError { error } } }
#[derive(Debug)]
pub enum FromJsonError {
HadNull,
HadBadNumber,
}
#[derive(Educe)]
#[educe(Debug)]
pub enum ToJsonError<C: CustomTypes<S>, S: System<C>> {
BadNumber(f64),
ComplexType(Type<C, S>),
Cyclic,
}
#[derive(Educe)]
#[educe(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Type<C: CustomTypes<S>, S: System<C>> {
Bool, Number, String, Image, Audio, List, Closure, Entity, Native(<C::NativeValue as GetType>::Output),
}
#[derive(Educe)]
#[educe(Debug)]
pub struct ConversionError<C: CustomTypes<S>, S: System<C>> {
pub got: Type<C, S>,
pub expected: Type<C, S>,
}
#[derive(Educe)]
#[educe(Debug)]
pub enum ErrorCause<C: CustomTypes<S>, S: System<C>> {
UndefinedVariable { name: String },
UndefinedCostume { name: String },
UndefinedEntity { name: String },
UpvarAtRoot,
ConversionError { got: Type<C, S>, expected: Type<C, S> },
VariadicConversionError { got: Type<C, S>, expected: Type<C, S> },
Incomparable { left: Type<C, S>, right: Type<C, S> },
EmptyList,
InvalidListLength { expected: usize, got: usize },
IndexOutOfBounds { index: i64, len: usize },
IndexNotInteger { index: f64 },
InvalidSize { value: f64 },
InvalidUnicode { value: f64 },
CallDepthLimit { limit: usize },
ClosureArgCount { expected: usize, got: usize },
CyclicValue,
NotCsv { value: String },
NotJson { value: String },
ToJsonError { error: ToJsonError<C, S> },
FromJsonError { error: FromJsonError },
NumberError { error: NumberError },
NotSupported { feature: Feature },
Promoted { error: String },
Custom { msg: String },
}
impl<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 } } }
impl<C: CustomTypes<S>, S: System<C>> From<ToJsonError<C, S>> for ErrorCause<C, S> { fn from(error: ToJsonError<C, S>) -> Self { Self::ToJsonError { error } } }
impl<C: CustomTypes<S>, S: System<C>> From<FromJsonError> for ErrorCause<C, S> { fn from(error: FromJsonError) -> Self { Self::FromJsonError { error } } }
impl<C: CustomTypes<S>, S: System<C>> From<NumberError> for ErrorCause<C, S> { fn from(error: NumberError) -> Self { Self::NumberError { error } } }
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Color { pub r: u8, pub g: u8, pub b: u8, pub a: u8 }
impl Color {
pub fn from_hsva(mut h: f32, mut s: f32, mut v: f32, mut a: f32) -> Self {
h = h.rem_euclid(360.0);
s = s.clamp(0.0, 1.0);
v = v.clamp(0.0, 1.0);
a = a.clamp(0.0, 1.0);
let c = v * s;
let hp = h / 60.0;
let x = c * (1.0 - (hp % 2.0 - 1.0).abs());
let m = v - c;
let (r, g, b) = match hp as usize {
0 | 6 => (c, x, 0.0), 1 => (x, c, 0.0),
2 => (0.0, c, x),
3 => (0.0, x, c),
4 => (x, 0.0, c),
5 => (c, 0.0, x),
_ => unreachable!(),
};
fn f(x: f32) -> u8 { (x * 255.0).round() as u8 }
Self { r: f(r + m), g: f(g + m), b: f(b + m), a: f(a) }
}
pub fn to_hsva(self) -> (f32, f32, f32, f32) {
fn f(x: u8) -> f32 { x as f32 / 255.0 }
let vals = [self.r, self.g, self.b];
let (c_max_i, c_max) = vals.iter().copied().enumerate().max_by_key(|x| x.1).map(|(i, v)| (i, f(v))).unwrap();
let c_min = vals.iter().copied().min().map(f).unwrap();
let delta = c_max - c_min;
let h = if delta == 0.0 { 0.0 } else {
match c_max_i {
0 => 60.0 * ((f(self.g) - f(self.b)) / delta).rem_euclid(6.0),
1 => 60.0 * ((f(self.b) - f(self.r)) / delta + 2.0),
2 => 60.0 * ((f(self.r) - f(self.g)) / delta + 4.0),
_ => unreachable!(),
}
};
let s = if c_max == 0.0 { 0.0 } else { delta / c_max };
let v = c_max;
let a = f(self.a);
(h, s, v, a)
}
}
#[test]
fn test_color_hsv_to_rgb() {
assert_eq!(Color::from_hsva(0.0, 0.0, 0.0, 1.0), Color { r: 0x00, g: 0x00, b: 0x00, a: 0xFF });
assert_eq!(Color::from_hsva(0.0, -0.5, 0.0, 1.0), Color { r: 0x00, g: 0x00, b: 0x00, a: 0xFF });
assert_eq!(Color::from_hsva(0.0, 0.07, 0.36, 1.0), Color { r: 0x5C, g: 0x55, b: 0x55, a: 0xFF });
assert_eq!(Color::from_hsva(0.0, 1.0, 0.36, 1.0), Color { r: 92, g: 0, b: 0, a: 0xFF });
assert_eq!(Color::from_hsva(0.0, 1.5, 0.36, 1.0), Color { r: 92, g: 0, b: 0, a: 0xFF });
assert_eq!(Color::from_hsva(0.0, 1.3, 0.36, 1.0), Color { r: 92, g: 0, b: 0, a: 0xFF });
assert_eq!(Color::from_hsva(0.0, 14.5, 0.36, 1.0), Color { r: 92, g: 0, b: 0, a: 0xFF });
assert_eq!(Color::from_hsva(0.0, 0.0, 0.36, 1.0), Color { r: 92, g: 92, b: 92, a: 0xFF });
assert_eq!(Color::from_hsva(0.0, -2.4, 0.36, 1.0), Color { r: 92, g: 92, b: 92, a: 0xFF });
assert_eq!(Color::from_hsva(0.0, -0.4, 0.36, 1.0), Color { r: 92, g: 92, b: 92, a: 0xFF });
assert_eq!(Color::from_hsva(360.0, 0.07, 0.36, 1.0), Color { r: 0x5C, g: 0x55, b: 0x55, a: 0xFF });
assert_eq!(Color::from_hsva(-360.0, 0.07, 0.36, 1.0), Color { r: 0x5C, g: 0x55, b: 0x55, a: 0xFF });
assert_eq!(Color::from_hsva(25.0, 0.5, 0.25, 1.0), Color { r: 0x40, g: 0x2D, b: 0x20, a: 0xFF });
assert_eq!(Color::from_hsva(25.0 + 360.0, 0.5, 0.25, 1.0), Color { r: 0x40, g: 0x2D, b: 0x20, a: 0xFF });
assert_eq!(Color::from_hsva(25.0 - 360.0, 0.5, 0.25, 1.0), Color { r: 0x40, g: 0x2D, b: 0x20, a: 0xFF });
assert_eq!(Color::from_hsva(49.0, 0.75, 0.12, 1.0), Color { r: 0x1F, g: 0x1A, b: 0x08, a: 0xFF });
assert_eq!(Color::from_hsva(65.0, 0.12, 0.87, 1.0), Color { r: 0xDC, g: 0xDE, b: 0xC3, a: 0xFF });
assert_eq!(Color::from_hsva(65.0, 0.12, 1.0, 1.0), Color { r: 252, g: 255, b: 224, a: 0xFF });
assert_eq!(Color::from_hsva(65.0, 0.12, 1.4, 1.0), Color { r: 252, g: 255, b: 224, a: 0xFF });
assert_eq!(Color::from_hsva(90.0, 0.22, 0.55, 1.0), Color { r: 0x7D, g: 0x8C, b: 0x6D, a: 0xFF });
assert_eq!(Color::from_hsva(90.0 + 360.0, 0.22, 0.55, 1.0), Color { r: 0x7D, g: 0x8C, b: 0x6D, a: 0xFF });
assert_eq!(Color::from_hsva(90.0, 0.22, 0.55, 1.0), Color { r: 0x7D, g: 0x8C, b: 0x6D, a: 0xFF });
assert_eq!(Color::from_hsva(120.0, 0.26, 0.91, 1.0), Color { r: 0xAC, g: 0xE8, b: 0xAC, a: 0xFF });
assert_eq!(Color::from_hsva(175.0, 0.97, 0.04, 1.0), Color { r: 0x00, g: 0x0A, b: 0x09, a: 0xFF });
assert_eq!(Color::from_hsva(175.0 + 360.0, 0.97, 0.04, 1.0), Color { r: 0x00, g: 0x0A, b: 0x09, a: 0xFF });
assert_eq!(Color::from_hsva(175.0 - 360.0, 0.97, 0.04, 1.0), Color { r: 0x00, g: 0x0A, b: 0x09, a: 0xFF });
assert_eq!(Color::from_hsva(180.0, 1.0, 1.0, 1.0), Color { r: 0x00, g: 0xFF, b: 0xFF, a: 0xFF });
assert_eq!(Color::from_hsva(211.0, 0.11, 0.59, 1.0), Color { r: 0x86, g: 0x8E, b: 0x96, a: 0xFF });
assert_eq!(Color::from_hsva(299.0, 0.58, 0.91, 1.0), Color { r: 0xE6, g: 0x61, b: 0xE8, a: 0xFF });
assert_eq!(Color::from_hsva(299.0 + 360.0, 0.58, 0.91, 1.0), Color { r: 0xE6, g: 0x61, b: 0xE8, a: 0xFF });
assert_eq!(Color::from_hsva(299.0 - 360.0, 0.58, 0.91, 1.0), Color { r: 0xE6, g: 0x61, b: 0xE8, a: 0xFF });
assert_eq!(Color::from_hsva(310.0, 0.33, 0.77, 1.0), Color { r: 0xC4, g: 0x84, b: 0xBA, a: 0xFF });
assert_eq!(Color::from_hsva(310.0, 0.33, 0.77, 1.5), Color { r: 0xC4, g: 0x84, b: 0xBA, a: 0xFF });
assert_eq!(Color::from_hsva(310.0, 0.33, 0.77, 0.5), Color { r: 0xC4, g: 0x84, b: 0xBA, a: 0x80 });
assert_eq!(Color::from_hsva(310.0, 0.33, 0.77, 0.0), Color { r: 0xC4, g: 0x84, b: 0xBA, a: 0x00 });
assert_eq!(Color::from_hsva(310.0, 0.33, 0.77, -0.2), Color { r: 0xC4, g: 0x84, b: 0xBA, a: 0x00 });
}
#[test]
fn test_color_rgb_to_hsv() {
macro_rules! assert_close {
($c1:expr, $c2:expr) => {{
let (h1, s1, v1, a1) = $c1;
let (h2, s2, v2, a2) = $c2;
let thresh = 1.0 / 255.0;
assert!((h1 - h2).abs() < thresh, "{h1} vs {h2}");
assert!((s1 - s2).abs() < thresh, "{s1} vs {s2}");
assert!((v1 - v2).abs() < thresh, "{v1} vs {v2}");
assert!((a1 - a2).abs() < thresh, "{a1} vs {a2}");
}}
}
assert_close!(Color { r: 0x00, g: 0x00, b: 0x00, a: 0xFF }.to_hsva(), (0.0, 0.0, 0.0, 1.0));
assert_close!(Color { r: 0x5C, g: 0x55, b: 0x55, a: 0xFF }.to_hsva(), (0.0, 0.076, 0.361, 1.0));
assert_close!(Color { r: 92, g: 0, b: 0, a: 0xFF }.to_hsva(), (0.0, 1.0, 0.361, 1.0));
assert_close!(Color { r: 92, g: 92, b: 92, a: 0xFF }.to_hsva(), (0.0, 0.0, 0.361, 1.0));
assert_close!(Color { r: 0x40, g: 0x2D, b: 0x20, a: 0xFF }.to_hsva(), (24.375, 0.5, 0.251, 1.0));
assert_close!(Color { r: 0x1F, g: 0x1A, b: 0x08, a: 0xFF }.to_hsva(), (46.956, 0.742, 0.122, 1.0));
assert_close!(Color { r: 0xDC, g: 0xDE, b: 0xC3, a: 0xFF }.to_hsva(), (64.444, 0.122, 0.871, 1.0));
assert_close!(Color { r: 252, g: 255, b: 224, a: 0xFF }.to_hsva(), (65.806, 0.122, 1.0, 1.0));
assert_close!(Color { r: 0x7D, g: 0x8C, b: 0x6D, a: 0xFF }.to_hsva(), (89.032, 0.221, 0.549, 1.0));
assert_close!(Color { r: 0xAC, g: 0xE8, b: 0xAC, a: 0xFF }.to_hsva(), (120.0, 0.259, 0.91, 1.0));
assert_close!(Color { r: 0x00, g: 0x0A, b: 0x09, a: 0xFF }.to_hsva(), (174.0, 1.0, 0.039, 1.0));
assert_close!(Color { r: 0x00, g: 0xFF, b: 0xFF, a: 0xFF }.to_hsva(), (180.0, 1.0, 1.0, 1.0));
assert_close!(Color { r: 0x86, g: 0x8E, b: 0x96, a: 0xFF }.to_hsva(), (210.0, 0.107, 0.588, 1.0));
assert_close!(Color { r: 0xE6, g: 0x61, b: 0xE8, a: 0xFF }.to_hsva(), (299.111, 0.582, 0.91, 1.0));
assert_close!(Color { r: 0xC4, g: 0x84, b: 0xBA, a: 0xFF }.to_hsva(), (309.375, 0.327, 0.769, 1.0));
assert_close!(Color { r: 0xC4, g: 0x84, b: 0xBA, a: 0x80 }.to_hsva(), (309.375, 0.327, 0.769, 0.5));
assert_close!(Color { r: 0xC4, g: 0x84, b: 0xBA, a: 0x00 }.to_hsva(), (309.375, 0.327, 0.769, 0.0));
assert_close!(Color { r: 255, g: 67, b: 14, a: 255 }.to_hsva(), (13.195, 0.945, 1.0, 1.0));
assert_close!(Color { r: 255, g: 14, b: 67, a: 255 }.to_hsva(), (346.805, 0.945, 1.0, 1.0));
assert_close!(Color { r: 87, g: 255, b: 33, a: 255 }.to_hsva(), (105.4054, 0.871, 1.0, 1.0));
assert_close!(Color { r: 33, g: 255, b: 87, a: 255 }.to_hsva(), (134.594, 0.871, 1.0, 1.0));
assert_close!(Color { r: 12, g: 54, b: 255, a: 255 }.to_hsva(), (229.629, 0.953, 1.0, 1.0));
assert_close!(Color { r: 54, g: 12, b: 255, a: 255 }.to_hsva(), (250.37, 0.953, 1.0, 1.0));
macro_rules! assert_round_trip {
($v:expr) => {{
let rgba = $v;
let hsva = rgba.to_hsva();
let back = Color::from_hsva(hsva.0, hsva.1, hsva.2, hsva.3);
assert_eq!(rgba, back);
}}
}
assert_round_trip!(Color { r: 12, g: 65, b: 23, a: 87 });
assert_round_trip!(Color { r: 128, g: 0, b: 23, a: 186 });
assert_round_trip!(Color { r: 0, g: 0, b: 0, a: 0 });
assert_round_trip!(Color { r: 0, g: 0, b: 0, a: 255 });
assert_round_trip!(Color { r: 255, g: 0, b: 0, a: 255 });
assert_round_trip!(Color { r: 0, g: 255, b: 0, a: 255 });
assert_round_trip!(Color { r: 0, g: 0, b: 255, a: 255 });
assert_round_trip!(Color { r: 255, g: 0, b: 0, a: 0 });
assert_round_trip!(Color { r: 0, g: 255, b: 0, a: 0 });
assert_round_trip!(Color { r: 0, g: 0, b: 255, a: 0 });
assert_round_trip!(Color { r: 57, g: 0, b: 0, a: 0 });
assert_round_trip!(Color { r: 0, g: 198, b: 0, a: 0 });
assert_round_trip!(Color { r: 0, g: 0, b: 10, a: 0 });
}
#[derive(Debug, Clone, Copy, FromPrimitive)]
#[repr(u8)]
pub enum Property {
XPos, YPos, Heading,
Visible, Size,
PenDown, PenSize, PenColor,
PenColorH, PenColorS, PenColorV, PenColorT,
Tempo, Volume, Balance,
ColorH, ColorS, ColorV, ColorT,
Fisheye, Whirl, Pixelate, Mosaic, Negative,
}
impl Property {
pub(crate) fn from_effect(kind: &ast::EffectKind) -> Self {
match kind {
ast::EffectKind::Color => Property::ColorH,
ast::EffectKind::Saturation => Property::ColorS,
ast::EffectKind::Brightness => Property::ColorV,
ast::EffectKind::Ghost => Property::ColorT,
ast::EffectKind::Fisheye => Property::Fisheye,
ast::EffectKind::Whirl => Property::Whirl,
ast::EffectKind::Pixelate => Property::Pixelate,
ast::EffectKind::Mosaic => Property::Mosaic,
ast::EffectKind::Negative => Property::Negative,
}
}
pub(crate) fn from_pen_attr(attr: &ast::PenAttribute) -> Self {
match attr {
ast::PenAttribute::Size => Property::PenSize,
ast::PenAttribute::Hue => Property::PenColorH,
ast::PenAttribute::Saturation => Property::PenColorS,
ast::PenAttribute::Brightness => Property::PenColorV,
ast::PenAttribute::Transparency => Property::PenColorT,
}
}
}
#[derive(Clone, Copy)]
pub struct Effects {
pub color_h: Number,
pub color_s: Number,
pub color_v: Number,
pub color_t: Number,
pub fisheye: Number,
pub whirl: Number,
pub pixelate: Number,
pub mosaic: Number,
pub negative: Number,
}
impl Default for Effects {
fn default() -> Self {
let zero = Number::new(0.0).unwrap();
Self {
color_h: zero,
color_s: zero,
color_v: zero,
color_t: zero,
fisheye: zero,
whirl: zero,
pixelate: zero,
mosaic: zero,
negative: zero,
}
}
}
#[derive(Clone, Copy)]
pub struct Properties {
pub pos: (Number, Number),
pub heading: Number,
pub visible: bool,
pub size: Number,
pub pen_down: bool,
pub pen_size: Number,
pub pen_color_h: Number,
pub pen_color_s: Number,
pub pen_color_v: Number,
pub pen_color_t: Number,
pub tempo: Number,
pub volume: Number,
pub balance: Number,
pub effects: Effects,
}
impl Default for Properties {
fn default() -> Self {
let zero = Number::new(0.0).unwrap();
let hundred = Number::new(100.0).unwrap();
Self {
pos: (zero, zero),
heading: zero,
visible: true,
size: hundred,
pen_down: false,
pen_size: Number::new(1.0).unwrap(),
pen_color_h: zero,
pen_color_s: zero,
pen_color_v: zero,
pen_color_t: zero,
tempo: Number::new(60.0).unwrap(),
volume: hundred,
balance: zero,
effects: Default::default(),
}
}
}
impl Properties {
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)) {
match value {
Ok(x) => {
f(self, x);
key.complete(Ok(()));
}
Err(e) => key.complete(Err(format!("{e:?}"))),
}
}
pub fn perform_get_property<'gc, C: CustomTypes<S>, S: System<C>>(&self, key: S::RequestKey, prop: Property) -> RequestStatus<'gc, C, S> {
let value: Json = match prop {
Property::XPos => self.pos.0.get().into(),
Property::YPos => self.pos.1.get().into(),
Property::Heading => self.heading.get().into(),
Property::Visible => self.visible.into(),
Property::Size => self.size.get().into(),
Property::PenDown => self.pen_down.into(),
Property::PenSize => self.pen_size.get().into(),
Property::PenColor => {
let Color { a, r, g, b } = Color::from_hsva(
self.pen_color_h.get() as f32,
self.pen_color_s.get() as f32 / 100.0,
self.pen_color_v.get() as f32 / 100.0,
1.0 - self.pen_color_t.get() as f32 / 100.0
);
json!(u32::from_be_bytes([a, r, g, b]))
}
Property::PenColorH => self.pen_color_h.get().into(),
Property::PenColorS => self.pen_color_s.get().into(),
Property::PenColorV => self.pen_color_v.get().into(),
Property::PenColorT => self.pen_color_t.get().into(),
Property::Tempo => self.tempo.get().into(),
Property::Volume => self.volume.get().into(),
Property::Balance => self.balance.get().into(),
Property::ColorH => self.effects.color_h.get().into(),
Property::ColorS => self.effects.color_s.get().into(),
Property::ColorV => self.effects.color_v.get().into(),
Property::ColorT => self.effects.color_t.get().into(),
Property::Fisheye => self.effects.fisheye.get().into(),
Property::Whirl => self.effects.whirl.get().into(),
Property::Pixelate => self.effects.pixelate.get().into(),
Property::Mosaic => self.effects.mosaic.get().into(),
Property::Negative => self.effects.negative.get().into(),
};
key.complete(Ok(C::Intermediate::from_json(value)));
RequestStatus::Handled
}
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> {
match prop {
Property::XPos => self.with_value(key, value.to_number().map_err(Into::into), |props, value| props.pos.0 = value),
Property::YPos => self.with_value(key, value.to_number().map_err(Into::into), |props, value| props.pos.1 = value),
Property::Heading => self.with_value(key, value.to_number().map_err(Into::into).and_then(|x| Number::new(x.get().rem_euclid(360.0)).map_err(Into::into)), |props, value| props.heading = value),
Property::Visible => self.with_value(key, value.to_bool().map_err(Into::into), |props, value| props.visible = value),
Property::Size => self.with_value(key, value.to_number().map_err(Into::into).and_then(|x| Number::new(x.get().max(0.0)).map_err(Into::into)), |props, value| props.size = value),
Property::PenDown => self.with_value(key, value.to_bool().map_err(Into::into), |props, value| props.pen_down = value),
Property::PenSize => self.with_value(key, value.to_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),
Property::PenColor => self.with_value(key, value.to_number().map_err(Into::into), |props, value| {
let [a, r, g, b] = (value.get() as u32).to_be_bytes();
let (h, s, v, a) = Color { a, r, g, b }.to_hsva();
props.pen_color_h = Number::new(h as f64).unwrap();
props.pen_color_s = Number::new(s as f64 * 100.0).unwrap();
props.pen_color_v = Number::new(v as f64 * 100.0).unwrap();
props.pen_color_t = Number::new((1.0 - a as f64) * 100.0).unwrap();
}),
Property::PenColorH => self.with_value(key, value.to_number().map_err(Into::into).and_then(|x| Number::new(x.get().rem_euclid(360.0)).map_err(Into::into)), |props, value| props.pen_color_h = value),
Property::PenColorS => self.with_value(key, value.to_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),
Property::PenColorV => self.with_value(key, value.to_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),
Property::PenColorT => self.with_value(key, value.to_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),
Property::Tempo => self.with_value(key, value.to_number().map_err(Into::into), |props, value| props.tempo = value),
Property::Volume => self.with_value(key, value.to_number().map_err(Into::into), |props, value| props.volume = value),
Property::Balance => self.with_value(key, value.to_number().map_err(Into::into), |props, value| props.balance = value),
Property::ColorH => self.with_value(key, value.to_number().map_err(Into::into).and_then(|x| Number::new(x.get().rem_euclid(360.0)).map_err(Into::into)), |props, value| props.effects.color_h = value),
Property::ColorS => self.with_value(key, value.to_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),
Property::ColorV => self.with_value(key, value.to_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),
Property::ColorT => self.with_value(key, value.to_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),
Property::Fisheye => self.with_value(key, value.to_number().map_err(Into::into), |props, value| props.effects.fisheye = value),
Property::Whirl => self.with_value(key, value.to_number().map_err(Into::into), |props, value| props.effects.whirl = value),
Property::Pixelate => self.with_value(key, value.to_number().map_err(Into::into), |props, value| props.effects.pixelate = value),
Property::Mosaic => self.with_value(key, value.to_number().map_err(Into::into), |props, value| props.effects.mosaic = value),
Property::Negative => self.with_value(key, value.to_number().map_err(Into::into), |props, value| props.effects.negative = value),
}
CommandStatus::Handled
}
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> {
match prop {
Property::XPos => self.with_value(key, delta.to_number().map_err(Into::into).and_then(|x| self.pos.0.add(x).map_err(Into::into)), |props, value| props.pos.0 = value),
Property::YPos => self.with_value(key, delta.to_number().map_err(Into::into).and_then(|x| self.pos.1.add(x).map_err(Into::into)), |props, value| props.pos.1 = value),
Property::Heading => self.with_value(key, delta.to_number().map_err(Into::into).and_then(|x| Number::new((self.heading.get() + x.get()).rem_euclid(360.0)).map_err(Into::into)), |props, value| props.heading = value),
Property::Visible => self.with_value(key, delta.to_bool().map_err(Into::into), |props, value| props.visible ^= value),
Property::Size => self.with_value(key, delta.to_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),
Property::PenDown => self.with_value(key, delta.to_bool().map_err(Into::into), |props, value| props.pen_down ^= value),
Property::PenSize => self.with_value(key, delta.to_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),
Property::PenColor => key.complete(Err("attempt to apply relative change to a color".into())),
Property::PenColorH => self.with_value(key, delta.to_number().map_err(Into::into).and_then(|x| Number::new((self.pen_color_h.get() + x.get()).rem_euclid(360.0)).map_err(Into::into)), |props, value| props.pen_color_h = value),
Property::PenColorS => self.with_value(key, delta.to_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),
Property::PenColorV => self.with_value(key, delta.to_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),
Property::PenColorT => self.with_value(key, delta.to_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),
Property::Tempo => self.with_value(key, delta.to_number().map_err(Into::into).and_then(|x| self.tempo.add(x).map_err(Into::into)), |props, value| props.tempo = value),
Property::Volume => self.with_value(key, delta.to_number().map_err(Into::into).and_then(|x| self.volume.add(x).map_err(Into::into)), |props, value| props.volume = value),
Property::Balance => self.with_value(key, delta.to_number().map_err(Into::into).and_then(|x| self.balance.add(x).map_err(Into::into)), |props, value| props.balance = value),
Property::ColorH => self.with_value(key, delta.to_number().map_err(Into::into).and_then(|x| Number::new((self.effects.color_h.get() + x.get()).rem_euclid(360.0)).map_err(Into::into)), |props, value| props.effects.color_h = value),
Property::ColorS => self.with_value(key, delta.to_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),
Property::ColorV => self.with_value(key, delta.to_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),
Property::ColorT => self.with_value(key, delta.to_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),
Property::Fisheye => self.with_value(key, delta.to_number().map_err(Into::into).and_then(|x| self.effects.fisheye.add(x).map_err(Into::into)), |props, value| props.effects.fisheye = value),
Property::Whirl => self.with_value(key, delta.to_number().map_err(Into::into).and_then(|x| self.effects.whirl.add(x).map_err(Into::into)), |props, value| props.effects.whirl = value),
Property::Pixelate => self.with_value(key, delta.to_number().map_err(Into::into).and_then(|x| self.effects.pixelate.add(x).map_err(Into::into)), |props, value| props.effects.pixelate = value),
Property::Mosaic => self.with_value(key, delta.to_number().map_err(Into::into).and_then(|x| self.effects.mosaic.add(x).map_err(Into::into)), |props, value| props.effects.mosaic = value),
Property::Negative => self.with_value(key, delta.to_number().map_err(Into::into).and_then(|x| self.effects.negative.add(x).map_err(Into::into)), |props, value| props.effects.negative = value),
}
CommandStatus::Handled
}
pub fn perform_clear_effects<'gc, 'a, C: CustomTypes<S>, S: System<C>>(&mut self, key: S::CommandKey) -> CommandStatus<'gc, 'a, C, S> {
self.effects = Default::default();
key.complete(Ok(()));
CommandStatus::Handled
}
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> {
self.pos = (x, y);
key.complete(Ok(()));
CommandStatus::Handled
}
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> {
let (dx, dy) = (x.get() - self.pos.0.get(), y.get() - self.pos.1.get());
let heading = dx.atan2(dy).to_degrees().rem_euclid(360.0);
self.with_value::<C, S, _>(key, Number::new(heading).map_err(Into::into), |props, value| props.heading = value);
CommandStatus::Handled
}
pub fn perform_forward<'gc, 'a, C: CustomTypes<S>, S: System<C>>(&mut self, key: S::CommandKey, dist: Number) -> CommandStatus<'gc, 'a, C, S> {
let (sin, cos) = self.heading.get().to_radians().sin_cos();
let (x, y) = (self.pos.0.get() + sin * dist.get(), self.pos.1.get() + cos * dist.get());
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);
CommandStatus::Handled
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyCode {
Char(char),
Up,
Down,
Left,
Right,
Enter,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone)]
pub enum Event {
OnFlag,
OnClone,
LocalMessage { msg_type: Option<String> },
NetworkMessage { msg_type: String, fields: Vec<String> },
OnKey { key_filter: Option<KeyCode> },
Custom { name: String, fields: Vec<String> },
}
#[derive(Debug, Clone, Copy, FromPrimitive)]
#[repr(u8)]
pub enum PrintStyle {
Say, Think,
}
#[derive(Educe)]
#[educe(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
pub struct Identity<'gc, C: CustomTypes<S>, S: System<C>>(*const (), PhantomData<&'gc Value<'gc, C, S>>);
pub trait GetType {
type Output: Clone + Copy + PartialEq + Eq + fmt::Debug;
fn get_type(&self) -> Self::Output;
}
pub trait Key<T> {
fn complete(self, value: T);
}
#[derive(Educe, Collect)]
#[educe(Clone)]
#[collect(no_drop, bound = "")]
pub enum Value<'gc, C: CustomTypes<S>, S: System<C>> {
Bool(#[collect(require_static)] bool),
Number(#[collect(require_static)] Number),
String(#[collect(require_static)] Rc<String>),
Image(#[collect(require_static)] Rc<Vec<u8>>),
Audio(#[collect(require_static)] Rc<Vec<u8>>),
Native(#[collect(require_static)] Rc<C::NativeValue>),
List(Gc<'gc, RefLock<VecDeque<Value<'gc, C, S>>>>),
Closure(Gc<'gc, RefLock<Closure<'gc, C, S>>>),
Entity(Gc<'gc, RefLock<Entity<'gc, C, S>>>),
}
impl<'gc, C: CustomTypes<S>, S: System<C>> GetType for Value<'gc, C, S> {
type Output = Type<C, S>;
fn get_type(&self) -> Self::Output {
match self {
Value::Bool(_) => Type::Bool,
Value::Number(_) => Type::Number,
Value::String(_) => Type::String,
Value::Image(_) => Type::Image,
Value::Audio(_) => Type::Audio,
Value::List(_) => Type::List,
Value::Closure(_) => Type::Closure,
Value::Entity(_) => Type::Entity,
Value::Native(x) => Type::Native(x.get_type()),
}
}
}
#[derive(Clone, Copy)]
enum FormatStyle {
Debug, Display,
}
impl<C: CustomTypes<S>, S: System<C>> fmt::Debug for Value<'_, C, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
format_value(self, f, FormatStyle::Debug)
}
}
impl<C: CustomTypes<S>, S: System<C>> fmt::Display for Value<'_, C, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
format_value(self, f, FormatStyle::Display)
}
}
fn format_value<C: CustomTypes<S>, S: System<C>>(value: &Value<'_, C, S>, f: &mut fmt::Formatter<'_>, style: FormatStyle) -> fmt::Result {
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 {
match value {
Value::Bool(x) => write!(f, "{x}"),
Value::Number(x) => write!(f, "{x}"),
Value::String(x) => match style {
FormatStyle::Debug => write!(f, "{:?}", x.as_str()),
FormatStyle::Display => write!(f, "{}", x.as_str()),
}
Value::Closure(x) => write!(f, "{:?}", &*x.borrow()),
Value::Entity(x) => write!(f, "{:?}", &*x.borrow()),
Value::Native(x) => write!(f, "{:?}", &**x),
Value::Image(x) => write!(f, "[Image {:?}]", Rc::as_ptr(x)),
Value::Audio(x) => write!(f, "[Audio {:?}]", Rc::as_ptr(x)),
Value::List(x) => {
let identity = value.identity();
if !cache.insert(identity) { return write!(f, "[...]") }
let x = x.borrow();
write!(f, "[")?;
for (i, val) in x.iter().enumerate() {
print(val, f, style, cache)?;
if i != x.len() - 1 { write!(f, ",")? }
}
write!(f, "]")?;
debug_assert!(cache.contains(&identity));
cache.remove(&identity);
Ok(())
}
}
}
let mut cache = Default::default();
let res = print(value, f, style, &mut cache);
if res.is_ok() { debug_assert_eq!(cache.len(), 0); }
res
}
impl<'gc, C: CustomTypes<S>, S: System<C>> From<bool> for Value<'gc, C, S> { fn from(v: bool) -> Self { Value::Bool(v) } }
impl<'gc, C: CustomTypes<S>, S: System<C>> From<Number> for Value<'gc, C, S> { fn from(v: Number) -> Self { Value::Number(v) } }
impl<'gc, C: CustomTypes<S>, S: System<C>> From<Rc<String>> for Value<'gc, C, S> { fn from(v: Rc<String>) -> Self { Value::String(v) } }
impl<'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) } }
impl<'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) } }
impl<'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) } }
impl<'gc, C: CustomTypes<S>, S: System<C>> Value<'gc, C, S> {
pub fn from_json(mc: &Mutation<'gc>, value: Json) -> Result<Self, FromJsonError> {
Ok(match value {
Json::Null => return Err(FromJsonError::HadNull),
Json::Bool(x) => Value::Bool(x),
Json::Number(x) => Value::Number(x.as_f64().and_then(|x| Number::new(x).ok()).ok_or(FromJsonError::HadBadNumber)?),
Json::String(x) => Value::String(Rc::new(x)),
Json::Array(x) => Value::List(Gc::new(mc, RefLock::new(x.into_iter().map(|x| Value::from_json(mc, x)).collect::<Result<_,_>>()?))),
Json::Object(x) => Value::List(Gc::new(mc, RefLock::new(x.into_iter().map(|(k, v)| {
let mut entry = VecDeque::with_capacity(2);
entry.push_back(Value::String(Rc::new(k)));
entry.push_back(Value::from_json(mc, v)?);
Ok(Value::List(Gc::new(mc, RefLock::new(entry))))
}).collect::<Result<_,_>>()?))),
})
}
pub fn to_json(&self) -> Result<Json, ToJsonError<C, S>> {
fn simplify<'gc, C: CustomTypes<S>, S: System<C>>(value: &Value<'gc, C, S>, cache: &mut BTreeSet<Identity<'gc, C, S>>) -> Result<Json, ToJsonError<C, S>> {
Ok(match value {
Value::Bool(x) => Json::Bool(*x),
Value::Number(x) => Json::Number(JsonNumber::from_f64(x.get()).ok_or_else(|| ToJsonError::BadNumber(x.get()))?),
Value::String(x) => Json::String(x.as_str().to_owned()),
Value::Image(_) | Value::Audio(_) | Value::Closure(_) | Value::Entity(_) | Value::Native(_) => return Err(ToJsonError::ComplexType(value.get_type())),
Value::List(x) => {
let identity = value.identity();
if !cache.insert(identity) { return Err(ToJsonError::Cyclic) }
let res = Json::Array(x.borrow().iter().map(|x| simplify(x, cache)).collect::<Result<_,_>>()?);
debug_assert!(cache.contains(&identity));
cache.remove(&identity);
res
}
})
}
let mut cache = Default::default();
let res = simplify(self, &mut cache);
if res.is_ok() { debug_assert_eq!(cache.len(), 0); }
res
}
pub fn identity(&self) -> Identity<'gc, C, S> {
match self {
Value::Bool(x) => Identity(x as *const bool as *const (), PhantomData),
Value::Number(x) => Identity(x as *const Number as *const (), PhantomData),
Value::String(x) => Identity(Rc::as_ptr(x) as *const (), PhantomData),
Value::Image(x) => Identity(Rc::as_ptr(x) as *const (), PhantomData),
Value::Audio(x) => Identity(Rc::as_ptr(x) as *const (), PhantomData),
Value::List(x) => Identity(Gc::as_ptr(*x) as *const (), PhantomData),
Value::Closure(x) => Identity(Gc::as_ptr(*x) as *const (), PhantomData),
Value::Entity(x) => Identity(Gc::as_ptr(*x) as *const (), PhantomData),
Value::Native(x) => Identity(Rc::as_ptr(x) as *const (), PhantomData),
}
}
pub fn to_bool(&self) -> Result<bool, ConversionError<C, S>> {
Ok(match self {
Value::Bool(x) => *x,
x => return Err(ConversionError { got: x.get_type(), expected: Type::Bool }),
})
}
pub fn to_number(&self) -> Result<Number, ConversionError<C, S>> {
match self {
Value::Number(x) => Ok(*x),
Value::String(x) => {
let parsed = match x.get(..2) {
Some("0x" | "0X") => i64::from_str_radix(&x[2..], 16).ok().map(|x| x as f64),
Some("0o" | "0O") => i64::from_str_radix(&x[2..], 8).ok().map(|x| x as f64),
Some("0b" | "0B") => i64::from_str_radix(&x[2..], 2).ok().map(|x| x as f64),
_ => x.parse::<f64>().ok(),
};
parsed.and_then(|x| Number::new(x).ok()).ok_or(ConversionError { got: Type::String, expected: Type::Number })
}
x => Err(ConversionError { got: x.get_type(), expected: Type::Number }),
}
}
pub fn to_string(&self) -> Result<Cow<str>, ConversionError<C, S>> {
Ok(match self {
Value::String(x) => Cow::Borrowed(&**x),
Value::Number(x) => Cow::Owned(x.to_string()),
x => return Err(ConversionError { got: x.get_type(), expected: Type::String }),
})
}
pub fn as_image(&self) -> Result<&Rc<Vec<u8>>, ConversionError<C, S>> {
match self {
Value::Image(x) => Ok(x),
x => Err(ConversionError { got: x.get_type(), expected: Type::Image }),
}
}
pub fn as_list(&self) -> Result<Gc<'gc, RefLock<VecDeque<Value<'gc, C, S>>>>, ConversionError<C, S>> {
match self {
Value::List(x) => Ok(*x),
x => Err(ConversionError { got: x.get_type(), expected: Type::List }),
}
}
pub fn as_closure(&self) -> Result<Gc<'gc, RefLock<Closure<'gc, C, S>>>, ConversionError<C, S>> {
match self {
Value::Closure(x) => Ok(*x),
x => Err(ConversionError { got: x.get_type(), expected: Type::Closure }),
}
}
pub fn as_entity(&self) -> Result<Gc<'gc, RefLock<Entity<'gc, C, S>>>, ConversionError<C, S>> {
match self {
Value::Entity(x) => Ok(*x),
x => Err(ConversionError { got: x.get_type(), expected: Type::Entity }),
}
}
}
#[derive(Collect)]
#[collect(no_drop, bound = "")]
pub struct Closure<'gc, C: CustomTypes<S>, S: System<C>> {
#[collect(require_static)] pub pos: usize,
#[collect(require_static)] pub params: Vec<String>,
pub captures: SymbolTable<'gc, C, S>,
}
impl<C: CustomTypes<S>, S: System<C>> fmt::Debug for Closure<'_, C, S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Closure {:#08x}", self.pos)
}
}
pub enum EntityKind<'gc, 'a, C: CustomTypes<S>, S: System<C>> {
Stage { props: Properties },
Sprite { props: Properties },
Clone { parent: &'a Entity<'gc, C, S> },
}
#[derive(Collect)]
#[collect(no_drop, bound = "")]
pub struct Entity<'gc, C: CustomTypes<S>, S: System<C>> {
#[collect(require_static)] pub name: Rc<String>,
#[collect(require_static)] pub costume_list: Rc<Vec<(String, Rc<Vec<u8>>)>>,
#[collect(require_static)] pub costume: Option<Rc<Vec<u8>>>,
#[collect(require_static)] pub state: C::EntityState,
#[collect(require_static)] pub alive: bool,
pub root: Option<Gc<'gc, RefLock<Entity<'gc, C, S>>>>,
pub fields: SymbolTable<'gc, C, S>,
}
impl<C: CustomTypes<S>, S: System<C>> fmt::Debug for Entity<'_, C, S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Entity {:?}", self.name)
}
}
#[derive(Collect)]
#[collect(no_drop)]
pub struct Watcher<'gc, C: CustomTypes<S>, S: System<C>> {
pub entity: GcWeak<'gc, RefLock<Entity<'gc, C, S>>>,
pub name: String,
pub value: GcWeak<'gc, RefLock<Value<'gc, C, S>>>,
}
impl<'gc, C: CustomTypes<S>, S: System<C>> fmt::Debug for Watcher<'gc, C, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Watcher").field("name", &self.name).finish_non_exhaustive()
}
}
#[derive(Collect)]
#[collect(no_drop)]
pub enum Shared<'gc, T: 'gc + Collect> {
Unique(T),
Aliased(Gc<'gc, RefLock<T>>),
}
impl<'gc, T: 'gc + Collect> Shared<'gc, T> {
pub fn set(&mut self, mc: &Mutation<'gc>, value: T) {
match self {
Shared::Unique(x) => *x = value,
Shared::Aliased(x) => *x.borrow_mut(mc) = value,
}
}
pub fn get(&self) -> SharedRef<T> {
match self {
Shared::Unique(x) => SharedRef::Unique(x),
Shared::Aliased(x) => SharedRef::Aliased(x.borrow()),
}
}
pub fn alias_inner(&mut self, mc: &Mutation<'gc>) -> Gc<'gc, RefLock<T>> {
replace_with::replace_with(self, || unreachable!(), |myself| match myself {
Shared::Unique(x) => Shared::Aliased(Gc::new(mc, RefLock::new(x))),
Shared::Aliased(_) => myself,
});
match self {
Shared::Unique(_) => unreachable!(),
Shared::Aliased(x) => *x,
}
}
pub fn alias(&mut self, mc: &Mutation<'gc>) -> Self {
Shared::Aliased(self.alias_inner(mc))
}
}
impl<'gc, T: Collect> From<T> for Shared<'gc, T> { fn from(value: T) -> Self { Shared::Unique(value) } }
pub enum SharedRef<'a, T> {
Unique(&'a T),
Aliased(Ref<'a, T>)
}
impl<'a, T> Deref for SharedRef<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
SharedRef::Unique(x) => x,
SharedRef::Aliased(x) => x,
}
}
}
#[derive(Collect, Educe)]
#[collect(no_drop, bound = "")]
#[educe(Default)]
pub struct SymbolTable<'gc, C: CustomTypes<S>, S: System<C>>(BTreeMap<String, Shared<'gc, Value<'gc, C, S>>>);
impl<'gc, C: CustomTypes<S>, S: System<C>> Clone for SymbolTable<'gc, C, S> {
fn clone(&self) -> Self {
let mut res = SymbolTable::default();
for (k, v) in self.iter() {
res.define_or_redefine(k, Shared::Unique(v.get().clone()));
}
res
}
}
impl<'gc, C: CustomTypes<S>, S: System<C>> SymbolTable<'gc, C, S> {
pub fn define_or_redefine(&mut self, var: &str, value: Shared<'gc, Value<'gc, C, S>>) {
self.0.insert(var.to_owned(), value);
}
pub fn define_if_undefined<F: FnOnce() -> Shared<'gc, Value<'gc, C, S>>>(&mut self, var: &str, f: F) {
if !self.0.contains_key(var) {
self.0.insert(var.to_owned(), f());
}
}
pub fn lookup(&self, var: &str) -> Option<&Shared<'gc, Value<'gc, C, S>>> {
self.0.get(var)
}
pub fn lookup_mut(&mut self, var: &str) -> Option<&mut Shared<'gc, Value<'gc, C, S>>> {
self.0.get_mut(var)
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn iter(&self) -> symbol_table::Iter<'gc, '_, C, S> {
symbol_table::Iter(self.0.iter())
}
pub fn iter_mut(&mut self) -> symbol_table::IterMut<'gc, '_, C, S> {
symbol_table::IterMut(self.0.iter_mut())
}
}
impl<'gc, C: CustomTypes<S>, S: System<C>> IntoIterator for SymbolTable<'gc, C, S> {
type Item = (String, Shared<'gc, Value<'gc, C, S>>);
type IntoIter = symbol_table::IntoIter<'gc, C, S>;
fn into_iter(self) -> Self::IntoIter { symbol_table::IntoIter(self.0.into_iter()) }
}
impl<'gc, 'a, C: CustomTypes<S>, S: System<C>> IntoIterator for &'a SymbolTable<'gc, C, S> {
type Item = <symbol_table::Iter<'gc, 'a, C, S> as Iterator>::Item;
type IntoIter = symbol_table::Iter<'gc, 'a, C, S>;
fn into_iter(self) -> Self::IntoIter { self.iter() }
}
impl<'gc, 'a, C: CustomTypes<S>, S: System<C>> IntoIterator for &'a mut SymbolTable<'gc, C, S> {
type Item = <symbol_table::IterMut<'gc, 'a, C, S> as Iterator>::Item;
type IntoIter = symbol_table::IterMut<'gc, 'a, C, S>;
fn into_iter(self) -> Self::IntoIter { self.iter_mut() }
}
pub mod symbol_table {
use super::*;
pub struct IntoIter<'gc, C: CustomTypes<S>, S: System<C>>(pub(crate) alloc::collections::btree_map::IntoIter<String, Shared<'gc, Value<'gc, C, S>>>);
pub struct Iter<'gc, 'a, C: CustomTypes<S>, S: System<C>>(pub(crate) alloc::collections::btree_map::Iter<'a, String, Shared<'gc, Value<'gc, C, S>>>);
pub struct IterMut<'gc, 'a, C: CustomTypes<S>, S: System<C>>(pub(crate) alloc::collections::btree_map::IterMut<'a, String, Shared<'gc, Value<'gc, C, S>>>);
impl<'gc, C: CustomTypes<S>, S: System<C>> Iterator for IntoIter<'gc, C, S> { type Item = (String, Shared<'gc, Value<'gc, C, S>>); fn next(&mut self) -> Option<Self::Item> { self.0.next() } }
impl<'gc, 'a, C: CustomTypes<S>, S: System<C>> Iterator for Iter<'gc, 'a, C, S> { type Item = (&'a String, &'a Shared<'gc, Value<'gc, C, S>>); fn next(&mut self) -> Option<Self::Item> { self.0.next() } }
impl<'gc, 'a, C: CustomTypes<S>, S: System<C>> Iterator for IterMut<'gc, 'a, C, S> { type Item = (&'a String, &'a mut Shared<'gc, Value<'gc, C, S>>); fn next(&mut self) -> Option<Self::Item> { self.0.next() } }
}
pub(crate) struct LookupGroup<'gc, 'a, 'b, C: CustomTypes<S>, S: System<C>>(&'a mut [&'b mut SymbolTable<'gc, C, S>]);
impl<'gc, 'a, 'b, C: CustomTypes<S>, S: System<C>> LookupGroup<'gc, 'a, 'b, C, S> {
pub fn new(tables: &'a mut [&'b mut SymbolTable<'gc, C, S>]) -> Self {
debug_assert!(!tables.is_empty());
Self(tables)
}
pub fn lookup(&self, var: &str) -> Option<&Shared<'gc, Value<'gc, C, S>>> {
for src in self.0.iter().rev() {
if let Some(val) = src.lookup(var) {
return Some(val);
}
}
None
}
pub fn lookup_mut(&mut self, var: &str) -> Option<&mut Shared<'gc, Value<'gc, C, S>>> {
for src in self.0.iter_mut().rev() {
if let Some(val) = src.lookup_mut(var) {
return Some(val);
}
}
None
}
pub fn locals_mut(&mut self) -> &mut SymbolTable<'gc, C, S> {
self.0.last_mut().unwrap()
}
}
#[derive(Clone, Copy)]
pub enum ErrorScheme {
Soft,
Hard,
}
#[derive(Clone, Copy)]
pub struct Settings {
pub max_call_depth: usize,
pub rpc_error_scheme: ErrorScheme,
pub syscall_error_scheme: ErrorScheme,
}
impl Default for Settings {
fn default() -> Self {
Self {
max_call_depth: 1024,
rpc_error_scheme: ErrorScheme::Hard,
syscall_error_scheme: ErrorScheme::Hard,
}
}
}
#[derive(Collect)]
#[collect(no_drop, bound = "")]
pub struct GlobalContext<'gc, C: CustomTypes<S>, S: System<C>> {
#[collect(require_static)] pub bytecode: Rc<ByteCode>,
#[collect(require_static)] pub settings: Settings,
#[collect(require_static)] pub system: Rc<S>,
#[collect(require_static)] pub timer_start: u64,
#[collect(require_static)] pub proj_name: String,
pub globals: SymbolTable<'gc, C, S>,
pub entities: BTreeMap<String, Gc<'gc, RefLock<Entity<'gc, C, S>>>>,
}
impl<'gc, C: CustomTypes<S>, S: System<C>> GlobalContext<'gc, C, S> {
pub fn from_init(mc: &Mutation<'gc>, init_info: &InitInfo, bytecode: Rc<ByteCode>, settings: Settings, system: Rc<S>) -> Self {
let allocated_refs = init_info.ref_values.iter().map(|ref_value| match ref_value {
RefValue::String(value) => Value::String(Rc::new(value.clone())),
RefValue::Image(content) => Value::Image(Rc::new(content.clone())),
RefValue::List(_) => Value::List(Gc::new(mc, Default::default())),
}).collect::<Vec<_>>();
fn get_value<'gc, C: CustomTypes<S>, S: System<C>>(value: &InitValue, allocated_refs: &[Value<'gc, C, S>]) -> Value<'gc, C, S> {
match value {
InitValue::Bool(x) => Value::Bool(*x),
InitValue::Number(x) => Value::Number(*x),
InitValue::Ref(x) => allocated_refs[*x].clone(),
}
}
for (allocated_ref, ref_value) in iter::zip(&allocated_refs, &init_info.ref_values) {
match ref_value {
RefValue::String(_) | RefValue::Image(_) => continue, RefValue::List(values) => {
let allocated_ref = match allocated_ref {
Value::List(x) => x,
_ => unreachable!(),
};
let mut allocated_ref = allocated_ref.borrow_mut(mc);
for value in values {
allocated_ref.push_back(get_value(value, &allocated_refs));
}
}
}
}
let mut globals = SymbolTable::default();
for (global, value) in init_info.globals.iter() {
globals.define_or_redefine(global, Shared::Unique(get_value(value, &allocated_refs)));
}
let mut entities = BTreeMap::new();
for (i, entity_info) in init_info.entities.iter().enumerate() {
let mut fields = SymbolTable::default();
for (field, value) in entity_info.fields.iter() {
fields.define_or_redefine(field, Shared::Unique(get_value(value, &allocated_refs)));
}
let costume_list = {
let mut res = Vec::with_capacity(entity_info.costumes.len());
for (name, value) in entity_info.costumes.iter() {
let image = match get_value(value, &allocated_refs) {
Value::Image(x) => x.clone(),
_ => unreachable!(),
};
res.push((name.clone(), image));
}
Rc::new(res)
};
let costume = entity_info.active_costume.and_then(|x| costume_list.get(x)).map(|x| x.1.clone());
let mut props = Properties::default();
props.visible = entity_info.visible;
props.size = entity_info.size;
props.pos = entity_info.pos;
props.heading = entity_info.heading;
let (r, g, b, a) = entity_info.color;
let (h, s, v, a) = Color { r, g, b, a }.to_hsva();
props.pen_color_h = Number::new(h as f64).unwrap();
props.pen_color_s = Number::new(s as f64 * 100.0).unwrap();
props.pen_color_v = Number::new(v as f64 * 100.0).unwrap();
props.pen_color_t = Number::new((1.0 - a as f64) * 100.0).unwrap();
let kind = if i == 0 { EntityKind::Stage { props } } else { EntityKind::Sprite { props } };
let name = Rc::new(entity_info.name.clone());
let state = kind.into();
entities.insert(entity_info.name.clone(), Gc::new(mc, RefLock::new(Entity { alive: true, root: None, name, fields, costume_list, costume, state })));
}
let proj_name = init_info.proj_name.clone();
let timer_start = system.time().to_arbitrary_ms::<C, S>().unwrap_or(0);
Self { proj_name, globals, entities, timer_start, system, settings, bytecode }
}
}
pub enum OutgoingMessage<C: CustomTypes<S>, S: System<C>> {
Normal {
msg_type: String,
values: Vec<(String, Json)>,
targets: Vec<String>,
},
Blocking {
msg_type: String,
values: Vec<(String, Json)>,
targets: Vec<String>,
reply_key: S::ExternReplyKey,
},
Reply {
value: Json,
reply_key: S::InternReplyKey,
},
}
pub struct IncomingMessage<C: CustomTypes<S>, S: System<C>> {
pub msg_type: String,
pub values: Vec<(String, Json)>,
pub reply_key: Option<S::InternReplyKey>,
}
#[derive(Debug, Default, Clone)]
pub struct Barrier(Rc<()>);
#[derive(Debug, Clone)]
pub struct BarrierCondition(Weak<()>);
impl Barrier {
pub fn new() -> Self {
Barrier(Rc::new(()))
}
pub fn get_condition(&self) -> BarrierCondition {
BarrierCondition(Rc::downgrade(&self.0))
}
}
impl BarrierCondition {
pub fn is_completed(&self) -> bool {
self.0.strong_count() == 0
}
}
pub enum MaybeAsync<T, K> {
Sync(T),
Async(K),
}
pub enum AsyncResult<T> {
Pending,
Completed(T),
Consumed,
}
impl<T> AsyncResult<T> {
pub fn new() -> Self {
Self::Pending
}
pub fn complete(&mut self, value: T) -> Result<(), T> {
match self {
AsyncResult::Pending => {
*self = AsyncResult::Completed(value);
Ok(())
}
AsyncResult::Completed(_) | AsyncResult::Consumed => Err(value),
}
}
pub fn poll(&mut self) -> Self {
match self {
AsyncResult::Pending => AsyncResult::Pending,
AsyncResult::Completed(_) | AsyncResult::Consumed => mem::replace(self, AsyncResult::Consumed),
}
}
}
#[derive(Debug)]
pub enum Feature {
ArbitraryTime,
RealTime,
Input,
Print,
Syscall { name: String },
Rpc { service: String, rpc: String },
GetProperty { prop: Property },
SetProperty { prop: Property },
ChangeProperty { prop: Property },
SetCostume,
ClearEffects,
ClearDrawings,
GotoXY,
GotoEntity,
PointTowardsXY,
PointTowardsEntity,
Forward,
UnknownBlock { name: String },
}
pub enum Request<'gc, C: CustomTypes<S>, S: System<C>> {
Input { prompt: Option<Value<'gc, C, S>> },
Syscall { name: String, args: Vec<Value<'gc, C, S>> },
Rpc { service: String, rpc: String, args: Vec<(String, Value<'gc, C, S>)> },
Property { prop: Property },
UnknownBlock { name: String, args: Vec<Value<'gc, C, S>> },
}
impl<'gc, C: CustomTypes<S>, S: System<C>> Request<'gc, C, S> {
pub fn feature(&self) -> Feature {
match self {
Request::Input { .. } => Feature::Input,
Request::Syscall { name, .. } => Feature::Syscall { name: name.clone() },
Request::Rpc { service, rpc, .. } => Feature::Rpc { service: service.clone(), rpc: rpc.clone() },
Request::Property { prop } => Feature::GetProperty { prop: *prop },
Request::UnknownBlock { name, .. } => Feature::UnknownBlock { name: name.clone() },
}
}
}
pub enum Command<'gc, 'a, C: CustomTypes<S>, S: System<C>> {
Print { style: PrintStyle, value: Option<Value<'gc, C, S>> },
SetProperty { prop: Property, value: Value<'gc, C, S> },
ChangeProperty { prop: Property, delta: Value<'gc, C, S> },
ClearEffects,
ClearDrawings,
SetCostume { costume: Option<Rc<Vec<u8>>> },
GotoXY { x: Number, y: Number },
GotoEntity { target: &'a Entity<'gc, C, S> },
PointTowardsXY { x: Number, y: Number },
PointTowardsEntity { target: &'a Entity<'gc, C, S> },
Forward { distance: Number },
}
impl<'gc, C: CustomTypes<S>, S: System<C>> Command<'gc, '_, C, S> {
pub fn feature(&self) -> Feature {
match self {
Command::Print { .. } => Feature::Print,
Command::SetProperty { prop, .. } => Feature::SetProperty { prop: *prop },
Command::ChangeProperty { prop, .. } => Feature::ChangeProperty { prop: *prop },
Command::SetCostume { .. } => Feature::SetCostume,
Command::ClearEffects { .. } => Feature::ClearEffects,
Command::ClearDrawings => Feature::ClearDrawings,
Command::GotoXY { .. } => Feature::GotoXY,
Command::GotoEntity { .. } => Feature::GotoEntity,
Command::PointTowardsXY { .. } => Feature::PointTowardsXY,
Command::PointTowardsEntity { .. } => Feature::PointTowardsEntity,
Command::Forward { .. } => Feature::Forward,
}
}
}
pub enum RequestStatus<'gc, C: CustomTypes<S>, S: System<C>> {
Handled,
UseDefault { key: S::RequestKey, request: Request<'gc, C, S> },
}
pub enum CommandStatus<'gc, 'a, C: CustomTypes<S>, S: System<C>> {
Handled,
UseDefault { key: S::CommandKey, command: Command<'gc, 'a, C, S> },
}
#[derive(Educe)]
#[educe(Clone)]
pub struct Config<C: CustomTypes<S>, S: System<C>> {
pub request: Option<Rc<dyn for<'gc> Fn(&S, &Mutation<'gc>, S::RequestKey, Request<'gc, C, S>, &mut Entity<'gc, C, S>) -> RequestStatus<'gc, C, S>>>,
pub command: Option<Rc<dyn for<'gc, 'a> Fn(&S, &Mutation<'gc>, S::CommandKey, Command<'gc, 'a, C, S>, &mut Entity<'gc, C, S>) -> CommandStatus<'gc, 'a, C, S>>>,
}
impl<C: CustomTypes<S>, S: System<C>> Default for Config<C, S> {
fn default() -> Self {
Self {
request: None,
command: Some(Rc::new(|_, _, key, command, entity| match command {
Command::SetCostume { costume } => {
entity.costume = costume;
key.complete(Ok(()));
CommandStatus::Handled
}
_ => CommandStatus::UseDefault { key, command },
})),
}
}
}
impl<C: CustomTypes<S>, S: System<C>> Config<C, S> {
pub fn fallback(&self, other: &Self) -> Self {
Self {
request: match (self.request.clone(), other.request.clone()) {
(Some(a), Some(b)) => Some(Rc::new(move |system, mc, key, request, entity| {
match a(system, mc, key, request, entity) {
RequestStatus::Handled => RequestStatus::Handled,
RequestStatus::UseDefault { key, request } => b(system, mc, key, request, entity),
}
})),
(Some(a), None) | (None, Some(a)) => Some(a),
(None, None) => None,
},
command: match (self.command.clone(), other.command.clone()) {
(Some(a), Some(b)) => Some(Rc::new(move |system, mc, key, command, entity| {
match a(system, mc, key, command, entity) {
CommandStatus::Handled => CommandStatus::Handled,
CommandStatus::UseDefault { key, command } => b(system, mc, key, command, entity),
}
})),
(Some(a), None) | (None, Some(a)) => Some(a),
(None, None) => None,
},
}
}
}
pub trait IntermediateType {
fn from_json(json: Json) -> Self;
fn from_image(img: Vec<u8>) -> Self;
fn from_audio(audio: Vec<u8>) -> Self;
}
pub trait CustomTypes<S: System<Self>>: 'static + Sized {
type NativeValue: 'static + GetType + fmt::Debug;
type Intermediate: 'static + Send + IntermediateType;
type EntityState: 'static + for<'gc, 'a> From<EntityKind<'gc, 'a, Self, S>>;
fn from_intermediate<'gc>(mc: &Mutation<'gc>, value: Self::Intermediate) -> Result<Value<'gc, Self, S>, ErrorCause<Self, S>>;
}
pub enum SysTime {
Timeless,
Arbitrary { ms: u64 },
Real { local: OffsetDateTime },
}
impl SysTime {
pub fn to_arbitrary_ms<C: CustomTypes<S>, S: System<C>>(&self) -> Result<u64, ErrorCause<C, S>> {
match self {
Self::Timeless => Err(ErrorCause::NotSupported { feature: Feature::ArbitraryTime }),
Self::Arbitrary { ms } => Ok(*ms),
Self::Real { local } => Ok((local.unix_timestamp_nanos() / 1000000) as u64),
}
}
pub fn to_real_local<C: CustomTypes<S>, S: System<C>>(&self) -> Result<time::OffsetDateTime, ErrorCause<C, S>> {
match self {
Self::Timeless | Self::Arbitrary { .. } => Err(ErrorCause::NotSupported { feature: Feature::RealTime }),
Self::Real { local } => Ok(*local),
}
}
}
pub trait System<C: CustomTypes<Self>>: 'static + Sized {
type RequestKey: 'static + Key<Result<C::Intermediate, String>>;
type CommandKey: 'static + Key<Result<(), String>>;
type ExternReplyKey: 'static;
type InternReplyKey: 'static + Clone;
fn rand<T: SampleUniform, R: SampleRange<T>>(&self, range: R) -> T;
fn time(&self) -> SysTime;
fn perform_request<'gc>(&self, mc: &Mutation<'gc>, request: Request<'gc, C, Self>, entity: &mut Entity<'gc, C, Self>) -> Result<MaybeAsync<Result<Value<'gc, C, Self>, String>, Self::RequestKey>, ErrorCause<C, Self>>;
fn poll_request<'gc>(&self, mc: &Mutation<'gc>, key: &Self::RequestKey, entity: &mut Entity<'gc, C, Self>) -> Result<AsyncResult<Result<Value<'gc, C, Self>, String>>, ErrorCause<C, Self>>;
fn perform_command<'gc>(&self, mc: &Mutation<'gc>, command: Command<'gc, '_, C, Self>, entity: &mut Entity<'gc, C, Self>) -> Result<MaybeAsync<Result<(), String>, Self::CommandKey>, ErrorCause<C, Self>>;
fn poll_command<'gc>(&self, mc: &Mutation<'gc>, key: &Self::CommandKey, entity: &mut Entity<'gc, C, Self>) -> Result<AsyncResult<Result<(), String>>, ErrorCause<C, Self>>;
fn send_message(&self, msg_type: String, values: Vec<(String, Json)>, targets: Vec<String>, expect_reply: bool) -> Result<Option<Self::ExternReplyKey>, ErrorCause<C, Self>>;
fn poll_reply(&self, key: &Self::ExternReplyKey) -> AsyncResult<Option<Json>>;
fn send_reply(&self, key: Self::InternReplyKey, value: Json) -> Result<(), ErrorCause<C, Self>>;
fn receive_message(&self) -> Option<IncomingMessage<C, Self>>;
}