diff --git a/Cargo.toml b/Cargo.toml index ee638f8..fd3c9cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ rustls-tls-webpki-roots = [ # core deps serde_json = { version = "1.0", default-features = false, features = ["alloc"] } gc-arena = { version = "=0.3.0", default-features = false } -netsblox-ast = { version = "=0.4.1", default-features = false } +netsblox-ast = { version = "=0.4.2", default-features = false } # netsblox-ast = { path = "../netsblox-ast", default-features = false } num-traits = { version = "0.2.17", default-features = false } num-derive = { version = "0.3.3", default-features = false } diff --git a/src/bytecode.rs b/src/bytecode.rs index e479e32..b49615b 100644 --- a/src/bytecode.rs +++ b/src/bytecode.rs @@ -1070,6 +1070,7 @@ pub(crate) enum InitValue { pub(crate) enum RefValue { List(Vec), Image(Vec, Option<(Number, Number)>), + Audio(Vec), String(String), } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -1077,6 +1078,7 @@ pub(crate) struct EntityInitInfo { pub(crate) name: String, pub(crate) fields: Vec<(String, InitValue)>, pub(crate) costumes: Vec<(String, InitValue)>, + pub(crate) sounds: Vec<(String, InitValue)>, pub(crate) scripts: Vec<(Event, usize)>, pub(crate) visible: bool, @@ -1284,6 +1286,7 @@ impl<'a: 'b, 'b> ByteCodeBuilder<'a, 'b> { self.ins.push(Instruction::VariadicOp { op: VariadicOp::MakeList, len: VariadicLen::Fixed(values.len()) }.into()); } ast::Value::Image(_) => unreachable!(), // Snap! doesn't have image literals + ast::Value::Audio(_) => unreachable!(), // Snap! doesn't have audio literals ast::Value::Ref(_) => unreachable!(), // Snap! doesn't have reference literals } Ok(()) @@ -2262,11 +2265,12 @@ impl ByteCode { let mut refs = BTreeMap::new(); let mut string_refs = BTreeMap::new(); let mut image_refs = BTreeMap::new(); + let mut audio_refs = BTreeMap::new(); fn register_ref_values<'a>(value: &'a ast::Value, ref_values: &mut Vec<(Option, &'a ast::Value)>, refs: &mut BTreeMap) { match value { ast::Value::Bool(_) | ast::Value::Number(_) | ast::Value::Constant(_) => (), // non-ref types - ast::Value::Ref(_) | ast::Value::String(_) | ast::Value::Image(_) => (), // lazily link + ast::Value::Ref(_) | ast::Value::String(_) | ast::Value::Image(_) | ast::Value::Audio(_) => (), // lazily link ast::Value::List(values, ref_id) => { if let Some(ref_id) = ref_id { refs.entry(ref_id.0).or_insert_with(|| { @@ -2293,7 +2297,7 @@ impl ByteCode { } } - fn get_value<'a>(value: &'a ast::Value, ref_values: &mut Vec<(Option, &'a ast::Value)>, refs: &BTreeMap, string_refs: &mut BTreeMap<&'a str, usize>, image_refs: &mut BTreeMap<*const Vec, usize>) -> Result> { + fn get_value<'a>(value: &'a ast::Value, ref_values: &mut Vec<(Option, &'a ast::Value)>, refs: &BTreeMap, string_refs: &mut BTreeMap<&'a str, usize>, image_refs: &mut BTreeMap<*const (Vec, Option<(f64, f64)>), usize>, audio_refs: &mut BTreeMap<*const Vec, usize>) -> Result> { Ok(match value { ast::Value::Bool(x) => InitValue::Bool(*x), ast::Value::Number(x) => InitValue::Number(Number::new(*x)?), @@ -2313,14 +2317,22 @@ impl ByteCode { InitValue::Ref(idx) } ast::Value::Image(x) => { + let center = x.1.map(|(x, y)| Ok::<_,NumberError>((Number::new(x)?, Number::new(y)?))).transpose()?; let idx = *image_refs.entry(Rc::as_ptr(x)).or_insert_with(|| { - ref_values.push((Some(RefValue::Image((**x).clone())), value)); + ref_values.push((Some(RefValue::Image(x.0.clone(), center)), value)); + ref_values.len() - 1 + }); + InitValue::Ref(idx) + } + ast::Value::Audio(x) => { + let idx = *audio_refs.entry(Rc::as_ptr(x)).or_insert_with(|| { + ref_values.push((Some(RefValue::Audio((**x).clone())), value)); ref_values.len() - 1 }); InitValue::Ref(idx) } ast::Value::List(values, ref_id) => { - let res = RefValue::List(values.iter().map(|x| get_value(x, ref_values, refs, string_refs, image_refs)).collect::>()?); + let res = RefValue::List(values.iter().map(|x| get_value(x, ref_values, refs, string_refs, image_refs, audio_refs)).collect::>()?); match ref_id { Some(ref_id) => { let idx = *refs.get(&ref_id.0).ok_or(CompileError::UndefinedRef { value })?; @@ -2345,7 +2357,7 @@ impl ByteCode { let mut entities = vec![]; for global in role.globals.iter() { - globals.push((global.def.name.clone(), get_value(&global.init, &mut ref_values, &refs, &mut string_refs, &mut image_refs)?)); + globals.push((global.def.name.clone(), get_value(&global.init, &mut ref_values, &refs, &mut string_refs, &mut image_refs, &mut audio_refs)?)); } for (entity, entity_info) in script_info.entities.iter() { @@ -2353,6 +2365,7 @@ impl ByteCode { let mut fields = vec![]; let mut scripts = vec![]; let mut costumes = vec![]; + let mut sounds = vec![]; let visible = entity.visible; let active_costume = entity.active_costume; @@ -2362,11 +2375,13 @@ impl ByteCode { let heading = Number::new(entity.heading.rem_euclid(360.0))?; for field in entity.fields.iter() { - fields.push((field.def.name.clone(), get_value(&field.init, &mut ref_values, &refs, &mut string_refs, &mut image_refs)?)); + fields.push((field.def.name.clone(), get_value(&field.init, &mut ref_values, &refs, &mut string_refs, &mut image_refs, &mut audio_refs)?)); } - for costume in entity.costumes.iter() { - costumes.push((costume.def.name.clone(), get_value(&costume.init, &mut ref_values, &refs, &mut string_refs, &mut image_refs)?)); + costumes.push((costume.def.name.clone(), get_value(&costume.init, &mut ref_values, &refs, &mut string_refs, &mut image_refs, &mut audio_refs)?)); + } + for sound in entity.sounds.iter() { + sounds.push((sound.def.name.clone(), get_value(&sound.init, &mut ref_values, &refs, &mut string_refs, &mut image_refs, &mut audio_refs)?)); } for (script, pos) in entity_info.scripts.iter().copied() { @@ -2402,7 +2417,7 @@ impl ByteCode { scripts.push((event, pos)); } - entities.push(EntityInitInfo { name, fields, costumes, scripts, active_costume, pos, heading, size, visible, color }); + entities.push(EntityInitInfo { name, fields, sounds, costumes, scripts, active_costume, pos, heading, size, visible, color }); } let ref_values = ref_values.into_iter().map(|x| x.0.ok_or(CompileError::UndefinedRef { value: x.1 })).collect::>()?; diff --git a/src/process.rs b/src/process.rs index 8b7594d..fa536c2 100644 --- a/src/process.rs +++ b/src/process.rs @@ -1203,7 +1203,7 @@ impl<'gc, C: CustomTypes, S: System> Process<'gc, C, S> { Value::Image(x) => Some(x), Value::String(x) => match x.as_str() { "" => None, - x => match entity.costume_list.iter().find(|c| c.0 == x) { + x => match entity.costume_list.iter().find(|&c| c.0 == x) { Some(c) => Some(c.1.clone()), None => return Err(ErrorCause::UndefinedCostume { name: x.into() }), } @@ -1237,6 +1237,7 @@ impl<'gc, C: CustomTypes, S: System> Process<'gc, C, S> { let target = target_cell.borrow(); let new_entity = Gc::new(mc, RefLock::new(Entity { name: target.name.clone(), + sound_list: target.sound_list.clone(), costume_list: target.costume_list.clone(), costume: target.costume.clone(), state: C::EntityState::from(EntityKind::Clone { parent: &*target }), diff --git a/src/runtime.rs b/src/runtime.rs index 3cfcbee..609d1f8 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -628,7 +628,7 @@ pub struct Image { pub content: Vec, /// The center `(x, y)` of the image as used for NetsBlox sprites. /// [`None`] is implied to represent `(w / 2, h / 2)` based on the true image size (size decoding cannot be done in no-std). - pub center: Option<(i32, i32)> + pub center: Option<(Number, Number)> } /// An audio clip type that can be used in the VM. @@ -946,8 +946,9 @@ pub enum EntityKind<'gc, 'a, C: CustomTypes, S: System> { #[collect(no_drop, bound = "")] pub struct Entity<'gc, C: CustomTypes, S: System> { #[collect(require_static)] pub name: Rc, - #[collect(require_static)] pub costume_list: Rc>)>>, - #[collect(require_static)] pub costume: Option>>, + #[collect(require_static)] pub sound_list: Rc)>>, + #[collect(require_static)] pub costume_list: Rc)>>, + #[collect(require_static)] pub costume: Option>, #[collect(require_static)] pub state: C::EntityState, #[collect(require_static)] pub alive: bool, pub root: Option>>>, @@ -1206,7 +1207,8 @@ impl<'gc, C: CustomTypes, S: System> GlobalContext<'gc, C, S> { pub fn from_init(mc: &Mutation<'gc>, init_info: &InitInfo, bytecode: Rc, settings: Settings, system: Rc) -> 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::Image(content, center) => Value::Image(Rc::new(Image {content: content.clone(), center: *center })), + RefValue::Audio(content) => Value::Audio(Rc::new(Audio { content: content.clone() })), RefValue::List(_) => Value::List(Gc::new(mc, Default::default())), }).collect::>(); @@ -1220,7 +1222,7 @@ impl<'gc, C: CustomTypes, S: System> GlobalContext<'gc, C, S> { for (allocated_ref, ref_value) in iter::zip(&allocated_refs, &init_info.ref_values) { match ref_value { - RefValue::String(_) | RefValue::Image(_) => continue, // we already populated these values in the first pass + RefValue::String(_) | RefValue::Image(_, _) | RefValue::Audio(_) => continue, // we already populated these values in the first pass RefValue::List(values) => { let allocated_ref = match allocated_ref { Value::List(x) => x, @@ -1246,6 +1248,18 @@ impl<'gc, C: CustomTypes, S: System> GlobalContext<'gc, C, S> { fields.define_or_redefine(field, Shared::Unique(get_value(value, &allocated_refs))); } + let sound_list = { + let mut res = Vec::with_capacity(entity_info.sounds.len()); + for (name, value) in entity_info.sounds.iter() { + let sound = match get_value(value, &allocated_refs) { + Value::Audio(x) => x.clone(), + _ => unreachable!(), + }; + res.push((name.clone(), sound)); + } + Rc::new(res) + }; + let costume_list = { let mut res = Vec::with_capacity(entity_info.costumes.len()); for (name, value) in entity_info.costumes.iter() { @@ -1277,7 +1291,7 @@ impl<'gc, C: CustomTypes, S: System> GlobalContext<'gc, C, S> { 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 }))); + entities.insert(entity_info.name.clone(), Gc::new(mc, RefLock::new(Entity { alive: true, root: None, name, fields, sound_list, costume_list, costume, state }))); } let proj_name = init_info.proj_name.clone(); @@ -1473,7 +1487,7 @@ pub enum Command<'gc, 'a, C: CustomTypes, S: System> { /// Sets the costume on the entity. This should essentially assigns the costume to [`Entity::costume`], /// but is treated as a system command so that custom code can be executed when an entity switches costumes. - SetCostume { costume: Option>> }, + SetCostume { costume: Option> }, /// Moves the entity to a specific location. GotoXY { x: Number, y: Number }, diff --git a/src/std_system.rs b/src/std_system.rs index 9e46e4c..32ac60a 100644 --- a/src/std_system.rs +++ b/src/std_system.rs @@ -123,9 +123,9 @@ async fn call_rpc_async>>(context: &Context, client: } if content_type.contains("image/") { - Ok(SimpleValue::Image(res)) + Ok(SimpleValue::Image(Image { content: res, center: None })) } else if content_type.contains("audio/") { - Ok(SimpleValue::Audio(res)) + Ok(SimpleValue::Audio(Audio { content: res })) } else if let Some(x) = parse_json_slice::(&res).ok().and_then(|x| SimpleValue::from_json(x).ok()) { Ok(x) } else if let Ok(x) = String::from_utf8(res) {