avm2: Implement `MovieClip.scenes`.

This commit is contained in:
David Wendt 2020-09-18 23:09:27 -04:00 committed by Mike Welsh
parent c682868205
commit 8152400a39
7 changed files with 218 additions and 53 deletions

View File

@ -11,7 +11,7 @@ use crate::avm2::string::AvmString;
use crate::avm2::traits::Trait;
use crate::avm2::value::Value;
use crate::avm2::Error;
use crate::display_object::{MovieClip, TDisplayObject};
use crate::display_object::{MovieClip, Scene, TDisplayObject};
use gc_arena::{GcCell, MutationContext};
/// Implements `flash.display.MovieClip`'s instance constructor.
@ -66,8 +66,13 @@ pub fn current_frame<'gc>(
.and_then(|o| o.as_display_object())
.and_then(|dobj| dobj.as_movie_clip())
{
if let Some((_scene, scene_basis, _length)) = mc.current_scene() {
return Ok(((mc.current_frame() + 1) - scene_basis).into());
if let Some(Scene {
name: _,
start,
length: _,
}) = mc.current_scene()
{
return Ok(((mc.current_frame() + 1) - start).into());
} else {
return Ok(mc.current_frame().into());
}
@ -122,16 +127,24 @@ pub fn current_label<'gc>(
Ok(Value::Undefined)
}
/// Given a scene, produce it's name, length, and a list of frame labels.
///
/// The intended purpose of this output is to be sent directly into the
/// constructor of `flash.display.Scene`.
fn labels_for_scene<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
mc: MovieClip<'gc>,
scene: &Scene,
) -> Result<(String, u16, Object<'gc>), Error> {
let (scene_name, scene_start, scene_length) =
mc.current_scene().unwrap_or(("".to_string(), 0, u16::MAX));
let Scene {
name: scene_name,
start: scene_start,
length: scene_length,
} = scene;
let mut frame_labels = Vec::new();
let frame_label_proto = activation.context.avm2.prototypes().framelabel;
for (name, frame) in mc.labels_in_range(scene_start, scene_start + scene_length) {
for (name, frame) in mc.labels_in_range(*scene_start, scene_start + scene_length) {
let name: Value<'gc> = AvmString::new(activation.context.gc_context, name).into();
let local_frame = frame - scene_start + 1;
let args = [name, local_frame.into()];
@ -143,8 +156,8 @@ fn labels_for_scene<'gc>(
}
Ok((
scene_name,
scene_length,
scene_name.to_string(),
*scene_length,
ArrayObject::from_array(
ArrayStorage::from_storage(frame_labels),
activation.context.avm2.prototypes().array,
@ -163,7 +176,8 @@ pub fn current_labels<'gc>(
.and_then(|o| o.as_display_object())
.and_then(|dobj| dobj.as_movie_clip())
{
return Ok(labels_for_scene(activation, mc)?.2.into());
let scene = mc.current_scene().unwrap_or_else(Default::default);
return Ok(labels_for_scene(activation, mc, &scene)?.2.into());
}
Ok(Value::Undefined)
@ -179,7 +193,8 @@ pub fn current_scene<'gc>(
.and_then(|o| o.as_display_object())
.and_then(|dobj| dobj.as_movie_clip())
{
let (scene_name, scene_length, scene_labels) = labels_for_scene(activation, mc)?;
let scene = mc.current_scene().unwrap_or_else(Default::default);
let (scene_name, scene_length, scene_labels) = labels_for_scene(activation, mc, &scene)?;
let scene_proto = activation.context.avm2.prototypes().scene;
let args = [
AvmString::new(activation.context.gc_context, scene_name).into(),
@ -197,6 +212,46 @@ pub fn current_scene<'gc>(
Ok(Value::Undefined)
}
/// Implements `scenes`.
pub fn scenes<'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())
{
let mut scene_objects = Vec::new();
for scene in mc.scenes() {
let (scene_name, scene_length, scene_labels) =
labels_for_scene(activation, mc, &scene)?;
let scene_proto = activation.context.avm2.prototypes().scene;
let args = [
AvmString::new(activation.context.gc_context, scene_name).into(),
scene_labels.into(),
scene_length.into(),
];
let scene = scene_proto.construct(activation, &args)?;
scene::instance_init(activation, Some(scene), &args)?;
scene_objects.push(Some(scene.into()));
}
return Ok(ArrayObject::from_array(
ArrayStorage::from_storage(scene_objects),
activation.context.avm2.prototypes().array,
activation.context.gc_context,
)
.into());
}
Ok(Value::Undefined)
}
/// Implements `framesLoaded`.
pub fn frames_loaded<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
@ -398,8 +453,13 @@ pub fn prev_scene<'gc>(
.and_then(|o| o.as_display_object())
.and_then(|dobj| dobj.as_movie_clip())
{
if let Some((_scene, target_frame, _length)) = mc.previous_scene() {
mc.goto_frame(&mut activation.context, target_frame, false);
if let Some(Scene {
name: _,
start,
length: _,
}) = mc.previous_scene()
{
mc.goto_frame(&mut activation.context, start, false);
}
}
@ -416,8 +476,13 @@ pub fn next_scene<'gc>(
.and_then(|o| o.as_display_object())
.and_then(|dobj| dobj.as_movie_clip())
{
if let Some((_scene, target_frame, _length)) = mc.next_scene() {
mc.goto_frame(&mut activation.context, target_frame, false);
if let Some(Scene {
name: _,
start,
length: _,
}) = mc.next_scene()
{
mc.goto_frame(&mut activation.context, start, false);
}
}
@ -466,6 +531,11 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
Method::from_builtin(current_scene),
));
write.define_instance_trait(Trait::from_getter(
QName::new(Namespace::package(""), "scenes"),
Method::from_builtin(scenes),
));
write.define_instance_trait(Trait::from_getter(
QName::new(Namespace::package(""), "framesLoaded"),
Method::from_builtin(frames_loaded),

View File

@ -30,7 +30,7 @@ pub use button::Button;
pub use edit_text::{AutoSizeMode, EditText};
pub use graphic::Graphic;
pub use morph_shape::{MorphShape, MorphShapeStatic};
pub use movie_clip::MovieClip;
pub use movie_clip::{MovieClip, Scene};
pub use text::Text;
#[derive(Clone, Debug)]

View File

@ -554,9 +554,6 @@ impl<'gc> MovieClip<'gc> {
static_data: &mut MovieClipStatic,
) -> DecodeResult {
let mut sfl_data = reader.read_define_scene_and_frame_label_data()?;
sfl_data
.frame_labels
.sort_unstable_by(|s1, s2| s1.frame_num.cmp(&s2.frame_num));
sfl_data
.scenes
.sort_unstable_by(|s1, s2| s1.frame_num.cmp(&s2.frame_num));
@ -640,60 +637,136 @@ impl<'gc> MovieClip<'gc> {
self.0.read().current_frame
}
pub fn current_scene(self) -> Option<(String, FrameNumber, FrameNumber)> {
/// Return the current scene.
pub fn current_scene(self) -> Option<Scene> {
let current_frame = self.0.read().current_frame();
self.filter_scenes(|best, (_scene, frame, _end)| {
frame <= current_frame && best.map(|v| frame >= v.1).unwrap_or(true)
})
self.filter_scenes(
|best,
Scene {
name: _,
start,
length: _,
}| {
*start <= current_frame
&& best
.map(
|Scene {
name: _,
start: best_start,
length: _,
}| start >= best_start,
)
.unwrap_or(true)
},
)
}
pub fn previous_scene(self) -> Option<(String, FrameNumber, FrameNumber)> {
/// Return the previous scene.
pub fn previous_scene(self) -> Option<Scene> {
let current_frame = self
.current_scene()
.map(|v| v.1)
.map(
|Scene {
name: _,
start,
length: _,
}| start,
)
.unwrap_or_else(|| self.current_frame());
self.filter_scenes(|best, (_scene, frame, _end)| {
frame < current_frame && best.map(|v| frame >= v.1).unwrap_or(true)
})
self.filter_scenes(
|best,
Scene {
name: _,
start,
length: _,
}| {
*start < current_frame
&& best
.map(
|Scene {
name: _,
start: best_start,
length: _,
}| start >= best_start,
)
.unwrap_or(true)
},
)
}
pub fn next_scene(self) -> Option<(String, FrameNumber, FrameNumber)> {
/// Return the next scene.
pub fn next_scene(self) -> Option<Scene> {
let current_frame = self.0.read().current_frame();
self.filter_scenes(|best, (_scene, frame, _end)| {
frame > current_frame && best.map(|v| frame <= v.1).unwrap_or(true)
})
self.filter_scenes(
|best,
Scene {
name: _,
start,
length: _,
}| {
*start > current_frame
&& best
.map(
|Scene {
name: _,
start: best_start,
length: _,
}| start <= best_start,
)
.unwrap_or(true)
},
)
}
pub fn filter_scenes<F>(self, mut cond: F) -> Option<(String, FrameNumber, FrameNumber)>
/// Return all scenes in the movie.
///
/// Scenes will be sorted in playback order.
pub fn scenes(self) -> Vec<Scene> {
let read = self.0.read();
let mut out = Vec::new();
for (_, scene) in read.static_data.scene_labels.iter() {
out.push(scene.clone());
}
out.sort_unstable_by(
|Scene {
name: _,
start: a,
length: _,
},
Scene {
name: _,
start: b,
length: _,
}| a.cmp(b),
);
out
}
/// Scan through the list of scenes and yield the best one, if available,
/// according to a given criterion function.
fn filter_scenes<F>(self, mut cond: F) -> Option<Scene>
where
F: FnMut(
Option<(&str, FrameNumber, FrameNumber)>,
(&str, FrameNumber, FrameNumber),
) -> bool,
F: FnMut(Option<&Scene>, &Scene) -> bool,
{
let read = self.0.read();
let mut best: Option<(&str, FrameNumber, FrameNumber)> = None;
let mut best: Option<&Scene> = None;
for (
_,
Scene {
name,
start,
length,
},
) in read.static_data.scene_labels.iter()
{
if cond(best, (name, *start, *length)) {
best = Some((name, *start, *length));
for (_, scene) in read.static_data.scene_labels.iter() {
if cond(best, scene) {
best = Some(scene);
}
}
best.map(|(s, fnum, len)| (s.to_string(), fnum, len))
best.cloned()
}
/// Yield the current frame label as a tuple of string and frame number.
pub fn current_label(self) -> Option<(String, FrameNumber)> {
let read = self.0.read();
let current_frame = read.current_frame();
@ -2863,10 +2936,20 @@ impl<'gc, 'a> MovieClip<'gc> {
}
#[derive(Clone)]
struct Scene {
name: String,
start: FrameNumber,
length: FrameNumber,
pub struct Scene {
pub name: String,
pub start: FrameNumber,
pub length: FrameNumber,
}
impl Default for Scene {
fn default() -> Self {
Scene {
name: "".to_string(),
start: 0,
length: u16::MAX,
}
}
}
/// Static data shared between all instances of a movie clip.

View File

@ -397,6 +397,7 @@ swf_tests! {
(as3_movieclip_currentlabels, "avm2/movieclip_currentlabels", 5),
(as3_scene_constr, "avm2/scene_constr", 5),
(as3_movieclip_currentscene, "avm2/movieclip_currentscene", 5),
(as3_movieclip_scenes, "avm2/movieclip_scenes", 5),
}
// TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough.

View File

@ -0,0 +1,11 @@
//(contents of this.scenes)
Scene 1
3
1
frame1
3
frame3
Scene 2
1
1
frame4

Binary file not shown.

Binary file not shown.