diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 834c31180bad..e980441e7f56 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -68,6 +68,7 @@ pub struct SystemClasses<'gc> { pub textfield: ClassObject<'gc>, pub textformat: ClassObject<'gc>, pub graphics: ClassObject<'gc>, + pub loader: ClassObject<'gc>, pub loaderinfo: ClassObject<'gc>, pub bytearray: ClassObject<'gc>, pub stage: ClassObject<'gc>, @@ -167,6 +168,7 @@ impl<'gc> SystemClasses<'gc> { textfield: object, textformat: object, graphics: object, + loader: object, loaderinfo: object, bytearray: object, stage: object, @@ -628,6 +630,7 @@ fn load_playerglobal<'gc>( ("flash.display", "Scene", scene), ("flash.display", "FrameLabel", framelabel), ("flash.display", "Graphics", graphics), + ("flash.display", "Loader", loader), ("flash.display", "LoaderInfo", loaderinfo), ("flash.display", "MovieClip", movieclip), ("flash.display", "Shape", shape), diff --git a/core/src/avm2/globals/flash/display/bitmap.rs b/core/src/avm2/globals/flash/display/bitmap.rs index 373dfcc03a61..105512c5bd9a 100644 --- a/core/src/avm2/globals/flash/display/bitmap.rs +++ b/core/src/avm2/globals/flash/display/bitmap.rs @@ -1,31 +1,84 @@ //! `flash.display.Bitmap` builtin/prototype use crate::avm2::activation::Activation; -use crate::avm2::globals::flash::display::bitmap_data::fill_bitmap_data_from_symbol; use crate::avm2::object::{BitmapDataObject, Object, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; +use crate::avm2::globals::flash::display::bitmap_data::fill_bitmap_data_from_symbol; use crate::bitmap::bitmap_data::BitmapData; use crate::character::Character; use crate::display_object::{Bitmap, TDisplayObject}; use crate::{avm2_stub_getter, avm2_stub_setter}; use gc_arena::GcCell; +pub fn assign_default_bitmap_data<'gc>( + activation: &mut Activation<'_, 'gc>, + bitmap: Bitmap<'gc>, +) -> Result<(), Error<'gc>> { + // We are being initialized by the movie. This means that we + // need to create bitmap data right away, since all AVM2 bitmaps + // hold bitmap data. + let bd_object = if let Some(bd_class) = bitmap.avm2_bitmapdata_class() { + Some(bd_class.construct(activation, &[])?) + } else if let Some(b_class) = bitmap.avm2_bitmap_class() { + // Instantiating Bitmap from a Flex-style bitmap asset. + // Contrary to the above comment, this code path DOES + // trigger from AVM2, since the DisplayObject instantiation + // logic does its job in this case. + if let Some((movie, symbol_id)) = activation + .context + .library + .avm2_class_registry() + .class_symbol(b_class) + { + if let Some(Character::Bitmap(bitmap)) = activation + .context + .library + .library_for_movie_mut(movie) + .character_by_id(symbol_id) + .cloned() + { + let new_bitmap_data = + GcCell::allocate(activation.context.gc_context, BitmapData::default()); + + fill_bitmap_data_from_symbol(activation, bitmap, new_bitmap_data); + Some(BitmapDataObject::from_bitmap_data( + activation, + new_bitmap_data, + activation.context.avm2.classes().bitmapdata, + )?) + } else { + //Class association not to a Bitmap + return Err("Attempted to instantiate Bitmap from timeline with symbol class associated to non-Bitmap!".into()); + } + } else { + //Class association not bidirectional + return Err("Cannot instantiate Bitmap from timeline without bidirectional symbol class association".into()); + } + } else { + // No class association + None + }; + + if let Some(bd_object) = bd_object.and_then(|bd| bd.as_bitmap_data()) { + bitmap.set_bitmap_data(&mut activation.context, bd_object); + } + Ok(()) +} + /// Implements `flash.display.Bitmap`'s `init` method, which is called from the constructor pub fn init<'gc>( activation: &mut Activation<'_, 'gc>, this: Option>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if let Some(mut this) = this { + if let Some(this) = this { activation.super_init(this, &[])?; let bitmap_data = args .get(0) - .cloned() - .unwrap_or(Value::Null) - .as_object() + .and_then(|v| v.as_object()) .and_then(|bd| bd.as_bitmap_data()); //TODO: Pixel snapping is not supported let _pixel_snapping = args @@ -33,77 +86,16 @@ pub fn init<'gc>( .cloned() .unwrap_or_else(|| "auto".into()) .coerce_to_string(activation)?; - let smoothing = args - .get(2) - .cloned() - .unwrap_or_else(|| false.into()) - .coerce_to_boolean(); + let smoothing = args.get(2).map(|v| v.coerce_to_boolean()); if let Some(bitmap) = this.as_display_object().and_then(|dobj| dobj.as_bitmap()) { - //We are being initialized by the movie. This means that we - //need to create bitmap data right away, since all AVM2 bitmaps - //hold bitmap data. - - let bd_object = if let Some(bd_class) = bitmap.avm2_bitmapdata_class() { - bd_class.construct(activation, &[])? - } else if let Some(b_class) = bitmap.avm2_bitmap_class() { - // Instantiating Bitmap from a Flex-style bitmap asset. - // Contrary to the above comment, this code path DOES - // trigger from AVM2, since the DisplayObject instantiation - // logic does its job in this case. - if let Some((movie, symbol_id)) = activation - .context - .library - .avm2_class_registry() - .class_symbol(b_class) - { - if let Some(Character::Bitmap(bitmap)) = activation - .context - .library - .library_for_movie_mut(movie) - .character_by_id(symbol_id) - .cloned() - { - let new_bitmap_data = - GcCell::allocate(activation.context.gc_context, BitmapData::default()); - - fill_bitmap_data_from_symbol(activation, bitmap, new_bitmap_data); - BitmapDataObject::from_bitmap_data( - activation, - new_bitmap_data, - activation.context.avm2.classes().bitmapdata, - )? - } else { - //Class association not to a Bitmap - return Err("Attempted to instantiate Bitmap from timeline with symbol class associated to non-Bitmap!".into()); - } - } else { - //Class association not bidirectional - return Err("Cannot instantiate Bitmap from timeline without bidirectional symbol class association".into()); - } - } else { - // No class association - return Err( - "Cannot instantiate Bitmap from timeline without associated symbol class" - .into(), - ); - }; - - this.set_public_property("bitmapData", bd_object.into(), activation)?; - - bitmap.set_smoothing(activation.context.gc_context, smoothing); - } else { - //We are being initialized by AVM2 (and aren't associated with a - //Bitmap subclass). - - let bitmap_data = bitmap_data.unwrap_or_else(|| { - GcCell::allocate(activation.context.gc_context, BitmapData::dummy()) - }); - - let bitmap = - Bitmap::new_with_bitmap_data(&mut activation.context, 0, bitmap_data, smoothing); - - this.init_display_object(&mut activation.context, bitmap.into()); + assign_default_bitmap_data(activation, bitmap)?; + if let Some(bitmap_data) = bitmap_data { + bitmap.set_bitmap_data(&mut activation.context, bitmap_data); + } + if let Some(smoothing) = smoothing { + bitmap.set_smoothing(activation.context.gc_context, smoothing); + } } } diff --git a/core/src/avm2/globals/flash/display/display_object.rs b/core/src/avm2/globals/flash/display/display_object.rs index e3018fd8bc66..acf82f29aebd 100644 --- a/core/src/avm2/globals/flash/display/display_object.rs +++ b/core/src/avm2/globals/flash/display/display_object.rs @@ -4,11 +4,15 @@ use crate::avm2::activation::Activation; use crate::avm2::filters::FilterAvm2Ext; use crate::avm2::object::{Object, TObject}; use crate::avm2::value::Value; -use crate::avm2::Error; use crate::avm2::Multiname; use crate::avm2::Namespace; use crate::avm2::{ArrayObject, ArrayStorage}; -use crate::display_object::{DisplayObject, HitTestOptions, TDisplayObject}; +use crate::avm2::{ClassObject, Error}; +use crate::bitmap::bitmap_data::BitmapData; +use crate::display_object::{ + Avm2Button, Bitmap, DisplayObject, EditText, Graphic, HitTestOptions, LoaderDisplay, MovieClip, + TDisplayObject, +}; use crate::ecma_conversions::round_to_even; use crate::frame_lifecycle::catchup_display_object_to_frame; use crate::prelude::*; @@ -16,11 +20,107 @@ use crate::string::AvmString; use crate::types::{Degrees, Percent}; use crate::vminterface::Instantiator; use crate::{avm2_stub_getter, avm2_stub_setter}; +use gc_arena::GcCell; use ruffle_render::filters::Filter; use std::str::FromStr; +use std::sync::Arc; use swf::BlendMode; pub use crate::avm2::object::stage_allocator as display_object_allocator; +use crate::tag_utils::SwfMovie; + +fn create_display_object<'gc>( + activation: &mut Activation<'_, 'gc>, + class_object: ClassObject<'gc>, +) -> Result<(DisplayObject<'gc>, bool, bool), Error<'gc>> { + // Iterate the inheritance chain, starting from `this` and working backwards through `super`s + // This accounts for the cases where a super may be linked to symbol, but `this` may not be + let mut current_class = Some(class_object); + while let Some(class) = current_class { + if let Some((movie, symbol)) = activation + .context + .library + .avm2_class_registry() + .class_symbol(class) + { + let display_object = activation + .context + .library + .library_for_movie_mut(movie) + .instantiate_by_id(symbol, activation.context.gc_context)?; + + return Ok((display_object, true, true)); + } + current_class = class.superclass_object(); + } + + if class_object.has_class_in_chain(activation.avm2().classes().loader) { + return Ok(( + LoaderDisplay::new_with_avm2( + activation.context.gc_context, + activation.context.swf.clone(), + ) + .into(), + false, + false, + )); + } + + if class_object.has_class_in_chain(activation.avm2().classes().simplebutton) { + return Ok(( + Avm2Button::empty_button(&mut activation.context).into(), + true, + false, + )); + } + + if class_object.has_class_in_chain(activation.avm2().classes().bitmap) { + let bitmap_data = GcCell::allocate(activation.context.gc_context, BitmapData::dummy()); + let bitmap = Bitmap::new_with_bitmap_data(&mut activation.context, 0, bitmap_data, false); + return Ok((bitmap.into(), false, false)); + } + + if class_object.has_class_in_chain(activation.avm2().classes().shape) { + return Ok(( + Graphic::new_with_avm2(&mut activation.context).into(), + false, + false, + )); + } + + if class_object.has_class_in_chain(activation.avm2().classes().textfield) { + let movie = Arc::new(SwfMovie::empty(activation.context.swf.version())); + let edit_text = EditText::new(&mut activation.context, movie, 0.0, 0.0, 100.0, 100.0); + return Ok((edit_text.into(), false, false)); + } + + let movie = Arc::new(SwfMovie::empty(activation.context.swf.version())); + return Ok(( + MovieClip::new_with_avm2(movie, None, class_object, activation.context.gc_context).into(), + false, + false, + )); +} + +pub fn setup_display_object<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, +) -> Result, Error<'gc>> { + let (display_object, post_instantiate, catchup) = create_display_object( + activation, + this.instance_of() + .expect("Something that inherits DisplayObject should have a class"), + )?; + this.init_display_object(&mut activation.context, display_object); + display_object.set_object2(&mut activation.context, this); + if post_instantiate { + display_object.post_instantiation(&mut activation.context, None, Instantiator::Avm2, false); + } + if catchup { + catchup_display_object_to_frame(&mut activation.context, display_object); + } + Ok(display_object) +} /// Implements `flash.display.DisplayObject`'s native instance constructor. pub fn native_instance_init<'gc>( @@ -32,36 +132,7 @@ pub fn native_instance_init<'gc>( activation.super_init(this, &[])?; if this.as_display_object().is_none() { - let mut class_object = this.instance_of(); - - // Iterate the inheritance chain, starting from `this` and working backwards through `super`s - // This accounts for the cases where a super may be linked to symbol, but `this` may not be - while let Some(class) = class_object { - if let Some((movie, symbol)) = activation - .context - .library - .avm2_class_registry() - .class_symbol(class) - { - let child = activation - .context - .library - .library_for_movie_mut(movie) - .instantiate_by_id(symbol, activation.context.gc_context)?; - - this.init_display_object(&mut activation.context, child); - - child.post_instantiation( - &mut activation.context, - None, - Instantiator::Avm2, - false, - ); - catchup_display_object_to_frame(&mut activation.context, child); - break; - } - class_object = class.superclass_object(); - } + setup_display_object(activation, this)?; } if let Some(dobj) = this.as_display_object() { diff --git a/core/src/avm2/globals/flash/display/display_object_container.rs b/core/src/avm2/globals/flash/display/display_object_container.rs index f398e8cd16e7..dc2a68d1ab6f 100644 --- a/core/src/avm2/globals/flash/display/display_object_container.rs +++ b/core/src/avm2/globals/flash/display/display_object_container.rs @@ -2,7 +2,6 @@ use crate::avm2::activation::Activation; use crate::avm2::error::range_error; -use crate::avm2::globals::flash::display::sprite::init_empty_sprite; use crate::avm2::object::{Object, TObject}; use crate::avm2::value::Value; use crate::avm2::{ArrayObject, ArrayStorage, Error}; @@ -182,17 +181,10 @@ pub fn add_child<'gc>( .unwrap_or(Value::Undefined) .as_object() .ok_or("ArgumentError: Child not a valid display object")?; - if child.as_display_object().is_none() { - let sprite = activation.avm2().classes().sprite; - if child.is_of_type(sprite, activation) { - // [NA] Hack to make Haxe work - they call addChild before super() - // This will create an empty sprite the same way sprite's constructor will. - init_empty_sprite(activation, child)?; - } - } let child = child - .as_display_object() - .ok_or("ArgumentError: Child not a valid display object")?; + .as_stage_object() + .ok_or("ArgumentError: Child not a valid display object")? + .get_or_create_display_object(activation)?; let target_index = ctr.num_children(); validate_add_operation(activation, parent, child, target_index)?; diff --git a/core/src/avm2/globals/flash/display/loader.rs b/core/src/avm2/globals/flash/display/loader.rs index 44a72befdf71..1582ab6d3850 100644 --- a/core/src/avm2/globals/flash/display/loader.rs +++ b/core/src/avm2/globals/flash/display/loader.rs @@ -7,7 +7,6 @@ use crate::avm2::value::Value; use crate::avm2::Multiname; use crate::avm2::{Error, Object}; use crate::backend::navigator::Request; -use crate::display_object::LoaderDisplay; use crate::display_object::MovieClip; use crate::loader::{Avm2LoaderData, MovieLoaderEventHandler}; use crate::tag_utils::SwfMovie; @@ -19,15 +18,6 @@ pub fn init<'gc>( _args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(mut this) = this { - if this.as_display_object().is_none() { - let new_do = LoaderDisplay::new_with_avm2( - activation.context.gc_context, - this, - activation.context.swf.clone(), - ); - this.init_display_object(&mut activation.context, new_do.into()); - } - // Some LoaderInfo properties (such as 'bytesLoaded' and 'bytesTotal') are always // accessible, even before the 'init' event has fired. Using an empty movie gives // us the correct value (0) for them. diff --git a/core/src/avm2/globals/flash/display/movie_clip.rs b/core/src/avm2/globals/flash/display/movie_clip.rs index f32bc0f6b806..1a1d8407cc78 100644 --- a/core/src/avm2/globals/flash/display/movie_clip.rs +++ b/core/src/avm2/globals/flash/display/movie_clip.rs @@ -16,7 +16,8 @@ pub fn add_frame_script<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(mc) = this - .and_then(|o| o.as_display_object()) + .and_then(|o| o.as_stage_object()) + .and_then(|o| o.get_or_create_display_object(activation).ok()) .and_then(|dobj| dobj.as_movie_clip()) { for (frame_id, callable) in args.chunks_exact(2).map(|s| (s[0], s[1])) { diff --git a/core/src/avm2/globals/flash/display/shape.rs b/core/src/avm2/globals/flash/display/shape.rs index 459daa71f354..8304016ad9bf 100644 --- a/core/src/avm2/globals/flash/display/shape.rs +++ b/core/src/avm2/globals/flash/display/shape.rs @@ -5,7 +5,6 @@ use crate::avm2::object::{Object, StageObject, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; use crate::avm2::Multiname; -use crate::display_object::Graphic; /// Implements `flash.display.Shape`'s 'init' method, which is called from the constructor pub fn init<'gc>( @@ -15,12 +14,6 @@ pub fn init<'gc>( ) -> Result, Error<'gc>> { if let Some(this) = this { activation.super_init(this, &[])?; - - if this.as_display_object().is_none() { - let new_do = Graphic::new_with_avm2(&mut activation.context, this); - - this.init_display_object(&mut activation.context, new_do.into()); - } } Ok(Value::Undefined) diff --git a/core/src/avm2/globals/flash/display/simple_button.rs b/core/src/avm2/globals/flash/display/simple_button.rs index 229490888c0a..d88f9afd36ef 100644 --- a/core/src/avm2/globals/flash/display/simple_button.rs +++ b/core/src/avm2/globals/flash/display/simple_button.rs @@ -4,8 +4,7 @@ use crate::avm2::activation::Activation; use crate::avm2::object::{Object, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; -use crate::display_object::{Avm2Button, ButtonTracking, TDisplayObject}; -use crate::vminterface::Instantiator; +use crate::display_object::{ButtonTracking, TDisplayObject}; use swf::ButtonState; pub use crate::avm2::globals::flash::media::soundmixer::{ @@ -21,43 +20,50 @@ pub fn init<'gc>( if let Some(this) = this { activation.super_init(this, &[])?; - if this.as_display_object().is_none() { - let new_do = Avm2Button::empty_button(&mut activation.context); - - new_do.post_instantiation(&mut activation.context, None, Instantiator::Avm2, false); - this.init_display_object(&mut activation.context, new_do.into()); - - let up_state = args + if let Some(button) = this.as_display_object().and_then(|d| d.as_avm2_button()) { + if let Some(up_state) = args .get(0) - .cloned() - .unwrap_or(Value::Null) - .as_object() - .and_then(|o| o.as_display_object()); - new_do.set_state_child(&mut activation.context, ButtonState::UP, up_state); + .and_then(|v| v.as_object()) + .and_then(|o| o.as_display_object()) + { + button.set_state_child(&mut activation.context, ButtonState::UP, Some(up_state)); + } - let over_state = args + if let Some(over_state) = args .get(1) - .cloned() - .unwrap_or(Value::Null) - .as_object() - .and_then(|o| o.as_display_object()); - new_do.set_state_child(&mut activation.context, ButtonState::OVER, over_state); - - let down_state = args + .and_then(|v| v.as_object()) + .and_then(|o| o.as_display_object()) + { + button.set_state_child( + &mut activation.context, + ButtonState::OVER, + Some(over_state), + ); + } + + if let Some(down_state) = args .get(2) - .cloned() - .unwrap_or(Value::Null) - .as_object() - .and_then(|o| o.as_display_object()); - new_do.set_state_child(&mut activation.context, ButtonState::DOWN, down_state); - - let hit_state = args + .and_then(|v| v.as_object()) + .and_then(|o| o.as_display_object()) + { + button.set_state_child( + &mut activation.context, + ButtonState::DOWN, + Some(down_state), + ); + } + + if let Some(hit_state) = args .get(3) - .cloned() - .unwrap_or(Value::Null) - .as_object() - .and_then(|o| o.as_display_object()); - new_do.set_state_child(&mut activation.context, ButtonState::HIT_TEST, hit_state); + .and_then(|v| v.as_object()) + .and_then(|o| o.as_display_object()) + { + button.set_state_child( + &mut activation.context, + ButtonState::HIT_TEST, + Some(hit_state), + ); + } } } diff --git a/core/src/avm2/globals/flash/display/sprite.rs b/core/src/avm2/globals/flash/display/sprite.rs index 3ca7d59e05c0..01ff844f6981 100644 --- a/core/src/avm2/globals/flash/display/sprite.rs +++ b/core/src/avm2/globals/flash/display/sprite.rs @@ -5,9 +5,7 @@ use crate::avm2::object::{Object, StageObject, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; use crate::avm2::Multiname; -use crate::display_object::{MovieClip, SoundTransform, TDisplayObject}; -use crate::tag_utils::SwfMovie; -use std::sync::Arc; +use crate::display_object::{SoundTransform, TDisplayObject}; use swf::{Rectangle, Twips}; /// Implements `flash.display.Sprite`'s `init` method, which is called from the constructor @@ -18,30 +16,11 @@ pub fn init<'gc>( ) -> Result, Error<'gc>> { if let Some(this) = this { activation.super_init(this, &[])?; - - if this.as_display_object().is_none() { - init_empty_sprite(activation, this)?; - } } Ok(Value::Undefined) } -pub fn init_empty_sprite<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, -) -> Result<(), Error<'gc>> { - let class_object = this - .instance_of() - .ok_or("Attempted to construct Sprite on a bare object")?; - let movie = Arc::new(SwfMovie::empty(activation.context.swf.version())); - let new_do = MovieClip::new_with_avm2(movie, this, class_object, activation.context.gc_context); - - this.init_display_object(&mut activation.context, new_do.into()); - - Ok(()) -} - /// Implements `dropTarget`'s getter pub fn get_drop_target<'gc>( _activation: &mut Activation<'_, 'gc>, diff --git a/core/src/avm2/globals/flash/text/text_field.rs b/core/src/avm2/globals/flash/text/text_field.rs index fc15f7c1de78..a44cb8ed5413 100644 --- a/core/src/avm2/globals/flash/text/text_field.rs +++ b/core/src/avm2/globals/flash/text/text_field.rs @@ -4,12 +4,10 @@ use crate::avm2::activation::Activation; use crate::avm2::object::{Object, TObject, TextFormatObject}; use crate::avm2::value::Value; use crate::avm2::Error; -use crate::display_object::{AutoSizeMode, EditText, TDisplayObject, TextSelection}; +use crate::display_object::{AutoSizeMode, TDisplayObject, TextSelection}; use crate::html::TextFormat; use crate::string::AvmString; -use crate::tag_utils::SwfMovie; use crate::{avm2_stub_getter, avm2_stub_setter}; -use std::sync::Arc; use swf::Color; /// Implements `flash.text.TextField`'s `init` method, which is called from the constructor. @@ -20,13 +18,6 @@ pub fn init<'gc>( ) -> Result, Error<'gc>> { if let Some(this) = this { activation.super_init(this, &[])?; - - if this.as_display_object().is_none() { - let movie = Arc::new(SwfMovie::empty(activation.context.swf.version())); - let new_do = EditText::new(&mut activation.context, movie, 0.0, 0.0, 100.0, 100.0); - - this.init_display_object(&mut activation.context, new_do.into()); - } } Ok(Value::Undefined) diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index d6325bbfeb2d..7625962d8bd6 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -1140,6 +1140,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy None } + /// Get this object's `DisplayObject`, if it has one. + fn as_stage_object(&self) -> Option> { + None + } + /// Associate this object with a display object, if it can support such an /// association. /// diff --git a/core/src/avm2/object/stage_object.rs b/core/src/avm2/object/stage_object.rs index cabd0d0f2e47..6669908439e9 100644 --- a/core/src/avm2/object/stage_object.rs +++ b/core/src/avm2/object/stage_object.rs @@ -1,6 +1,7 @@ //! AVM2 object impl for the display hierarchy. use crate::avm2::activation::Activation; +use crate::avm2::globals::flash::display::display_object::setup_display_object; use crate::avm2::object::script_object::ScriptObjectData; use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject}; use crate::avm2::value::Value; @@ -107,6 +108,17 @@ impl<'gc> StageObject<'gc> { Ok(this) } + + pub fn get_or_create_display_object( + &self, + activation: &mut Activation<'_, 'gc>, + ) -> Result, Error<'gc>> { + if let Some(display_object) = self.0.read().display_object { + return Ok(display_object); + } + // Not an else as it'll keep self.0 locked + setup_display_object(activation, (*self).into()) + } } impl<'gc> TObject<'gc> for StageObject<'gc> { @@ -126,6 +138,10 @@ impl<'gc> TObject<'gc> for StageObject<'gc> { self.0.read().display_object } + fn as_stage_object(&self) -> Option> { + Some(*self) + } + fn init_display_object(&self, context: &mut UpdateContext<'_, 'gc>, obj: DisplayObject<'gc>) { self.0.write(context.gc_context).display_object = Some(obj); obj.set_object2(context, (*self).into()); diff --git a/core/src/display_object/graphic.rs b/core/src/display_object/graphic.rs index f82c3a86deb0..8089e8fc7f95 100644 --- a/core/src/display_object/graphic.rs +++ b/core/src/display_object/graphic.rs @@ -71,10 +71,7 @@ impl<'gc> Graphic<'gc> { } /// Construct an empty `Graphic`. - pub fn new_with_avm2( - context: &mut UpdateContext<'_, 'gc>, - avm2_object: Avm2Object<'gc>, - ) -> Self { + pub fn new_with_avm2(context: &mut UpdateContext<'_, 'gc>) -> Self { let static_data = GraphicStatic { id: 0, bounds: Default::default(), @@ -100,7 +97,7 @@ impl<'gc> Graphic<'gc> { GraphicData { base: Default::default(), static_data: gc_arena::Gc::allocate(context.gc_context, static_data), - avm2_object: Some(avm2_object), + avm2_object: None, drawing: Some(drawing), }, )) diff --git a/core/src/display_object/loader_display.rs b/core/src/display_object/loader_display.rs index 61838f9e3537..802fb368d1f3 100644 --- a/core/src/display_object/loader_display.rs +++ b/core/src/display_object/loader_display.rs @@ -34,22 +34,18 @@ impl fmt::Debug for LoaderDisplay<'_> { pub struct LoaderDisplayData<'gc> { base: InteractiveObjectBase<'gc>, container: ChildContainer<'gc>, - avm2_object: Avm2Object<'gc>, + avm2_object: Option>, movie: Arc, } impl<'gc> LoaderDisplay<'gc> { - pub fn new_with_avm2( - gc_context: MutationContext<'gc, '_>, - avm2_object: Avm2Object<'gc>, - movie: Arc, - ) -> Self { + pub fn new_with_avm2(gc_context: MutationContext<'gc, '_>, movie: Arc) -> Self { LoaderDisplay(GcCell::allocate( gc_context, LoaderDisplayData { base: Default::default(), container: ChildContainer::new(), - avm2_object, + avm2_object: None, movie, }, )) @@ -86,7 +82,11 @@ impl<'gc> TDisplayObject<'gc> for LoaderDisplay<'gc> { } fn object2(&self) -> Avm2Value<'gc> { - self.0.read().avm2_object.into() + self.0 + .read() + .avm2_object + .map(Avm2Value::from) + .unwrap_or(Avm2Value::Null) } fn as_container(self) -> Option> { diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index 33cc8f7e1992..af0fa37b7511 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -174,7 +174,7 @@ impl<'gc> MovieClip<'gc> { pub fn new_with_avm2( movie: Arc, - this: Avm2Object<'gc>, + this: Option>, class: Avm2ClassObject<'gc>, gc_context: MutationContext<'gc, '_>, ) -> Self { @@ -187,7 +187,7 @@ impl<'gc> MovieClip<'gc> { current_frame: 0, audio_stream: None, container: ChildContainer::new(), - object: Some(this.into()), + object: this.map(|this| this.into()), clip_event_handlers: Vec::new(), clip_event_flags: ClipEventFlag::empty(), frame_scripts: Vec::new(),