Implement `addFrameScript`.

This commit is contained in:
David Wendt 2020-08-15 18:00:30 -04:00 committed by Mike Welsh
parent 664152d739
commit d991c49774
2 changed files with 106 additions and 16 deletions

View File

@ -4,9 +4,11 @@ use crate::avm2::activation::Activation;
use crate::avm2::class::Class; use crate::avm2::class::Class;
use crate::avm2::method::Method; use crate::avm2::method::Method;
use crate::avm2::names::{Namespace, QName}; use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::Object; use crate::avm2::object::{Object, TObject};
use crate::avm2::traits::Trait;
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use crate::display_object::TDisplayObject;
use gc_arena::{GcCell, MutationContext}; use gc_arena::{GcCell, MutationContext};
/// Implements `flash.display.MovieClip`'s instance constructor. /// Implements `flash.display.MovieClip`'s instance constructor.
@ -27,13 +29,44 @@ pub fn class_init<'gc>(
Ok(Value::Undefined) Ok(Value::Undefined)
} }
/// Implements `addFrameScript`, an undocumented method of `MovieClip` used to
/// specify what methods of a clip's class run on which frames.
pub fn add_frame_script<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
if let Some(mc) = this
.and_then(|o| o.as_display_object())
.and_then(|dobj| dobj.as_movie_clip())
{
for (frame_id, callable) in args.chunks_exact(2).map(|s| (s[0].clone(), s[1].clone())) {
let frame_id = frame_id.coerce_to_u32(activation)? as u16;
let callable = callable.coerce_to_object(activation)?;
mc.register_frame_script(frame_id, callable, &mut activation.context);
}
} else {
log::error!("Attempted to add frame scripts to non-MovieClip this!");
}
Ok(Value::Undefined)
}
/// Construct `MovieClip`'s class. /// Construct `MovieClip`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
Class::new( let class = Class::new(
QName::new(Namespace::package("flash.display"), "MovieClip"), QName::new(Namespace::package("flash.display"), "MovieClip"),
Some(QName::new(Namespace::package("flash.display"), "Sprite").into()), Some(QName::new(Namespace::package("flash.display"), "Sprite").into()),
Method::from_builtin(instance_init), Method::from_builtin(instance_init),
Method::from_builtin(class_init), Method::from_builtin(class_init),
mc, mc,
) );
class.write(mc).define_instance_trait(Trait::from_method(
QName::new(Namespace::package(""), "addFrameScript"),
Method::from_builtin(add_frame_script),
));
class
} }

View File

@ -54,12 +54,27 @@ pub struct MovieClipData<'gc> {
children: BTreeMap<Depth, DisplayObject<'gc>>, children: BTreeMap<Depth, DisplayObject<'gc>>,
object: Option<AvmObject<'gc>>, object: Option<AvmObject<'gc>>,
clip_actions: Vec<ClipAction>, clip_actions: Vec<ClipAction>,
frame_scripts: Vec<Avm2FrameScript<'gc>>,
has_button_clip_event: bool, has_button_clip_event: bool,
flags: EnumSet<MovieClipFlags>, flags: EnumSet<MovieClipFlags>,
avm_constructor: Option<AvmObject<'gc>>, avm_constructor: Option<AvmObject<'gc>>,
drawing: Drawing, drawing: Drawing,
} }
unsafe impl<'gc> Collect for MovieClipData<'gc> {
#[inline]
fn trace(&self, cc: gc_arena::CollectionContext) {
for child in self.children.values() {
child.trace(cc);
}
self.base.trace(cc);
self.static_data.trace(cc);
self.object.trace(cc);
self.avm_constructor.trace(cc);
self.frame_scripts.trace(cc);
}
}
impl<'gc> MovieClip<'gc> { impl<'gc> MovieClip<'gc> {
#[allow(dead_code)] #[allow(dead_code)]
pub fn new(swf: SwfSlice, gc_context: MutationContext<'gc, '_>) -> Self { pub fn new(swf: SwfSlice, gc_context: MutationContext<'gc, '_>) -> Self {
@ -74,6 +89,7 @@ impl<'gc> MovieClip<'gc> {
children: BTreeMap::new(), children: BTreeMap::new(),
object: None, object: None,
clip_actions: Vec::new(), clip_actions: Vec::new(),
frame_scripts: Vec::new(),
has_button_clip_event: false, has_button_clip_event: false,
flags: EnumSet::empty(), flags: EnumSet::empty(),
avm_constructor: None, avm_constructor: None,
@ -108,6 +124,7 @@ impl<'gc> MovieClip<'gc> {
children: BTreeMap::new(), children: BTreeMap::new(),
object: None, object: None,
clip_actions: Vec::new(), clip_actions: Vec::new(),
frame_scripts: Vec::new(),
has_button_clip_event: false, has_button_clip_event: false,
flags: MovieClipFlags::Playing.into(), flags: MovieClipFlags::Playing.into(),
avm_constructor: None, avm_constructor: None,
@ -1276,6 +1293,47 @@ impl<'gc> MovieClip<'gc> {
} }
} }
} }
pub fn register_frame_script(
self,
frame_id: FrameNumber,
callable: Avm2Object<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
) {
let mut write = self.0.write(context.gc_context);
write
.frame_scripts
.push(Avm2FrameScript { frame_id, callable });
}
fn run_frame_scripts(self, frame_id: FrameNumber, context: &mut UpdateContext<'_, 'gc, '_>) {
let mut index = 0;
let mut read = self.0.read();
let avm2_object = read.object.and_then(|o| o.as_avm2_object().ok());
if let Some(avm2_object) = avm2_object {
while let Some(fs) = read.frame_scripts.get(index) {
if fs.frame_id == frame_id {
let callable = fs.callable;
drop(read);
let mut activation = Avm2Activation::from_nothing(context.reborrow());
if let Err(e) = callable.call(Some(avm2_object), &[], &mut activation, None) {
log::error!("Error in script on frame {}: {}", frame_id, e);
}
read = self.0.read();
}
index += 1;
}
} else {
log::error!("Attempted to run AVM2 frame scripts on an AVM1 MovieClip.");
}
}
} }
impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> { impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
@ -1322,6 +1380,18 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
ClipEvent::Load, ClipEvent::Load,
); );
} }
if self
.0
.read()
.object
.map(|o| o.is_avm2_object())
.unwrap_or(false)
{
let frame_id = self.0.read().current_frame;
self.run_frame_scripts(frame_id, context);
}
} }
fn render(&self, context: &mut RenderContext<'_, 'gc>) { fn render(&self, context: &mut RenderContext<'_, 'gc>) {
@ -1529,19 +1599,6 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
} }
} }
unsafe impl<'gc> Collect for MovieClipData<'gc> {
#[inline]
fn trace(&self, cc: gc_arena::CollectionContext) {
for child in self.children.values() {
child.trace(cc);
}
self.base.trace(cc);
self.static_data.trace(cc);
self.object.trace(cc);
self.avm_constructor.trace(cc);
}
}
impl<'gc> MovieClipData<'gc> { impl<'gc> MovieClipData<'gc> {
/// Replace the current MovieClipData with a completely new SwfMovie. /// Replace the current MovieClipData with a completely new SwfMovie.
/// ///