Skip to content

Commit

Permalink
support sounds
Browse files Browse the repository at this point in the history
  • Loading branch information
dragazo committed Oct 26, 2023
1 parent 8d99026 commit ebde6db
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 20 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
33 changes: 24 additions & 9 deletions src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1070,13 +1070,15 @@ pub(crate) enum InitValue {
pub(crate) enum RefValue {
List(Vec<InitValue>),
Image(Vec<u8>, Option<(Number, Number)>),
Audio(Vec<u8>),
String(String),
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
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,
Expand Down Expand Up @@ -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(())
Expand Down Expand Up @@ -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<RefValue>, &'a ast::Value)>, refs: &mut BTreeMap<usize, usize>) {
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(|| {
Expand All @@ -2293,7 +2297,7 @@ impl ByteCode {
}
}

fn get_value<'a>(value: &'a ast::Value, ref_values: &mut Vec<(Option<RefValue>, &'a ast::Value)>, refs: &BTreeMap<usize, usize>, string_refs: &mut BTreeMap<&'a str, usize>, image_refs: &mut BTreeMap<*const Vec<u8>, usize>) -> Result<InitValue, CompileError<'a>> {
fn get_value<'a>(value: &'a ast::Value, ref_values: &mut Vec<(Option<RefValue>, &'a ast::Value)>, refs: &BTreeMap<usize, usize>, string_refs: &mut BTreeMap<&'a str, usize>, image_refs: &mut BTreeMap<*const (Vec<u8>, Option<(f64, f64)>), usize>, audio_refs: &mut BTreeMap<*const Vec<u8>, usize>) -> Result<InitValue, CompileError<'a>> {
Ok(match value {
ast::Value::Bool(x) => InitValue::Bool(*x),
ast::Value::Number(x) => InitValue::Number(Number::new(*x)?),
Expand All @@ -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::<Result<_,_>>()?);
let res = RefValue::List(values.iter().map(|x| get_value(x, ref_values, refs, string_refs, image_refs, audio_refs)).collect::<Result<_,_>>()?);
match ref_id {
Some(ref_id) => {
let idx = *refs.get(&ref_id.0).ok_or(CompileError::UndefinedRef { value })?;
Expand All @@ -2345,14 +2357,15 @@ 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() {
let name = entity.name.clone();
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;
Expand All @@ -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() {
Expand Down Expand Up @@ -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::<Result<_,_>>()?;
Expand Down
3 changes: 2 additions & 1 deletion src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1203,7 +1203,7 @@ impl<'gc, C: CustomTypes<S>, S: System<C>> 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() }),
}
Expand Down Expand Up @@ -1237,6 +1237,7 @@ impl<'gc, C: CustomTypes<S>, S: System<C>> 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 }),
Expand Down
28 changes: 21 additions & 7 deletions src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,7 @@ pub struct Image {
pub content: Vec<u8>,
/// 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.
Expand Down Expand Up @@ -946,8 +946,9 @@ pub enum EntityKind<'gc, 'a, C: CustomTypes<S>, S: System<C>> {
#[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 sound_list: Rc<Vec<(String, Rc<Audio>)>>,
#[collect(require_static)] pub costume_list: Rc<Vec<(String, Rc<Image>)>>,
#[collect(require_static)] pub costume: Option<Rc<Image>>,
#[collect(require_static)] pub state: C::EntityState,
#[collect(require_static)] pub alive: bool,
pub root: Option<Gc<'gc, RefLock<Entity<'gc, C, S>>>>,
Expand Down Expand Up @@ -1206,7 +1207,8 @@ 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::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::<Vec<_>>();

Expand All @@ -1220,7 +1222,7 @@ impl<'gc, C: CustomTypes<S>, S: System<C>> 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,
Expand All @@ -1246,6 +1248,18 @@ impl<'gc, C: CustomTypes<S>, S: System<C>> 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() {
Expand Down Expand Up @@ -1277,7 +1291,7 @@ impl<'gc, C: CustomTypes<S>, S: System<C>> 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();
Expand Down Expand Up @@ -1473,7 +1487,7 @@ pub enum Command<'gc, 'a, C: CustomTypes<S>, S: System<C>> {

/// 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<Rc<Vec<u8>>> },
SetCostume { costume: Option<Rc<Image>> },

/// Moves the entity to a specific location.
GotoXY { x: Number, y: Number },
Expand Down
4 changes: 2 additions & 2 deletions src/std_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,9 @@ async fn call_rpc_async<C: CustomTypes<StdSystem<C>>>(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::<Json>(&res).ok().and_then(|x| SimpleValue::from_json(x).ok()) {
Ok(x)
} else if let Ok(x) = String::from_utf8(res) {
Expand Down

0 comments on commit ebde6db

Please sign in to comment.