avm2: Split `StageObject` associated constructors into two versions: allocation-only (`for_display_object`) and allocation-and-initialization (`for_display_object_childless`).

The latter is intended for display objects that *do not* have children and can be both allocated and initialized in one shot. Hence the name.
This commit is contained in:
David Wendt 2021-05-31 23:56:55 -04:00
parent 46a4da9dc5
commit e4201625a1
9 changed files with 131 additions and 127 deletions

View File

@ -62,15 +62,7 @@ pub fn graphics<'gc>(
activation, activation,
)? { )? {
Value::Undefined | Value::Null => { Value::Undefined | Value::Null => {
let graphics_proto = activation.context.avm2.prototypes().graphics; let graphics = Value::from(StageObject::graphics_of(activation, dobj)?);
let graphics_constr = activation.context.avm2.constructors().graphics;
let graphics = Value::from(StageObject::for_display_object(
activation.context.gc_context,
dobj,
graphics_constr,
graphics_proto,
));
this.set_property( this.set_property(
this, this,
&QName::new(Namespace::private(NS_RUFFLE_INTERNAL), "graphics"), &QName::new(Namespace::private(NS_RUFFLE_INTERNAL), "graphics"),

View File

@ -48,15 +48,7 @@ pub fn graphics<'gc>(
activation, activation,
)? { )? {
Value::Undefined | Value::Null => { Value::Undefined | Value::Null => {
let graphics_proto = activation.context.avm2.prototypes().graphics; let graphics = Value::from(StageObject::graphics_of(activation, dobj)?);
let graphics_constr = activation.context.avm2.constructors().graphics;
let graphics = Value::from(StageObject::for_display_object(
activation.context.gc_context,
dobj,
graphics_constr,
graphics_proto,
));
this.set_property( this.set_property(
this, this,
&QName::new(Namespace::private(NS_RUFFLE_INTERNAL), "graphics"), &QName::new(Namespace::private(NS_RUFFLE_INTERNAL), "graphics"),

View File

@ -20,7 +20,16 @@ pub fn stage_deriver<'gc>(
proto: Object<'gc>, proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
StageObject::derive(constr, proto, activation.context.gc_context) let base = ScriptObjectData::base_new(Some(proto), ScriptObjectClass::ClassInstance(constr));
Ok(StageObject(GcCell::allocate(
activation.context.gc_context,
StageObjectData {
base,
display_object: None,
},
))
.into())
} }
#[derive(Clone, Collect, Debug, Copy)] #[derive(Clone, Collect, Debug, Copy)]
@ -38,14 +47,31 @@ pub struct StageObjectData<'gc> {
} }
impl<'gc> StageObject<'gc> { impl<'gc> StageObject<'gc> {
/// Allocate the AVM2 side of a display object intended to be of a given
/// constructor's type.
///
/// This function makes no attempt to construct the returned object. You
/// are responsible for calling the native initializer of the given
/// constructor at a later time. Typically, a display object that can
/// contain movie-constructed children must first allocate itself (using
/// this function), construct it's children, and then finally initialize
/// itself. Display objects that do not need to use this flow should use
/// `for_display_object_childless`.
pub fn for_display_object( pub fn for_display_object(
mc: MutationContext<'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
display_object: DisplayObject<'gc>, display_object: DisplayObject<'gc>,
constr: Object<'gc>, mut constr: Object<'gc>,
proto: Object<'gc>, ) -> Result<Self, Error> {
) -> Self { let proto = constr
Self(GcCell::allocate( .get_property(
mc, constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
Ok(Self(GcCell::allocate(
activation.context.gc_context,
StageObjectData { StageObjectData {
base: ScriptObjectData::base_new( base: ScriptObjectData::base_new(
Some(proto), Some(proto),
@ -53,26 +79,47 @@ impl<'gc> StageObject<'gc> {
), ),
display_object: Some(display_object), display_object: Some(display_object),
}, },
)) )))
} }
/// Construct a stage object subclass. /// Allocate and construct the AVM2 side of a display object intended to be
pub fn derive( /// of a given constructor's type.
///
/// This function is intended for display objects that do not have children
/// and thus do not need to be allocated and initialized in separate phases.
pub fn for_display_object_childless(
activation: &mut Activation<'_, 'gc, '_>,
display_object: DisplayObject<'gc>,
constr: Object<'gc>, constr: Object<'gc>,
proto: Object<'gc>, ) -> Result<Self, Error> {
mc: MutationContext<'gc, '_>, let this = Self::for_display_object(activation, display_object, constr)?;
) -> Result<Object<'gc>, Error> {
let base =
ScriptObjectData::base_new(Some(proto), ScriptObjectClass::ClassInstance(constr));
Ok(StageObject(GcCell::allocate( constr.call_native_initializer(Some(this.into()), &[], activation, Some(constr))?;
mc,
Ok(this)
}
/// Create a `graphics` object for a given display object.
pub fn graphics_of(
activation: &mut Activation<'_, 'gc, '_>,
display_object: DisplayObject<'gc>,
) -> Result<Self, Error> {
let constr = activation.avm2().constructors().graphics;
let proto = activation.avm2().prototypes().graphics;
let this = Self(GcCell::allocate(
activation.context.gc_context,
StageObjectData { StageObjectData {
base, base: ScriptObjectData::base_new(
display_object: None, Some(proto),
ScriptObjectClass::ClassInstance(constr),
),
display_object: Some(display_object),
}, },
)) ));
.into())
constr.call_native_initializer(Some(this.into()), &[], activation, Some(constr))?;
Ok(this)
} }
} }

View File

@ -1,5 +1,8 @@
use crate::avm1::Object as Avm1Object; use crate::avm1::Object as Avm1Object;
use crate::avm2::{Object as Avm2Object, StageObject as Avm2StageObject, Value as Avm2Value}; use crate::avm2::{
Activation as Avm2Activation, Object as Avm2Object, StageObject as Avm2StageObject,
Value as Avm2Value,
};
use crate::backend::ui::MouseCursor; use crate::backend::ui::MouseCursor;
use crate::context::{RenderContext, UpdateContext}; use crate::context::{RenderContext, UpdateContext};
use crate::display_object::container::{dispatch_added_event, dispatch_removed_event}; use crate::display_object::container::{dispatch_added_event, dispatch_removed_event};
@ -426,15 +429,16 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
} }
if self.0.read().object.is_none() { if self.0.read().object.is_none() {
let simplebutton_proto = context.avm2.prototypes().simplebutton;
let simplebutton_constr = context.avm2.constructors().simplebutton; let simplebutton_constr = context.avm2.constructors().simplebutton;
let object = Avm2StageObject::for_display_object( let mut activation = Avm2Activation::from_nothing(context.reborrow());
context.gc_context, match Avm2StageObject::for_display_object(
&mut activation,
(*self).into(), (*self).into(),
simplebutton_constr, simplebutton_constr,
simplebutton_proto, ) {
); Ok(object) => self.0.write(context.gc_context).object = Some(object.into()),
self.0.write(context.gc_context).object = Some(object.into()); Err(e) => log::error!("Got {} when constructing AVM2 side of button", e),
};
let (up_state, up_should_fire) = self.create_state(context, swf::ButtonState::UP); let (up_state, up_should_fire) = self.create_state(context, swf::ButtonState::UP);
let (over_state, over_should_fire) = self.create_state(context, swf::ButtonState::OVER); let (over_state, over_should_fire) = self.create_state(context, swf::ButtonState::OVER);

View File

@ -7,7 +7,6 @@ use crate::avm1::{
}; };
use crate::avm2::{ use crate::avm2::{
Activation as Avm2Activation, Object as Avm2Object, StageObject as Avm2StageObject, Activation as Avm2Activation, Object as Avm2Object, StageObject as Avm2StageObject,
TObject as Avm2TObject,
}; };
use crate::backend::ui::MouseCursor; use crate::backend::ui::MouseCursor;
use crate::context::{RenderContext, UpdateContext}; use crate::context::{RenderContext, UpdateContext};
@ -1509,29 +1508,23 @@ impl<'gc> EditText<'gc> {
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
display_object: DisplayObject<'gc>, display_object: DisplayObject<'gc>,
) { ) {
let textfield_proto = context.avm2.prototypes().textfield;
let textfield_constr = context.avm2.constructors().textfield; let textfield_constr = context.avm2.constructors().textfield;
let object: Avm2Object<'gc> = Avm2StageObject::for_display_object(
context.gc_context,
display_object,
textfield_constr,
textfield_proto,
)
.into();
let mut activation = Avm2Activation::from_nothing(context.reborrow()); let mut activation = Avm2Activation::from_nothing(context.reborrow());
if let Err(e) = match Avm2StageObject::for_display_object_childless(
textfield_constr.call(Some(object), &[], &mut activation, Some(textfield_constr)) &mut activation,
{ display_object,
log::error!( textfield_constr,
) {
Ok(object) => {
let object: Avm2Object<'gc> = object.into();
self.0.write(activation.context.gc_context).object = Some(object.into())
}
Err(e) => log::error!(
"Got {} when constructing AVM2 side of dynamic text field", "Got {} when constructing AVM2 side of dynamic text field",
e e
); ),
} }
self.0.write(activation.context.gc_context).object = Some(object.into());
} }
} }

View File

@ -1,7 +1,6 @@
use crate::avm1::Object as Avm1Object; use crate::avm1::Object as Avm1Object;
use crate::avm2::{ use crate::avm2::{
Activation as Avm2Activation, Error as Avm2Error, Object as Avm2Object, Activation as Avm2Activation, Object as Avm2Object, StageObject as Avm2StageObject,
StageObject as Avm2StageObject, TObject as Avm2TObject,
}; };
use crate::backend::render::ShapeHandle; use crate::backend::render::ShapeHandle;
use crate::context::{RenderContext, UpdateContext}; use crate::context::{RenderContext, UpdateContext};
@ -115,27 +114,17 @@ impl<'gc> TDisplayObject<'gc> for Graphic<'gc> {
fn construct_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) { fn construct_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
if self.avm_type() == AvmType::Avm2 && matches!(self.object2(), Avm2Value::Undefined) { if self.avm_type() == AvmType::Avm2 && matches!(self.object2(), Avm2Value::Undefined) {
let mut allocator = || {
let shape_proto = context.avm2.prototypes().shape;
let shape_constr = context.avm2.constructors().shape; let shape_constr = context.avm2.constructors().shape;
let mut activation = Avm2Activation::from_nothing(context.reborrow());
let object = Avm2StageObject::for_display_object( match Avm2StageObject::for_display_object_childless(
context.gc_context, &mut activation,
(*self).into(), (*self).into(),
shape_constr, shape_constr,
shape_proto, ) {
) Ok(object) => {
.into(); self.0.write(activation.context.gc_context).avm2_object = Some(object.into())
}
let mut activation = Avm2Activation::from_nothing(context.reborrow());
shape_constr.call(Some(object), &[], &mut activation, Some(shape_constr))?;
Ok(object)
};
let result: Result<Avm2Object<'gc>, Avm2Error> = allocator();
match result {
Ok(object) => self.0.write(context.gc_context).avm2_object = Some(object),
Err(e) => log::error!("Got {} when constructing AVM2 side of display object", e), Err(e) => log::error!("Got {} when constructing AVM2 side of display object", e),
} }
} }

View File

@ -1513,7 +1513,7 @@ impl<'gc> MovieClip<'gc> {
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
display_object: DisplayObject<'gc>, display_object: DisplayObject<'gc>,
) { ) {
let mut constructor = self let constructor = self
.0 .0
.read() .read()
.avm2_constructor .avm2_constructor
@ -1521,19 +1521,8 @@ impl<'gc> MovieClip<'gc> {
let mut constr_thing = || { let mut constr_thing = || {
let mut activation = Avm2Activation::from_nothing(context.reborrow()); let mut activation = Avm2Activation::from_nothing(context.reborrow());
let mc_proto = constructor let object =
.get_property( Avm2StageObject::for_display_object(&mut activation, display_object, constructor)?
constructor,
&Avm2QName::new(Avm2Namespace::public(), "prototype"),
&mut activation,
)?
.coerce_to_object(&mut activation)?;
let object = Avm2StageObject::for_display_object(
activation.context.gc_context,
display_object,
constructor,
mc_proto,
)
.into(); .into();
Ok(object) Ok(object)

View File

@ -3,8 +3,7 @@
use crate::avm1::Object as Avm1Object; use crate::avm1::Object as Avm1Object;
use crate::avm2::{ use crate::avm2::{
Activation as Avm2Activation, Event as Avm2Event, Object as Avm2Object, Activation as Avm2Activation, Event as Avm2Event, Object as Avm2Object,
ScriptObject as Avm2ScriptObject, StageObject as Avm2StageObject, TObject as Avm2TObject, ScriptObject as Avm2ScriptObject, StageObject as Avm2StageObject, Value as Avm2Value,
Value as Avm2Value,
}; };
use crate::backend::ui::UiBackend; use crate::backend::ui::UiBackend;
use crate::config::Letterbox; use crate::config::Letterbox;
@ -518,29 +517,24 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> {
_instantiated_by: Instantiator, _instantiated_by: Instantiator,
_run_frame: bool, _run_frame: bool,
) { ) {
let stage_proto = context.avm2.prototypes().stage;
let stage_constr = context.avm2.constructors().stage; let stage_constr = context.avm2.constructors().stage;
let avm2_stage = Avm2StageObject::for_display_object(
context.gc_context,
(*self).into(),
stage_constr,
stage_proto,
);
// TODO: Replace this when we have a convenience method for constructing AVM2 native objects. // TODO: Replace this when we have a convenience method for constructing AVM2 native objects.
// TODO: We should only do this if the movie is actually an AVM2 movie. // TODO: We should only do this if the movie is actually an AVM2 movie.
// This is necessary for EventDispatcher super-constructor to run. // This is necessary for EventDispatcher super-constructor to run.
let mut activation = Avm2Activation::from_nothing(context.reborrow()); let mut activation = Avm2Activation::from_nothing(context.reborrow());
if let Err(e) = stage_constr.call_native_initializer( let avm2_stage = Avm2StageObject::for_display_object_childless(
Some(avm2_stage.into()),
&[],
&mut activation, &mut activation,
Some(stage_constr), (*self).into(),
) { stage_constr,
log::error!("Unable to construct AVM2 Stage: {}", e); );
}
self.0.write(activation.context.gc_context).avm2_object = avm2_stage.into(); match avm2_stage {
Ok(avm2_stage) => {
self.0.write(activation.context.gc_context).avm2_object = avm2_stage.into()
}
Err(e) => log::error!("Unable to construct AVM2 Stage: {}", e),
}
} }
fn id(&self) -> CharacterId { fn id(&self) -> CharacterId {

View File

@ -1,7 +1,9 @@
//! Video player display object //! Video player display object
use crate::avm1::{Object as Avm1Object, StageObject as Avm1StageObject}; use crate::avm1::{Object as Avm1Object, StageObject as Avm1StageObject};
use crate::avm2::{Object as Avm2Object, StageObject as Avm2StageObject}; use crate::avm2::{
Activation as Avm2Activation, Object as Avm2Object, StageObject as Avm2StageObject,
};
use crate::backend::render::BitmapInfo; use crate::backend::render::BitmapInfo;
use crate::backend::video::{EncodedFrame, VideoStreamHandle}; use crate::backend::video::{EncodedFrame, VideoStreamHandle};
use crate::bounding_box::BoundingBox; use crate::bounding_box::BoundingBox;
@ -381,17 +383,19 @@ impl<'gc> TDisplayObject<'gc> for Video<'gc> {
fn construct_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) { fn construct_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
let vm_type = self.avm_type(); let vm_type = self.avm_type();
if vm_type == AvmType::Avm2 && matches!(self.object2(), Avm2Value::Undefined) { if vm_type == AvmType::Avm2 && matches!(self.object2(), Avm2Value::Undefined) {
let video_proto = context.avm2.prototypes().video;
let video_constr = context.avm2.constructors().video; let video_constr = context.avm2.constructors().video;
let mut activation = Avm2Activation::from_nothing(context.reborrow());
let object: Avm2Object<'_> = Avm2StageObject::for_display_object( match Avm2StageObject::for_display_object_childless(
context.gc_context, &mut activation,
(*self).into(), (*self).into(),
video_constr, video_constr,
video_proto, ) {
) Ok(object) => {
.into(); let object: Avm2Object<'gc> = object.into();
self.0.write(context.gc_context).object = Some(object.into()); self.0.write(context.gc_context).object = Some(object.into())
}
Err(e) => log::error!("Got {} when constructing AVM2 side of video player", e),
}
} }
} }