From 8a58956f1fb2396254ade297f357fd55ac031f4a Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 16 Aug 2021 19:16:25 -0400 Subject: [PATCH] avm2: Add `flash.media.SoundChannel` class & associated object storage --- core/src/avm2/globals.rs | 11 ++ core/src/avm2/globals/flash/media.rs | 1 + .../avm2/globals/flash/media/soundchannel.rs | 50 ++++++++ core/src/avm2/object.rs | 15 ++- core/src/avm2/object/soundchannel_object.rs | 110 ++++++++++++++++++ 5 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 core/src/avm2/globals/flash/media/soundchannel.rs create mode 100644 core/src/avm2/object/soundchannel_object.rs diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index de08f302c..ec1097cfb 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -117,6 +117,7 @@ pub struct SystemPrototypes<'gc> { pub regexp: Object<'gc>, pub vector: Object<'gc>, pub soundtransform: Object<'gc>, + pub soundchannel: Object<'gc>, } impl<'gc> SystemPrototypes<'gc> { @@ -168,6 +169,7 @@ impl<'gc> SystemPrototypes<'gc> { regexp: empty, vector: empty, soundtransform: empty, + soundchannel: empty, } } } @@ -210,6 +212,7 @@ pub struct SystemClasses<'gc> { pub regexp: Object<'gc>, pub vector: Object<'gc>, pub soundtransform: Object<'gc>, + pub soundchannel: Object<'gc>, } impl<'gc> SystemClasses<'gc> { @@ -261,6 +264,7 @@ impl<'gc> SystemClasses<'gc> { regexp: empty, vector: empty, soundtransform: empty, + soundchannel: empty, } } } @@ -819,6 +823,13 @@ pub fn load_player_globals<'gc>( domain, script, )?; + avm2_system_class!( + soundchannel, + activation, + flash::media::soundchannel::create_class(mc), + domain, + script + ); // package `flash.text` avm2_system_class!( diff --git a/core/src/avm2/globals/flash/media.rs b/core/src/avm2/globals/flash/media.rs index 31d6c3091..8879080ea 100644 --- a/core/src/avm2/globals/flash/media.rs +++ b/core/src/avm2/globals/flash/media.rs @@ -1,6 +1,7 @@ //! `flash.media` namespace pub mod sound; +pub mod soundchannel; pub mod soundmixer; pub mod soundtransform; pub mod video; diff --git a/core/src/avm2/globals/flash/media/soundchannel.rs b/core/src/avm2/globals/flash/media/soundchannel.rs new file mode 100644 index 000000000..c94607816 --- /dev/null +++ b/core/src/avm2/globals/flash/media/soundchannel.rs @@ -0,0 +1,50 @@ +//! `flash.media.SoundChannel` builtin/prototype + +use crate::avm2::activation::Activation; +use crate::avm2::class::{Class, ClassAttributes}; +use crate::avm2::method::Method; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::{soundchannel_allocator, Object}; +use crate::avm2::value::Value; +use crate::avm2::Error; +use gc_arena::{GcCell, MutationContext}; + +/// Implements `flash.media.SoundChannel`'s instance constructor. +pub fn instance_init<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + if let Some(this) = this { + activation.super_init(this, &[])?; + } + + Ok(Value::Undefined) +} + +/// Implements `flash.media.SoundChannel`'s class constructor. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Construct `SoundChannel`'s class. +pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { + let class = Class::new( + QName::new(Namespace::package("flash.media"), "SoundChannel"), + Some(QName::new(Namespace::package("flash.events"), "EventDispatcher").into()), + Method::from_builtin(instance_init, "", mc), + Method::from_builtin(class_init, "", mc), + mc, + ); + + let mut write = class.write(mc); + + write.set_attributes(ClassAttributes::SEALED | ClassAttributes::FINAL); + write.set_instance_allocator(soundchannel_allocator); + + class +} diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 3327a67e0..7cfce0065 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -15,7 +15,7 @@ use crate::avm2::traits::{Trait, TraitKind}; use crate::avm2::value::{Hint, Value}; use crate::avm2::vector::VectorStorage; use crate::avm2::Error; -use crate::backend::audio::SoundHandle; +use crate::backend::audio::{SoundHandle, SoundInstanceHandle}; use crate::display_object::DisplayObject; use gc_arena::{Collect, GcCell, MutationContext}; use ruffle_macros::enum_trait_object; @@ -37,6 +37,7 @@ mod primitive_object; mod regexp_object; mod script_object; mod sound_object; +mod soundchannel_object; mod stage_object; mod vector_object; mod xml_object; @@ -56,6 +57,7 @@ pub use crate::avm2::object::primitive_object::{primitive_allocator, PrimitiveOb pub use crate::avm2::object::regexp_object::{regexp_allocator, RegExpObject}; pub use crate::avm2::object::script_object::ScriptObject; pub use crate::avm2::object::sound_object::{sound_allocator, SoundObject}; +pub use crate::avm2::object::soundchannel_object::{soundchannel_allocator, SoundChannelObject}; pub use crate::avm2::object::stage_object::{stage_allocator, StageObject}; pub use crate::avm2::object::vector_object::{vector_allocator, VectorObject}; pub use crate::avm2::object::xml_object::{xml_allocator, XmlObject}; @@ -83,6 +85,7 @@ pub use crate::avm2::object::xml_object::{xml_allocator, XmlObject}; ClassObject(ClassObject<'gc>), VectorObject(VectorObject<'gc>), SoundObject(SoundObject<'gc>), + SoundChannelObject(SoundChannelObject<'gc>), } )] pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy { @@ -1226,6 +1229,16 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// /// This does nothing if the object is not a sound. fn set_sound(self, _mc: MutationContext<'gc, '_>, _sound: SoundHandle) {} + + /// Unwrap this object's sound instance handle. + fn as_sound_instance(self) -> Option { + None + } + + /// Associate the object with a particular sound instance handle. + /// + /// This does nothing if the object is not a sound channel. + fn set_sound_instance(self, _mc: MutationContext<'gc, '_>, _sound: SoundInstanceHandle) {} } pub enum ObjectPtr {} diff --git a/core/src/avm2/object/soundchannel_object.rs b/core/src/avm2/object/soundchannel_object.rs new file mode 100644 index 000000000..029514bca --- /dev/null +++ b/core/src/avm2/object/soundchannel_object.rs @@ -0,0 +1,110 @@ +//! Object representation for sounds + +use crate::avm2::activation::Activation; +use crate::avm2::class::Class; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::script_object::ScriptObjectData; +use crate::avm2::object::{Object, ObjectPtr, TObject}; +use crate::avm2::scope::Scope; +use crate::avm2::string::AvmString; +use crate::avm2::value::Value; +use crate::avm2::Error; +use crate::backend::audio::SoundInstanceHandle; +use crate::{ + impl_avm2_custom_object, impl_avm2_custom_object_instance, impl_avm2_custom_object_properties, +}; +use gc_arena::{Collect, GcCell, MutationContext}; + +/// A class instance allocator that allocates SoundChannel objects. +pub fn soundchannel_allocator<'gc>( + class: Object<'gc>, + proto: Object<'gc>, + activation: &mut Activation<'_, 'gc, '_>, +) -> Result, Error> { + let base = ScriptObjectData::base_new(Some(proto), Some(class)); + + Ok(SoundChannelObject(GcCell::allocate( + activation.context.gc_context, + SoundChannelObjectData { base, sound: None }, + )) + .into()) +} + +#[derive(Clone, Collect, Debug, Copy)] +#[collect(no_drop)] +pub struct SoundChannelObject<'gc>(GcCell<'gc, SoundChannelObjectData<'gc>>); + +#[derive(Clone, Collect, Debug)] +#[collect(no_drop)] +pub struct SoundChannelObjectData<'gc> { + /// Base script object + base: ScriptObjectData<'gc>, + + /// The sound this object holds. + #[collect(require_static)] + sound: Option, +} + +impl<'gc> SoundChannelObject<'gc> { + /// Convert a bare sound instance into it's object representation. + /// + /// In AS3, playing sounds are accessed through subclasses of `Sound`. As a + /// result, this needs to take the subclass so that the returned object is + /// an instance of the correct class. + pub fn from_sound_instance( + activation: &mut Activation<'_, 'gc, '_>, + class: Object<'gc>, + sound: SoundInstanceHandle, + ) -> Result, Error> { + let proto = class + .get_property( + class, + &QName::new(Namespace::public(), "prototype"), + activation, + )? + .coerce_to_object(activation)?; + let base = ScriptObjectData::base_new(Some(proto), Some(class)); + + let mut sound_object: Object<'gc> = SoundChannelObject(GcCell::allocate( + activation.context.gc_context, + SoundChannelObjectData { + base, + sound: Some(sound), + }, + )) + .into(); + sound_object.install_instance_traits(activation, class)?; + + //TODO: Do we need to call the default initializer of sounds? + + Ok(sound_object) + } +} + +impl<'gc> TObject<'gc> for SoundChannelObject<'gc> { + impl_avm2_custom_object!(base); + impl_avm2_custom_object_properties!(base); + impl_avm2_custom_object_instance!(base); + + fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result, Error> { + Ok(Object::from(*self).into()) + } + + fn derive(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result, Error> { + let base = ScriptObjectData::base_new(Some((*self).into()), None); + + Ok(SoundChannelObject(GcCell::allocate( + activation.context.gc_context, + SoundChannelObjectData { base, sound: None }, + )) + .into()) + } + + fn as_sound_instance(self) -> Option { + self.0.read().sound + } + + fn set_sound_instance(self, mc: MutationContext<'gc, '_>, sound: SoundInstanceHandle) { + self.0.write(mc).sound = Some(sound); + } +}