Skip to content

Commit

Permalink
audio/image names
Browse files Browse the repository at this point in the history
  • Loading branch information
dragazo committed Feb 17, 2024
1 parent 7f8a21a commit c878b7a
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 55 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ rustls-tls-webpki-roots = [
# core deps
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
gc-arena = { version = "=0.4.0", default-features = false }
netsblox-ast = { version = "=0.5.5", default-features = false }
netsblox-ast = { version = "=0.5.6", default-features = false }
# netsblox-ast = { path = "../netsblox-ast", default-features = false }
num-traits = { version = "0.2.17", default-features = false }
num-derive = { version = "0.4.1", default-features = false }
Expand Down
90 changes: 49 additions & 41 deletions src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,9 @@ pub(crate) enum Instruction<'a> {
PushCostumeNumber,
/// Pushes a shallow copy of the entity's list of static costumes onto the value stack.
PushCostumeList,
/// Consumes 1 value, `costume`, from the value stack and pushes its name onto the value stack.
PushCostumeName,

/// Consumes 1 value, `costume`, from the value stack and assigns it as the current costume.
/// This can be an image or the name of a static costume on the entity.
/// Empty string can be used to remove the current costume.
Expand Down Expand Up @@ -860,31 +863,33 @@ impl<'a> BinaryRead<'a> for Instruction<'a> {
119 => read_prefixed!(Instruction::PushCostume),
120 => read_prefixed!(Instruction::PushCostumeNumber),
121 => read_prefixed!(Instruction::PushCostumeList),
122 => read_prefixed!(Instruction::SetCostume),
123 => read_prefixed!(Instruction::NextCostume),
122 => read_prefixed!(Instruction::PushCostumeName),

123 => read_prefixed!(Instruction::SetCostume),
124 => read_prefixed!(Instruction::NextCostume),

124 => read_prefixed!(Instruction::PushSoundList),
125 => read_prefixed!(Instruction::PlaySound { blocking: true }),
126 => read_prefixed!(Instruction::PlaySound { blocking: false }),
127 => read_prefixed!(Instruction::PlayNotes { blocking: true }),
128 => read_prefixed!(Instruction::PlayNotes { blocking: false }),
129 => read_prefixed!(Instruction::StopSounds),
125 => read_prefixed!(Instruction::PushSoundList),
126 => read_prefixed!(Instruction::PlaySound { blocking: true }),
127 => read_prefixed!(Instruction::PlaySound { blocking: false }),
128 => read_prefixed!(Instruction::PlayNotes { blocking: true }),
129 => read_prefixed!(Instruction::PlayNotes { blocking: false }),
130 => read_prefixed!(Instruction::StopSounds),

130 => read_prefixed!(Instruction::Clone),
131 => read_prefixed!(Instruction::DeleteClone),
131 => read_prefixed!(Instruction::Clone),
132 => read_prefixed!(Instruction::DeleteClone),

132 => read_prefixed!(Instruction::ClearEffects),
133 => read_prefixed!(Instruction::ClearDrawings),
133 => read_prefixed!(Instruction::ClearEffects),
134 => read_prefixed!(Instruction::ClearDrawings),

134 => read_prefixed!(Instruction::GotoXY),
135 => read_prefixed!(Instruction::Goto),
135 => read_prefixed!(Instruction::GotoXY),
136 => read_prefixed!(Instruction::Goto),

136 => read_prefixed!(Instruction::PointTowardsXY),
137 => read_prefixed!(Instruction::PointTowards),
137 => read_prefixed!(Instruction::PointTowardsXY),
138 => read_prefixed!(Instruction::PointTowards),

138 => read_prefixed!(Instruction::Forward),
139 => read_prefixed!(Instruction::Forward),

139 => read_prefixed!(Instruction::UnknownBlock {} : name, args),
140 => read_prefixed!(Instruction::UnknownBlock {} : name, args),

_ => unreachable!(),
}
Expand Down Expand Up @@ -1071,31 +1076,33 @@ impl BinaryWrite for Instruction<'_> {
Instruction::PushCostume => append_prefixed!(119),
Instruction::PushCostumeNumber => append_prefixed!(120),
Instruction::PushCostumeList => append_prefixed!(121),
Instruction::SetCostume => append_prefixed!(122),
Instruction::NextCostume => append_prefixed!(123),
Instruction::PushCostumeName => append_prefixed!(122),

Instruction::SetCostume => append_prefixed!(123),
Instruction::NextCostume => append_prefixed!(124),

Instruction::PushSoundList => append_prefixed!(124),
Instruction::PlaySound { blocking: true } => append_prefixed!(125),
Instruction::PlaySound { blocking: false } => append_prefixed!(126),
Instruction::PlayNotes { blocking: true } => append_prefixed!(127),
Instruction::PlayNotes { blocking: false } => append_prefixed!(128),
Instruction::StopSounds => append_prefixed!(129),
Instruction::PushSoundList => append_prefixed!(125),
Instruction::PlaySound { blocking: true } => append_prefixed!(126),
Instruction::PlaySound { blocking: false } => append_prefixed!(127),
Instruction::PlayNotes { blocking: true } => append_prefixed!(128),
Instruction::PlayNotes { blocking: false } => append_prefixed!(129),
Instruction::StopSounds => append_prefixed!(130),

Instruction::Clone => append_prefixed!(130),
Instruction::DeleteClone => append_prefixed!(131),
Instruction::Clone => append_prefixed!(131),
Instruction::DeleteClone => append_prefixed!(132),

Instruction::ClearEffects => append_prefixed!(132),
Instruction::ClearDrawings => append_prefixed!(133),
Instruction::ClearEffects => append_prefixed!(133),
Instruction::ClearDrawings => append_prefixed!(134),

Instruction::GotoXY => append_prefixed!(134),
Instruction::Goto => append_prefixed!(135),
Instruction::GotoXY => append_prefixed!(135),
Instruction::Goto => append_prefixed!(136),

Instruction::PointTowardsXY => append_prefixed!(136),
Instruction::PointTowards => append_prefixed!(137),
Instruction::PointTowardsXY => append_prefixed!(137),
Instruction::PointTowards => append_prefixed!(138),

Instruction::Forward => append_prefixed!(138),
Instruction::Forward => append_prefixed!(139),

Instruction::UnknownBlock { name, args } => append_prefixed!(139: move str name, args),
Instruction::UnknownBlock { name, args } => append_prefixed!(140: move str name, args),
}
}
}
Expand Down Expand Up @@ -1127,8 +1134,8 @@ pub(crate) enum InitValue {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub(crate) enum RefValue {
List(Vec<InitValue>),
Image(Vec<u8>, Option<(Number, Number)>),
Audio(Vec<u8>),
Image(Vec<u8>, Option<(Number, Number)>, CompactString),
Audio(Vec<u8>, CompactString),
String(CompactString),
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
Expand Down Expand Up @@ -1552,6 +1559,7 @@ impl<'a: 'b, 'b> ByteCodeBuilder<'a, 'b> {
ast::ExprKind::Costume => self.ins.push(Instruction::PushCostume.into()),
ast::ExprKind::CostumeNumber => self.ins.push(Instruction::PushCostumeNumber.into()),
ast::ExprKind::CostumeList => self.ins.push(Instruction::PushCostumeList.into()),
ast::ExprKind::CostumeName { costume } => self.append_simple_ins(entity, &[costume], Instruction::PushCostumeName)?,
ast::ExprKind::SoundList => self.ins.push(Instruction::PushSoundList.into()),
ast::ExprKind::Size => self.ins.push(Instruction::PushProperty { prop: Property::Size }.into()),
ast::ExprKind::IsVisible => self.ins.push(Instruction::PushProperty { prop: Property::Visible }.into()),
Expand Down Expand Up @@ -2532,7 +2540,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>, Option<(f64, f64)>), usize>, audio_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)>, CompactString), usize>, audio_refs: &mut BTreeMap<*const (Vec<u8>, CompactString), 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 @@ -2554,14 +2562,14 @@ impl ByteCode {
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.0.clone(), center)), value));
ref_values.push((Some(RefValue::Image(x.0.clone(), center, x.2.clone())), 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.push((Some(RefValue::Audio(x.0.clone(), x.1.clone())), value));
ref_values.len() - 1
});
InitValue::Ref(idx)
Expand Down
9 changes: 9 additions & 0 deletions src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,15 @@ impl<'gc, C: CustomTypes<S>, S: System<C>> Process<'gc, C, S> {
self.value_stack.push(Value::List(Gc::new(mc, RefLock::new(entity.costume_list.iter().map(|x| Value::Image(x.1.clone())).collect()))));
self.pos = aft_pos;
}
Instruction::PushCostumeName => {
let costume = self.value_stack.pop().unwrap();
self.value_stack.push(match costume {
Value::String(x) if x.is_empty() => empty_string().into(),
Value::Image(x) => Rc::new(x.name.clone()).into(),
x => return Err(ErrorCause::ConversionError { got: x.get_type(), expected: Type::Image }),
});
self.pos = aft_pos;
}
Instruction::SetCostume => {
let mut entity_raw = self.call_stack.last().unwrap().entity.borrow_mut(mc);
let entity = &mut *entity_raw;
Expand Down
30 changes: 19 additions & 11 deletions src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -644,14 +644,18 @@ 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<(Number, Number)>
pub center: Option<(Number, Number)>,
/// The user-level name of the image
pub name: CompactString,
}

/// An audio clip type that can be used in the VM.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Audio {
/// The raw binary content of the audio clip
pub content: Vec<u8>,
/// The user-level name of the audio clip
pub name: CompactString,
}

/// An error produced by [`Value::to_simple`]
Expand Down Expand Up @@ -739,7 +743,7 @@ impl SimpleValue {
SimpleValue::List(x) => Json::Array(x.into_iter().map(SimpleValue::into_netsblox_json).collect()),
SimpleValue::Image(img) => {
let center_attrs = img.center.map(|(x, y)| format!(" center-x=\"{x}\" center-y=\"{y}\"")).unwrap_or_default();
Json::String(format!("<costume{center_attrs} image=\"data:image/png;base64,{}\" />", crate::util::base64_encode(&img.content)))
Json::String(format!("<costume name=\"{}\"{center_attrs} image=\"data:image/png;base64,{}\" />", ast::util::xml_escape(&img.name), crate::util::base64_encode(&img.content)))
},
SimpleValue::Audio(audio) => Json::String(format!("<sound sound=\"data:audio/mpeg;base64,{}\" />", crate::util::base64_encode(&audio.content))),
}
Expand All @@ -762,19 +766,21 @@ impl SimpleValue {
let mut center_x = None;
let mut center_y = None;
let mut content = None;
let mut name = "untitled".into();
loop {
match tokenizer.next() {
Some(Ok(xmlparser::Token::Attribute { local, value, .. })) => match local.as_str() {
"center-x" => center_x = Some(value.as_str().parse().ok().and_then(|x| Number::new(x).ok()).ok_or(FromNetsBloxJsonError::BadImage)?),
"center-y" => center_y = Some(value.as_str().parse().ok().and_then(|y| Number::new(y).ok()).ok_or(FromNetsBloxJsonError::BadImage)?),
"name" => name = value.as_str().into(),
"image" => match value.as_str().split(";base64,").nth(1) {
Some(raw) if value.as_str().starts_with("data:image/") => content = Some(crate::util::base64_decode(raw).map_err(|_| FromNetsBloxJsonError::BadImage)?),
_ => return Err(FromNetsBloxJsonError::BadImage),
}
_ => (),
}
Some(Ok(xmlparser::Token::ElementEnd { .. })) => match content {
Some(content) => return Ok(SimpleValue::Image(Image { content, center: center_x.zip(center_y) })),
Some(content) => return Ok(SimpleValue::Image(Image { content, center: center_x.zip(center_y), name })),
None => return Ok(SimpleValue::String(x.into())),
}
Some(Ok(_)) => (),
Expand All @@ -784,17 +790,19 @@ impl SimpleValue {
}
"sound" => {
let mut content = None;
let mut name = "untitled".into();
loop {
match tokenizer.next() {
Some(Ok(xmlparser::Token::Attribute { local, value, .. })) => match local.as_str() {
"name" => name = value.as_str().into(),
"sound" => match value.as_str().split(";base64,").nth(1) {
Some(raw) if value.as_str().starts_with("data:audio/") => content = Some(crate::util::base64_decode(raw).map_err(|_| FromNetsBloxJsonError::BadAudio)?),
_ => return Err(FromNetsBloxJsonError::BadAudio),
}
_ => (),
}
Some(Ok(xmlparser::Token::ElementEnd { .. })) => match content {
Some(content) => return Ok(SimpleValue::Audio(Audio { content })),
Some(content) => return Ok(SimpleValue::Audio(Audio { content, name })),
None => return Ok(SimpleValue::String(x.into())),
}
Some(Ok(_)) => (),
Expand Down Expand Up @@ -861,10 +869,10 @@ fn test_netsblox_json() {
SimpleValue::String("<costume>".into()),
SimpleValue::String("<costume/>".into()),
SimpleValue::String("<costume />".into()),
SimpleValue::Image(Image { content: vec![], center: None }),
SimpleValue::Image(Image { content: vec![], center: Some((Number::new(0.0).unwrap(), Number::new(4.5).unwrap())) }),
SimpleValue::Image(Image { content: vec![0, 1, 2, 255, 254, 253, 127, 128], center: None }),
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())) }),
SimpleValue::Image(Image { content: vec![], center: None, name: "test".into() }),
SimpleValue::Image(Image { content: vec![], center: Some((Number::new(0.0).unwrap(), Number::new(4.5).unwrap())), name: "another one".into() }),
SimpleValue::Image(Image { content: vec![0, 1, 2, 255, 254, 253, 127, 128], center: None, name: "untitled".into() }),
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() }),
]);
let js = val.clone().into_netsblox_json();
let back = SimpleValue::from_netsblox_json(js).unwrap();
Expand Down Expand Up @@ -1372,8 +1380,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, center) => Value::Image(Rc::new(Image {content: content.clone(), center: *center })),
RefValue::Audio(content) => Value::Audio(Rc::new(Audio { content: content.clone() })),
RefValue::Image(content, center, name) => Value::Image(Rc::new(Image {content: content.clone(), center: *center, name: name.clone() })),
RefValue::Audio(content, name) => Value::Audio(Rc::new(Audio { content: content.clone(), name: name.clone() })),
RefValue::List(_) => Value::List(Gc::new(mc, Default::default())),
}).collect::<Vec<_>>();

Expand All @@ -1387,7 +1395,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(_, _) | RefValue::Audio(_) => 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 Down
4 changes: 2 additions & 2 deletions src/std_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ async fn call_rpc_async<C: CustomTypes<S>, S: System<C>>(context: &NetsBloxConte
}

if content_type.contains("image/") {
Ok(SimpleValue::Image(Image { content: res, center: None }))
Ok(SimpleValue::Image(Image { content: res, center: None, name: "untitled".into() }))
} else if content_type.contains("audio/") {
Ok(SimpleValue::Audio(Audio { content: res }))
Ok(SimpleValue::Audio(Audio { content: res, name: "untitled".into() }))
} else if let Some(x) = parse_json_slice::<Json>(&res).ok() {
SimpleValue::from_netsblox_json(x).map_err(|e| format_compact!("Received ill-formed success value: {e:?}"))
} else if let Ok(x) = CompactString::from_utf8(res) {
Expand Down
Loading

0 comments on commit c878b7a

Please sign in to comment.