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,
)? {
Value::Undefined | Value::Null => {
let graphics_proto = activation.context.avm2.prototypes().graphics;
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,
));
let graphics = Value::from(StageObject::graphics_of(activation, dobj)?);
this.set_property(
this,
&QName::new(Namespace::private(NS_RUFFLE_INTERNAL), "graphics"),

View File

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

View File

@ -20,7 +20,16 @@ pub fn stage_deriver<'gc>(
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>,
) -> 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)]
@ -38,14 +47,31 @@ pub struct StageObjectData<'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(
mc: MutationContext<'gc, '_>,
activation: &mut Activation<'_, 'gc, '_>,
display_object: DisplayObject<'gc>,
constr: Object<'gc>,
proto: Object<'gc>,
) -> Self {
Self(GcCell::allocate(
mc,
mut constr: Object<'gc>,
) -> Result<Self, Error> {
let proto = constr
.get_property(
constr,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
Ok(Self(GcCell::allocate(
activation.context.gc_context,
StageObjectData {
base: ScriptObjectData::base_new(
Some(proto),
@ -53,26 +79,47 @@ impl<'gc> StageObject<'gc> {
),
display_object: Some(display_object),
},
))
)))
}
/// Construct a stage object subclass.
pub fn derive(
/// Allocate and construct the AVM2 side of a display object intended to be
/// 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>,
proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
) -> Result<Object<'gc>, Error> {
let base =
ScriptObjectData::base_new(Some(proto), ScriptObjectClass::ClassInstance(constr));
) -> Result<Self, Error> {
let this = Self::for_display_object(activation, display_object, constr)?;
Ok(StageObject(GcCell::allocate(
mc,
constr.call_native_initializer(Some(this.into()), &[], activation, Some(constr))?;
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 {
base,
display_object: None,
base: ScriptObjectData::base_new(
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::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::context::{RenderContext, UpdateContext};
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() {
let simplebutton_proto = context.avm2.prototypes().simplebutton;
let simplebutton_constr = context.avm2.constructors().simplebutton;
let object = Avm2StageObject::for_display_object(
context.gc_context,
let mut activation = Avm2Activation::from_nothing(context.reborrow());
match Avm2StageObject::for_display_object(
&mut activation,
(*self).into(),
simplebutton_constr,
simplebutton_proto,
);
self.0.write(context.gc_context).object = Some(object.into());
) {
Ok(object) => 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 (over_state, over_should_fire) = self.create_state(context, swf::ButtonState::OVER);

View File

@ -7,7 +7,6 @@ use crate::avm1::{
};
use crate::avm2::{
Activation as Avm2Activation, Object as Avm2Object, StageObject as Avm2StageObject,
TObject as Avm2TObject,
};
use crate::backend::ui::MouseCursor;
use crate::context::{RenderContext, UpdateContext};
@ -1509,29 +1508,23 @@ impl<'gc> EditText<'gc> {
context: &mut UpdateContext<'_, 'gc, '_>,
display_object: DisplayObject<'gc>,
) {
let textfield_proto = context.avm2.prototypes().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());
if let Err(e) =
textfield_constr.call(Some(object), &[], &mut activation, Some(textfield_constr))
{
log::error!(
match Avm2StageObject::for_display_object_childless(
&mut activation,
display_object,
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",
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::avm2::{
Activation as Avm2Activation, Error as Avm2Error, Object as Avm2Object,
StageObject as Avm2StageObject, TObject as Avm2TObject,
Activation as Avm2Activation, Object as Avm2Object, StageObject as Avm2StageObject,
};
use crate::backend::render::ShapeHandle;
use crate::context::{RenderContext, UpdateContext};
@ -115,27 +114,17 @@ impl<'gc> TDisplayObject<'gc> for Graphic<'gc> {
fn construct_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
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(
context.gc_context,
(*self).into(),
shape_constr,
shape_proto,
)
.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),
match Avm2StageObject::for_display_object_childless(
&mut activation,
(*self).into(),
shape_constr,
) {
Ok(object) => {
self.0.write(activation.context.gc_context).avm2_object = Some(object.into())
}
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, '_>,
display_object: DisplayObject<'gc>,
) {
let mut constructor = self
let constructor = self
.0
.read()
.avm2_constructor
@ -1521,20 +1521,9 @@ impl<'gc> MovieClip<'gc> {
let mut constr_thing = || {
let mut activation = Avm2Activation::from_nothing(context.reborrow());
let mc_proto = constructor
.get_property(
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();
let object =
Avm2StageObject::for_display_object(&mut activation, display_object, constructor)?
.into();
Ok(object)
};

View File

@ -3,8 +3,7 @@
use crate::avm1::Object as Avm1Object;
use crate::avm2::{
Activation as Avm2Activation, Event as Avm2Event, Object as Avm2Object,
ScriptObject as Avm2ScriptObject, StageObject as Avm2StageObject, TObject as Avm2TObject,
Value as Avm2Value,
ScriptObject as Avm2ScriptObject, StageObject as Avm2StageObject, Value as Avm2Value,
};
use crate::backend::ui::UiBackend;
use crate::config::Letterbox;
@ -518,29 +517,24 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> {
_instantiated_by: Instantiator,
_run_frame: bool,
) {
let stage_proto = context.avm2.prototypes().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: We should only do this if the movie is actually an AVM2 movie.
// This is necessary for EventDispatcher super-constructor to run.
let mut activation = Avm2Activation::from_nothing(context.reborrow());
if let Err(e) = stage_constr.call_native_initializer(
Some(avm2_stage.into()),
&[],
let avm2_stage = Avm2StageObject::for_display_object_childless(
&mut activation,
Some(stage_constr),
) {
log::error!("Unable to construct AVM2 Stage: {}", e);
}
(*self).into(),
stage_constr,
);
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 {

View File

@ -1,7 +1,9 @@
//! Video player display object
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::video::{EncodedFrame, VideoStreamHandle};
use crate::bounding_box::BoundingBox;
@ -381,17 +383,19 @@ impl<'gc> TDisplayObject<'gc> for Video<'gc> {
fn construct_frame(&self, context: &mut UpdateContext<'_, 'gc, '_>) {
let vm_type = self.avm_type();
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 object: Avm2Object<'_> = Avm2StageObject::for_display_object(
context.gc_context,
let mut activation = Avm2Activation::from_nothing(context.reborrow());
match Avm2StageObject::for_display_object_childless(
&mut activation,
(*self).into(),
video_constr,
video_proto,
)
.into();
self.0.write(context.gc_context).object = Some(object.into());
) {
Ok(object) => {
let object: Avm2Object<'gc> = 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),
}
}
}