core: Implement Object.registerClass (merge #344)

Implement Object.registerClass
This commit is contained in:
Mike Welsh 2020-03-25 18:55:49 -07:00 committed by GitHub
commit 4df1128c19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 291 additions and 134 deletions

View File

@ -979,7 +979,7 @@ impl<'gc> Avm1<'gc> {
/// If the level does not exist, then it will be created and instantiated
/// with a script object.
pub fn resolve_level(
&self,
&mut self,
level_id: u32,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> DisplayObject<'gc> {
@ -989,7 +989,7 @@ impl<'gc> Avm1<'gc> {
let mut level: DisplayObject<'_> =
MovieClip::new(NEWEST_PLAYER_VERSION, context.gc_context).into();
level.post_instantiation(context.gc_context, level, self.prototypes.movie_clip);
level.post_instantiation(self, context, level);
level.set_depth(context.gc_context, level_id as i32);
context.levels.insert(level_id, level);
@ -1804,7 +1804,7 @@ impl<'gc> Avm1<'gc> {
if let Some(clip) = self.target_clip() {
if let Some(clip) = clip.as_movie_clip() {
// The frame on the stack is 0-based, not 1-based.
clip.goto_frame(context, frame + 1, true);
clip.goto_frame(self, context, frame + 1, true);
} else {
log::error!("GotoFrame failed: Target is not a MovieClip");
}
@ -1850,7 +1850,7 @@ impl<'gc> Avm1<'gc> {
if let Some(clip) = self.target_clip() {
if let Some(clip) = clip.as_movie_clip() {
if let Some(frame) = clip.frame_label_to_number(label) {
clip.goto_frame(context, frame, true);
clip.goto_frame(self, context, frame, true);
} else {
log::warn!("GoToLabel: Frame label '{}' not found", label);
}
@ -2052,7 +2052,7 @@ impl<'gc> Avm1<'gc> {
fn action_next_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
if let Some(clip) = self.target_clip() {
if let Some(clip) = clip.as_movie_clip() {
clip.next_frame(context);
clip.next_frame(self, context);
} else {
log::warn!("NextFrame: Target is not a MovieClip");
}
@ -2165,7 +2165,7 @@ impl<'gc> Avm1<'gc> {
fn action_prev_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
if let Some(clip) = self.target_clip() {
if let Some(clip) = clip.as_movie_clip() {
clip.prev_frame(context);
clip.prev_frame(self, context);
} else {
log::warn!("PrevFrame: Target is not a MovieClip");
}

View File

@ -179,12 +179,7 @@ pub fn create_globals<'gc>(
boolean::create_proto(gc_context, object_proto, function_proto);
//TODO: These need to be constructors and should also set `.prototype` on each one
let object = FunctionObject::function(
gc_context,
Executable::Native(object::constructor),
Some(function_proto),
Some(object_proto),
);
let object = object::create_object_object(gc_context, object_proto, function_proto);
let color = FunctionObject::function(
gc_context,

View File

@ -229,14 +229,13 @@ fn attach_movie<'gc>(
.library
.library_for_movie(movie_clip.movie().unwrap())
.ok_or_else(|| "Movie is missing!".into())
.and_then(|l| {
l.instantiate_by_export_name(&export_name, context.gc_context, &avm.prototypes)
})
.and_then(|l| l.instantiate_by_export_name(&export_name, context.gc_context))
{
new_clip.post_instantiation(avm, context, new_clip);
// Set name and attach to parent.
new_clip.set_name(context.gc_context, &new_instance_name);
movie_clip.add_child_from_avm(context, new_clip, depth);
new_clip.run_frame(context);
new_clip.run_frame(avm, context);
// Copy properties from init_object to the movieclip.
let new_clip = new_clip.object().as_object().unwrap();
@ -272,16 +271,12 @@ fn create_empty_movie_clip<'gc>(
// Create empty movie clip.
let mut new_clip = MovieClip::new(avm.current_swf_version(), context.gc_context);
new_clip.post_instantiation(
context.gc_context,
new_clip.into(),
avm.prototypes.movie_clip,
);
new_clip.post_instantiation(avm, context, new_clip.into());
// Set name and attach to parent.
new_clip.set_name(context.gc_context, &new_instance_name);
movie_clip.add_child_from_avm(context, new_clip.into(), depth);
new_clip.run_frame(context);
new_clip.run_frame(avm, context);
Ok(new_clip.object().into())
}
@ -326,7 +321,7 @@ fn create_text_field<'gc>(
let mut text_field: DisplayObject<'gc> =
EditText::new(context, movie, x, y, width, height).into();
text_field.post_instantiation(context.gc_context, text_field, avm.prototypes().text_field);
text_field.post_instantiation(avm, context, text_field);
text_field.set_name(context.gc_context, &instance_name);
movie_clip.add_child_from_avm(context, text_field, depth as Depth);
@ -384,8 +379,10 @@ pub fn duplicate_movie_clip_with_bias<'gc>(
.library
.library_for_movie(movie_clip.movie().unwrap())
.ok_or_else(|| "Movie is missing!".into())
.and_then(|l| l.instantiate_by_id(movie_clip.id(), context.gc_context, &avm.prototypes))
.and_then(|l| l.instantiate_by_id(movie_clip.id(), context.gc_context))
{
new_clip.post_instantiation(avm, context, new_clip);
// Set name and attach to parent.
new_clip.set_name(context.gc_context, &new_instance_name);
parent.add_child_from_avm(context, new_clip, depth);
@ -395,7 +392,7 @@ pub fn duplicate_movie_clip_with_bias<'gc>(
new_clip.set_color_transform(context.gc_context, &*movie_clip.color_transform());
// TODO: Any other properties we should copy...?
// Definitely not ScriptObject properties.
new_clip.run_frame(context);
new_clip.run_frame(avm, context);
// Copy properties from init_object to the movieclip.
let new_clip = new_clip.object().as_object().unwrap();
@ -506,7 +503,7 @@ pub fn goto_frame<'gc>(
frame = frame.wrapping_sub(1);
frame = frame.wrapping_add(i32::from(scene_offset));
if frame >= 0 {
movie_clip.goto_frame(context, frame.saturating_add(1) as u16, stop);
movie_clip.goto_frame(avm, context, frame.saturating_add(1) as u16, stop);
}
}
val => {
@ -514,7 +511,7 @@ pub fn goto_frame<'gc>(
let frame_label = val.clone().coerce_to_string(avm, context)?;
if let Some(mut frame) = movie_clip.frame_label_to_number(&frame_label) {
frame = frame.wrapping_add(scene_offset);
movie_clip.goto_frame(context, frame, stop);
movie_clip.goto_frame(avm, context, frame, stop);
}
}
}
@ -523,11 +520,11 @@ pub fn goto_frame<'gc>(
fn next_frame<'gc>(
movie_clip: MovieClip<'gc>,
_avm: &mut Avm1<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
movie_clip.next_frame(context);
movie_clip.next_frame(avm, context);
Ok(Value::Undefined.into())
}
@ -543,11 +540,11 @@ fn play<'gc>(
fn prev_frame<'gc>(
movie_clip: MovieClip<'gc>,
_avm: &mut Avm1<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
movie_clip.prev_frame(context);
movie_clip.prev_frame(avm, context);
Ok(Value::Undefined.into())
}

View File

@ -1,7 +1,9 @@
//! Object prototype
use crate::avm1::function::{Executable, FunctionObject};
use crate::avm1::property::Attribute::{self, *};
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, Object, TObject, UpdateContext, Value};
use crate::character::Character;
use enumset::EnumSet;
use gc_arena::MutationContext;
@ -129,6 +131,30 @@ fn value_of<'gc>(
Ok(ReturnValue::Immediate(this.into()))
}
/// Implements `Object.registerClass`
pub fn register_class<'gc>(
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let Some(class_name) = args.get(0).cloned() {
let class_name = class_name.coerce_to_string(avm, context)?;
if let Some(Character::MovieClip(movie_clip)) = context
.library
.library_for_movie_mut(context.swf.clone())
.get_character_by_export_name(&class_name)
{
if let Some(constructor) = args.get(1) {
movie_clip.set_avm1_constructor(context.gc_context, Some(constructor.as_object()?));
} else {
movie_clip.set_avm1_constructor(context.gc_context, None);
}
}
}
Ok(Value::Undefined.into())
}
/// Partially construct `Object.prototype`.
///
/// `__proto__` and other cross-linked properties of this object will *not*
@ -259,3 +285,27 @@ pub fn as_set_prop_flags<'gc>(
Ok(Value::Undefined.into())
}
pub fn create_object_object<'gc>(
gc_context: MutationContext<'gc, '_>,
proto: Object<'gc>,
fn_proto: Object<'gc>,
) -> Object<'gc> {
let object_function = FunctionObject::function(
gc_context,
Executable::Native(constructor),
Some(fn_proto),
Some(proto),
);
let mut object = object_function.as_script_object().unwrap();
object.force_set_function(
"registerClass",
register_class,
gc_context,
Attribute::DontEnum | Attribute::DontDelete | Attribute::ReadOnly,
Some(fn_proto),
);
object_function
}

View File

@ -587,7 +587,6 @@ mod tests {
let mut avm = Avm1::new(gc_context, swf_version);
let swf = Arc::new(SwfMovie::empty(swf_version));
let mut root: DisplayObject<'_> = MovieClip::new(swf_version, gc_context).into();
root.post_instantiation(gc_context, root, avm.prototypes().movie_clip);
root.set_depth(gc_context, 0);
let mut levels = BTreeMap::new();
levels.insert(0, root);
@ -620,6 +619,8 @@ mod tests {
load_manager: &mut LoadManager::new(),
};
root.post_instantiation(&mut avm, &mut context, root);
let object = ScriptObject::object(gc_context, Some(avm.prototypes().object)).into();
let globals = avm.global_object_cell();

View File

@ -26,7 +26,6 @@ where
let mut avm = Avm1::new(gc_context, swf_version);
let swf = Arc::new(SwfMovie::empty(swf_version));
let mut root: DisplayObject<'_> = MovieClip::new(swf_version, gc_context).into();
root.post_instantiation(gc_context, root, avm.prototypes().movie_clip);
root.set_depth(gc_context, 0);
let mut levels = BTreeMap::new();
levels.insert(0, root);
@ -58,6 +57,7 @@ where
player: None,
load_manager: &mut LoadManager::new(),
};
root.post_instantiation(&mut avm, &mut context, root);
let globals = avm.global_object_cell();
avm.insert_stack_frame(GcCell::allocate(

View File

@ -1,4 +1,4 @@
use crate::avm1::{Object, TObject, Value};
use crate::avm1::{Avm1, TObject, Value};
use crate::context::{RenderContext, UpdateContext};
use crate::player::NEWEST_PLAYER_VERSION;
use crate::prelude::*;
@ -726,7 +726,7 @@ pub trait TDisplayObject<'gc>: 'gc + Collect + Debug + Into<DisplayObject<'gc>>
}
}
fn run_frame(&mut self, _context: &mut UpdateContext<'_, 'gc, '_>) {}
fn run_frame(&mut self, _avm: &mut Avm1<'gc>, _context: &mut UpdateContext<'_, 'gc, '_>) {}
fn render(&self, _context: &mut RenderContext<'_, 'gc>) {}
fn unload(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
self.set_removed(context.gc_context, true);
@ -823,9 +823,9 @@ pub trait TDisplayObject<'gc>: 'gc + Collect + Debug + Into<DisplayObject<'gc>>
fn post_instantiation(
&mut self,
_gc_context: MutationContext<'gc, '_>,
_avm: &mut Avm1<'gc>,
_context: &mut UpdateContext<'_, 'gc, '_>,
_display_object: DisplayObject<'gc>,
_proto: Object<'gc>,
) {
}

View File

@ -1,5 +1,6 @@
//! Bitmap display object
use crate::avm1::Avm1;
use crate::backend::render::BitmapHandle;
use crate::context::{RenderContext, UpdateContext};
use crate::display_object::{DisplayObjectBase, TDisplayObject};
@ -79,7 +80,7 @@ impl<'gc> TDisplayObject<'gc> for Bitmap<'gc> {
}
}
fn run_frame(&mut self, _context: &mut UpdateContext) {
fn run_frame(&mut self, _avm: &mut Avm1<'gc>, _context: &mut UpdateContext) {
// Noop
}

View File

@ -1,4 +1,4 @@
use crate::avm1::{Object, StageObject, Value};
use crate::avm1::{Avm1, Object, StageObject, Value};
use crate::context::{ActionType, RenderContext, UpdateContext};
use crate::display_object::{DisplayObjectBase, TDisplayObject};
use crate::events::{ButtonEvent, ButtonEventResult, ButtonKeyCode};
@ -79,12 +79,13 @@ impl<'gc> Button<'gc> {
pub fn handle_button_event(
&mut self,
avm: &mut Avm1<'gc>,
context: &mut crate::context::UpdateContext<'_, 'gc, '_>,
event: ButtonEvent,
) {
self.0
.write(context.gc_context)
.handle_button_event((*self).into(), context, event)
.handle_button_event((*self).into(), avm, context, event)
}
pub fn set_sounds(self, gc_context: MutationContext<'gc, '_>, sounds: swf::ButtonSounds) {
@ -128,21 +129,25 @@ impl<'gc> TDisplayObject<'gc> for Button<'gc> {
fn post_instantiation(
&mut self,
gc_context: MutationContext<'gc, '_>,
_avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
display_object: DisplayObject<'gc>,
proto: Object<'gc>,
) {
let mut mc = self.0.write(gc_context);
let mut mc = self.0.write(context.gc_context);
if mc.object.is_none() {
let object = StageObject::for_display_object(gc_context, display_object, Some(proto));
let object = StageObject::for_display_object(
context.gc_context,
display_object,
Some(context.system_prototypes.object),
);
mc.object = Some(object.into());
}
}
fn run_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
fn run_frame(&mut self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>) {
self.0
.write(context.gc_context)
.run_frame((*self).into(), context)
.run_frame((*self).into(), avm, context)
}
fn render(&self, context: &mut RenderContext<'_, 'gc>) {
@ -222,6 +227,7 @@ impl<'gc> ButtonData<'gc> {
fn set_state(
&mut self,
self_display_object: DisplayObject<'gc>,
avm: &mut Avm1<'gc>,
context: &mut crate::context::UpdateContext<'_, 'gc, '_>,
state: ButtonState,
) {
@ -237,8 +243,9 @@ impl<'gc> ButtonData<'gc> {
if let Ok(mut child) = context
.library
.library_for_movie_mut(self.movie())
.instantiate_by_id(record.id, context.gc_context, &context.system_prototypes)
.instantiate_by_id(record.id, context.gc_context)
{
child.post_instantiation(avm, context, child);
child.set_parent(context.gc_context, Some(self_display_object));
child.set_matrix(context.gc_context, &record.matrix.clone().into());
child.set_color_transform(
@ -255,25 +262,24 @@ impl<'gc> ButtonData<'gc> {
fn run_frame(
&mut self,
self_display_object: DisplayObject<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
) {
// TODO: Move this to post_instantiation.
if !self.initialized {
self.initialized = true;
self.set_state(self_display_object, context, ButtonState::Up);
self.set_state(self_display_object, avm, context, ButtonState::Up);
for record in &self.static_data.read().records {
if record.states.contains(&swf::ButtonState::HitTest) {
match context
.library
.library_for_movie_mut(self.static_data.read().swf.clone())
.instantiate_by_id(
record.id,
context.gc_context,
&context.system_prototypes,
) {
.instantiate_by_id(record.id, context.gc_context)
{
Ok(mut child) => {
{
child.post_instantiation(avm, context, child);
child.set_matrix(context.gc_context, &record.matrix.clone().into());
child.set_parent(context.gc_context, Some(self_display_object));
child.set_depth(context.gc_context, record.depth.into());
@ -294,13 +300,14 @@ impl<'gc> ButtonData<'gc> {
}
for child in self.children.values_mut() {
child.run_frame(context);
child.run_frame(avm, context);
}
}
fn handle_button_event(
&mut self,
self_display_object: DisplayObject<'gc>,
avm: &mut Avm1<'gc>,
context: &mut crate::context::UpdateContext<'_, 'gc, '_>,
event: ButtonEvent,
) {
@ -340,7 +347,7 @@ impl<'gc> ButtonData<'gc> {
_ => (),
}
self.set_state(self_display_object, context, new_state);
self.set_state(self_display_object, avm, context, new_state);
}
fn play_sound(

View File

@ -1,6 +1,6 @@
//! `EditText` display object and support code.
use crate::avm1::globals::text_field::attach_virtual_properties;
use crate::avm1::{Object, StageObject, Value};
use crate::avm1::{Avm1, Object, StageObject, Value};
use crate::context::{RenderContext, UpdateContext};
use crate::display_object::{DisplayObjectBase, TDisplayObject};
use crate::font::{Font, Glyph, TextFormat};
@ -419,7 +419,7 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
Some(self.0.read().static_data.swf.clone())
}
fn run_frame(&mut self, _context: &mut UpdateContext) {
fn run_frame(&mut self, _avm: &mut Avm1<'gc>, _context: &mut UpdateContext) {
// Noop
}
@ -429,16 +429,20 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
fn post_instantiation(
&mut self,
gc_context: MutationContext<'gc, '_>,
_avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
display_object: DisplayObject<'gc>,
proto: Object<'gc>,
) {
let mut text = self.0.write(gc_context);
let mut text = self.0.write(context.gc_context);
if text.object.is_none() {
let object =
StageObject::for_display_object(gc_context, display_object, Some(proto)).into();
let object = StageObject::for_display_object(
context.gc_context,
display_object,
Some(context.system_prototypes.text_field),
)
.into();
attach_virtual_properties(gc_context, object);
attach_virtual_properties(context.gc_context, object);
text.object = Some(object);
}

View File

@ -1,3 +1,4 @@
use crate::avm1::Avm1;
use crate::backend::render::ShapeHandle;
use crate::context::{RenderContext, UpdateContext};
use crate::display_object::{DisplayObjectBase, TDisplayObject};
@ -42,7 +43,18 @@ impl<'gc> TDisplayObject<'gc> for Graphic<'gc> {
self.0.read().static_data.bounds.clone()
}
fn run_frame(&mut self, _context: &mut UpdateContext) {
fn world_bounds(&self) -> BoundingBox {
// TODO: Use dirty flags and cache this.
let mut bounds = self.local_bounds();
let mut node = self.parent();
while let Some(display_object) = node {
bounds = bounds.transform(&*display_object.matrix());
node = display_object.parent();
}
bounds
}
fn run_frame(&mut self, _avm: &mut Avm1<'gc>, _context: &mut UpdateContext) {
// Noop
}

View File

@ -1,3 +1,4 @@
use crate::avm1::Avm1;
use crate::backend::render::{RenderBackend, ShapeHandle};
use crate::context::{RenderContext, UpdateContext};
use crate::display_object::{DisplayObjectBase, TDisplayObject};
@ -51,7 +52,7 @@ impl<'gc> TDisplayObject<'gc> for MorphShape<'gc> {
Some(*self)
}
fn run_frame(&mut self, _context: &mut UpdateContext) {
fn run_frame(&mut self, _avm: &mut Avm1<'gc>, _context: &mut UpdateContext) {
// Noop
}

View File

@ -1,5 +1,5 @@
//! `MovieClip` display object and support code.
use crate::avm1::{Object, StageObject, Value};
use crate::avm1::{Avm1, Object, StageObject, TObject, Value};
use crate::backend::audio::AudioStreamHandle;
use crate::character::Character;
use crate::context::{ActionType, RenderContext, UpdateContext};
@ -41,6 +41,7 @@ pub struct MovieClipData<'gc> {
object: Option<Object<'gc>>,
clip_actions: SmallVec<[ClipAction; 2]>,
flags: EnumSet<MovieClipFlags>,
avm1_constructor: Option<Object<'gc>>,
}
impl<'gc> MovieClip<'gc> {
@ -58,6 +59,7 @@ impl<'gc> MovieClip<'gc> {
object: None,
clip_actions: SmallVec::new(),
flags: EnumSet::empty(),
avm1_constructor: None,
},
))
}
@ -89,6 +91,7 @@ impl<'gc> MovieClip<'gc> {
object: None,
clip_actions: SmallVec::new(),
flags: MovieClipFlags::Playing.into(),
avm1_constructor: None,
},
))
}
@ -133,9 +136,9 @@ impl<'gc> MovieClip<'gc> {
self.0.read().playing()
}
pub fn next_frame(self, context: &mut UpdateContext<'_, 'gc, '_>) {
pub fn next_frame(self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>) {
if self.current_frame() < self.total_frames() {
self.goto_frame(context, self.current_frame() + 1, true);
self.goto_frame(avm, context, self.current_frame() + 1, true);
}
}
@ -143,9 +146,9 @@ impl<'gc> MovieClip<'gc> {
self.0.write(context.gc_context).play()
}
pub fn prev_frame(self, context: &mut UpdateContext<'_, 'gc, '_>) {
pub fn prev_frame(self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>) {
if self.current_frame() > 1 {
self.goto_frame(context, self.current_frame() - 1, true);
self.goto_frame(avm, context, self.current_frame() - 1, true);
}
}
@ -157,13 +160,14 @@ impl<'gc> MovieClip<'gc> {
/// `frame` should be 1-based.
pub fn goto_frame(
self,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
frame: FrameNumber,
stop: bool,
) {
self.0
.write(context.gc_context)
.goto_frame(self.into(), context, frame, stop)
.goto_frame(self.into(), avm, context, frame, stop)
}
pub fn current_frame(self) -> FrameNumber {
@ -179,6 +183,14 @@ impl<'gc> MovieClip<'gc> {
self.0.read().static_data.total_frames
}
pub fn set_avm1_constructor(
self,
gc_context: MutationContext<'gc, '_>,
prototype: Option<Object<'gc>>,
) {
self.0.write(gc_context).avm1_constructor = prototype;
}
pub fn frame_label_to_number(self, frame_label: &str) -> Option<FrameNumber> {
// Frame labels are case insensitive.
let label = frame_label.to_ascii_lowercase();
@ -314,10 +326,10 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
Some(self.0.read().movie())
}
fn run_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
fn run_frame(&mut self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>) {
// Children must run first.
for mut child in self.children() {
child.run_frame(context);
child.run_frame(avm, context);
}
// Run my load/enterFrame clip event.
@ -332,7 +344,7 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
// Run my SWF tags.
if mc.playing() {
mc.run_frame_internal((*self).into(), context, true);
mc.run_frame_internal((*self).into(), avm, context, true);
}
if is_load_frame {
@ -385,13 +397,37 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
fn post_instantiation(
&mut self,
gc_context: MutationContext<'gc, '_>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
display_object: DisplayObject<'gc>,
proto: Object<'gc>,
) {
let mut mc = self.0.write(gc_context);
let mut mc = self.0.write(context.gc_context);
if mc.object.is_none() {
let object = StageObject::for_display_object(gc_context, display_object, Some(proto));
if let Some(constructor) = mc.avm1_constructor {
if let Ok(prototype) = constructor
.get("prototype", avm, context)
.and_then(|v| v.resolve(avm, context))
.and_then(|v| v.as_object())
{
let object: Object<'gc> = StageObject::for_display_object(
context.gc_context,
display_object,
Some(prototype),
)
.into();
mc.object = Some(object);
if let Ok(result) = constructor.call(avm, context, object, &[]) {
let _ = result.resolve(avm, context);
}
return;
}
};
let object = StageObject::for_display_object(
context.gc_context,
display_object,
Some(context.system_prototypes.movie_clip),
);
mc.object = Some(object.into());
}
}
@ -427,6 +463,7 @@ unsafe impl<'gc> Collect for MovieClipData<'gc> {
self.base.trace(cc);
self.static_data.trace(cc);
self.object.trace(cc);
self.avm1_constructor.trace(cc);
}
}
@ -521,6 +558,7 @@ impl<'gc> MovieClipData<'gc> {
pub fn goto_frame(
&mut self,
self_display_object: DisplayObject<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
mut frame: FrameNumber,
stop: bool,
@ -538,13 +576,14 @@ impl<'gc> MovieClipData<'gc> {
}
if frame != self.current_frame() {
self.run_goto(self_display_object, context, frame);
self.run_goto(self_display_object, avm, context, frame);
}
}
fn run_frame_internal(
&mut self,
self_display_object: DisplayObject<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
run_display_actions: bool,
) {
@ -555,7 +594,7 @@ impl<'gc> MovieClipData<'gc> {
// Looping acts exactly like a gotoAndPlay(1).
// Specifically, object that existed on frame 1 should not be destroyed
// and recreated.
self.run_goto(self_display_object, context, 1);
self.run_goto(self_display_object, avm, context, 1);
return;
} else {
// Single frame clips do not play.
@ -571,16 +610,16 @@ impl<'gc> MovieClipData<'gc> {
let tag_callback = |reader: &mut _, tag_code, tag_len| match tag_code {
TagCode::DoAction => self.do_action(self_display_object, context, reader, tag_len),
TagCode::PlaceObject if run_display_actions => {
self.place_object(self_display_object, context, reader, tag_len, 1)
self.place_object(self_display_object, avm, context, reader, tag_len, 1)
}
TagCode::PlaceObject2 if run_display_actions => {
self.place_object(self_display_object, context, reader, tag_len, 2)
self.place_object(self_display_object, avm, context, reader, tag_len, 2)
}
TagCode::PlaceObject3 if run_display_actions => {
self.place_object(self_display_object, context, reader, tag_len, 3)
self.place_object(self_display_object, avm, context, reader, tag_len, 3)
}
TagCode::PlaceObject4 if run_display_actions => {
self.place_object(self_display_object, context, reader, tag_len, 4)
self.place_object(self_display_object, avm, context, reader, tag_len, 4)
}
TagCode::RemoveObject if run_display_actions => self.remove_object(context, reader, 1),
TagCode::RemoveObject2 if run_display_actions => self.remove_object(context, reader, 2),
@ -602,9 +641,11 @@ impl<'gc> MovieClipData<'gc> {
}
}
#[allow(clippy::too_many_arguments)]
fn instantiate_child(
&mut self,
self_display_object: DisplayObject<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
id: CharacterId,
depth: Depth,
@ -614,8 +655,10 @@ impl<'gc> MovieClipData<'gc> {
if let Ok(mut child) = context
.library
.library_for_movie_mut(self.movie())
.instantiate_by_id(id, context.gc_context, &context.system_prototypes)
.instantiate_by_id(id, context.gc_context)
{
child.post_instantiation(avm, context, child);
// Remove previous child from children list,
// and add new childonto front of the list.
let prev_child = self.children.insert(depth, child);
@ -635,7 +678,7 @@ impl<'gc> MovieClipData<'gc> {
}
// Run first frame.
child.apply_place_object(context.gc_context, place_object);
child.run_frame(context);
child.run_frame(avm, context);
}
Some(child)
} else {
@ -684,6 +727,7 @@ impl<'gc> MovieClipData<'gc> {
pub fn run_goto(
&mut self,
self_display_object: DisplayObject<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
frame: FrameNumber,
) {
@ -786,6 +830,7 @@ impl<'gc> MovieClipData<'gc> {
// Run the list of goto commands to actually create and update the display objects.
let run_goto_command = |clip: &mut MovieClipData<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
params: &GotoPlaceObject| {
let child_entry = clip.children.get_mut(&params.depth()).copied();
@ -803,6 +848,7 @@ impl<'gc> MovieClipData<'gc> {
_ => {
if let Some(mut child) = clip.instantiate_child(
self_display_object,
avm,
context,
params.id(),
params.depth(),
@ -828,7 +874,7 @@ impl<'gc> MovieClipData<'gc> {
goto_commands
.iter()
.filter(|params| params.frame < frame)
.for_each(|goto| run_goto_command(self, context, goto));
.for_each(|goto| run_goto_command(self, avm, context, goto));
// Next, run the final frame for the parent clip.
// Re-run the final frame without display tags (DoAction, StartSound, etc.)
@ -837,7 +883,7 @@ impl<'gc> MovieClipData<'gc> {
if hit_target_frame {
self.current_frame -= 1;
self.tag_stream_pos = frame_pos;
self.run_frame_internal(self_display_object, context, false);
self.run_frame_internal(self_display_object, avm, context, false);
} else {
self.current_frame = clamped_frame;
}
@ -846,7 +892,7 @@ impl<'gc> MovieClipData<'gc> {
goto_commands
.iter()
.filter(|params| params.frame >= frame)
.for_each(|goto| run_goto_command(self, context, goto));
.for_each(|goto| run_goto_command(self, avm, context, goto));
}
/// Handles a PlaceObject tag when running a goto action.
@ -1816,6 +1862,7 @@ impl<'gc, 'a> MovieClipData<'gc> {
fn place_object(
&mut self,
self_display_object: DisplayObject<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
reader: &mut SwfStream<&'a [u8]>,
tag_len: usize,
@ -1831,6 +1878,7 @@ impl<'gc, 'a> MovieClipData<'gc> {
PlaceObjectAction::Place(id) | PlaceObjectAction::Replace(id) => {
if let Some(child) = self.instantiate_child(
self_display_object,
avm,
context,
id,
place_object.depth.into(),

View File

@ -1,3 +1,4 @@
use crate::avm1::Avm1;
use crate::context::{RenderContext, UpdateContext};
use crate::display_object::{DisplayObjectBase, TDisplayObject};
use crate::prelude::*;
@ -52,7 +53,7 @@ impl<'gc> TDisplayObject<'gc> for Text<'gc> {
Some(self.0.read().static_data.swf.clone())
}
fn run_frame(&mut self, _context: &mut UpdateContext) {
fn run_frame(&mut self, _avm: &mut Avm1<'gc>, _context: &mut UpdateContext) {
// Noop
}

View File

@ -1,5 +1,3 @@
use crate::avm1::globals::SystemPrototypes;
use crate::avm1::Object;
use crate::backend::audio::SoundHandle;
use crate::character::Character;
use crate::display_object::TDisplayObject;
@ -79,14 +77,14 @@ impl<'gc> MovieLibrary<'gc> {
}
/// Instantiates the library item with the given character ID into a display object.
/// The object must then be post-instantiated before being used.
pub fn instantiate_by_id(
&self,
id: CharacterId,
gc_context: MutationContext<'gc, '_>,
prototypes: &SystemPrototypes<'gc>,
) -> Result<DisplayObject<'gc>, Box<dyn std::error::Error>> {
if let Some(character) = self.characters.get(&id) {
self.instantiate_display_object(character, gc_context, prototypes)
self.instantiate_display_object(character, gc_context)
} else {
log::error!("Tried to instantiate non-registered character ID {}", id);
Err("Character id doesn't exist".into())
@ -94,14 +92,14 @@ impl<'gc> MovieLibrary<'gc> {
}
/// Instantiates the library item with the given export name into a display object.
/// The object must then be post-instantiated before being used.
pub fn instantiate_by_export_name(
&self,
export_name: &str,
gc_context: MutationContext<'gc, '_>,
prototypes: &SystemPrototypes<'gc>,
) -> Result<DisplayObject<'gc>, Box<dyn std::error::Error>> {
if let Some(character) = self.export_characters.get(export_name) {
self.instantiate_display_object(character, gc_context, prototypes)
self.instantiate_display_object(character, gc_context)
} else {
log::error!(
"Tried to instantiate non-registered character {}",
@ -112,30 +110,22 @@ impl<'gc> MovieLibrary<'gc> {
}
/// Instantiates the given character into a display object.
/// The object must then be post-instantiated before being used.
fn instantiate_display_object(
&self,
character: &Character<'gc>,
gc_context: MutationContext<'gc, '_>,
prototypes: &SystemPrototypes<'gc>,
) -> Result<DisplayObject<'gc>, Box<dyn std::error::Error>> {
let (mut obj, proto): (DisplayObject<'gc>, Object<'gc>) = match character {
Character::Bitmap(bitmap) => (bitmap.instantiate(gc_context), prototypes.object),
Character::EditText(edit_text) => {
(edit_text.instantiate(gc_context), prototypes.text_field)
match character {
Character::Bitmap(bitmap) => Ok(bitmap.instantiate(gc_context)),
Character::EditText(edit_text) => Ok(edit_text.instantiate(gc_context)),
Character::Graphic(graphic) => Ok(graphic.instantiate(gc_context)),
Character::MorphShape(morph_shape) => Ok(morph_shape.instantiate(gc_context)),
Character::MovieClip(movie_clip) => Ok(movie_clip.instantiate(gc_context)),
Character::Button(button) => Ok(button.instantiate(gc_context)),
Character::Text(text) => Ok(text.instantiate(gc_context)),
_ => Err("Not a DisplayObject".into()),
}
Character::Graphic(graphic) => (graphic.instantiate(gc_context), prototypes.object),
Character::MorphShape(morph_shape) => {
(morph_shape.instantiate(gc_context), prototypes.object)
}
Character::MovieClip(movie_clip) => {
(movie_clip.instantiate(gc_context), prototypes.movie_clip)
}
Character::Button(button) => (button.instantiate(gc_context), prototypes.object),
Character::Text(text) => (text.instantiate(gc_context), prototypes.object),
_ => return Err("Not a DisplayObject".into()),
};
obj.post_instantiation(gc_context, obj, proto);
Ok(obj)
}
pub fn get_font(&self, id: CharacterId) -> Option<Font<'gc>> {

View File

@ -334,7 +334,7 @@ impl<'gc> Loader<'gc> {
.expect("Attempted to load movie into not movie clip");
mc.replace_with_movie(uc.gc_context, Some(movie.clone()));
mc.post_instantiation(uc.gc_context, clip, avm.prototypes().movie_clip);
mc.post_instantiation(avm, uc, clip);
let mut morph_shapes = fnv::FnvHashMap::default();
mc.preload(uc, &mut morph_shapes);

View File

@ -193,9 +193,6 @@ impl Player {
};
let mut library = Library::default();
let root = MovieClip::from_movie(gc_context, movie.clone()).into();
let mut levels = BTreeMap::new();
levels.insert(0, root);
library
.library_for_movie_mut(movie.clone())
@ -205,7 +202,7 @@ impl Player {
gc_context,
GcRootData {
library,
levels,
levels: BTreeMap::new(),
mouse_hovered_object: None,
drag_object: None,
avm: Avm1::new(gc_context, NEWEST_PLAYER_VERSION),
@ -236,14 +233,12 @@ impl Player {
self_reference: None,
};
player.gc_arena.mutate(|gc_context, gc_root| {
let mut root_data = gc_root.0.write(gc_context);
let mc_proto = root_data.avm.prototypes().movie_clip;
for (_i, level) in root_data.levels.iter_mut() {
level.post_instantiation(gc_context, *level, mc_proto);
level.set_depth(gc_context, 0);
}
player.mutate_with_update_context(|avm, context| {
let mut root: DisplayObject =
MovieClip::from_movie(context.gc_context, movie.clone()).into();
root.post_instantiation(avm, context, root);
root.set_depth(context.gc_context, 0);
context.levels.insert(0, root);
});
player.build_matrices();
@ -430,13 +425,13 @@ impl Player {
PlayerEvent::MouseDown { .. } => {
is_mouse_down = true;
needs_render = true;
button.handle_button_event(context, ButtonEvent::Press);
button.handle_button_event(avm, context, ButtonEvent::Press);
}
PlayerEvent::MouseUp { .. } => {
is_mouse_down = false;
needs_render = true;
button.handle_button_event(context, ButtonEvent::Release);
button.handle_button_event(avm, context, ButtonEvent::Release);
}
_ => (),
@ -508,7 +503,7 @@ impl Player {
// RollOut of previous node.
if let Some(node) = cur_hovered {
if let Some(mut button) = node.as_button() {
button.handle_button_event(context, ButtonEvent::RollOut);
button.handle_button_event(avm, context, ButtonEvent::RollOut);
}
}
@ -516,7 +511,7 @@ impl Player {
new_cursor = MouseCursor::Arrow;
if let Some(node) = new_hovered {
if let Some(mut button) = node.as_button() {
button.handle_button_event(context, ButtonEvent::RollOver);
button.handle_button_event(avm, context, ButtonEvent::RollOver);
new_cursor = MouseCursor::Hand;
}
}
@ -563,7 +558,7 @@ impl Player {
}
pub fn run_frame(&mut self) {
self.update(|_avm, update_context| {
self.update(|avm, update_context| {
// TODO: In what order are levels run?
// NOTE: We have to copy all the layer pointers into a separate list
// because level updates can create more levels, which we don't
@ -571,7 +566,7 @@ impl Player {
let levels: Vec<_> = update_context.levels.values().copied().collect();
for mut level in levels {
level.run_frame(update_context);
level.run_frame(avm, update_context);
}
})
}

View File

@ -127,6 +127,7 @@ swf_tests! {
(equals2_swf5, "avm1/equals2_swf5", 1),
(equals2_swf6, "avm1/equals2_swf6", 1),
(equals2_swf7, "avm1/equals2_swf7", 1),
(register_class, "avm1/register_class", 1),
(set_variable_scope, "avm1/set_variable_scope", 1),
(slash_syntax, "avm1/slash_syntax", 2),
(strictequals_swf6, "avm1/strictequals_swf6", 1),

View File

@ -1,15 +1,51 @@
// mc = _root.attachMovie("Clip", "clip", 0)
// clip
_level0.clip
// clip == mc
true
// mc._x
0
// mc._y
0
// mc = _root.attachMovie("BOGUS", "clip", 0)
// mc
undefined
// clip == mc
false
// mc = _root.attachMovie("Clip", "clip", 0, {_x: 100, _y: 50, foo: "foo"})
// clip == mc
true
// mc._x
100
// mc._y
50
// mc.foo
foo
// mc = _root.attachMovie("Clip", "clip", -1)
// clip == mc
true
// mc._x
0
// mc._y
0
// mc2 = _root.attachMovie("Clip", "clip2", 2130690044)
// mc2
_level0.clip2
// mc3 = _root.attachMovie("Clip", "clip2", 2130690045)
// mc3
undefined

View File

@ -0,0 +1,18 @@
Custom movieclip!
// typeof custom_movieclip
movieclip
// custom_movieclip.__proto__ == custom_mc.prototype
true
// custom_movieclip.__proto__ == MovieClip.prototype
false
// custom_movieclip.isCustom()
true
// typeof normal_movieclip
movieclip
// normal_movieclip.__proto__ == custom_mc.prototype
false
// normal_movieclip.__proto__ == MovieClip.prototype
true
// normal_movieclip.isCustom()
undefined

Binary file not shown.

Binary file not shown.