core: Improve execution order of AS
This commit is contained in:
parent
666075c651
commit
a4bed6c643
|
@ -62,6 +62,7 @@ pub struct ActionContext<'a, 'gc, 'gc_context> {
|
||||||
pub target_path: Value<'gc>,
|
pub target_path: Value<'gc>,
|
||||||
|
|
||||||
pub rng: &'a mut SmallRng,
|
pub rng: &'a mut SmallRng,
|
||||||
|
pub action_queue: &'a mut crate::player::ActionQueue<'gc>,
|
||||||
pub audio: &'a mut dyn crate::backend::audio::AudioBackend,
|
pub audio: &'a mut dyn crate::backend::audio::AudioBackend,
|
||||||
pub navigator: &'a mut dyn crate::backend::navigator::NavigatorBackend,
|
pub navigator: &'a mut dyn crate::backend::navigator::NavigatorBackend,
|
||||||
}
|
}
|
||||||
|
@ -1130,7 +1131,12 @@ impl<'gc> Avm1<'gc> {
|
||||||
let mut display_object = clip.write(context.gc_context);
|
let mut display_object = clip.write(context.gc_context);
|
||||||
if let Some(clip) = display_object.as_movie_clip_mut() {
|
if let Some(clip) = display_object.as_movie_clip_mut() {
|
||||||
// The frame on the stack is 0-based, not 1-based.
|
// The frame on the stack is 0-based, not 1-based.
|
||||||
clip.goto_frame(frame + 1, true);
|
clip.goto_frame(
|
||||||
|
context.active_clip,
|
||||||
|
&mut context.action_queue,
|
||||||
|
frame + 1,
|
||||||
|
true,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
log::error!("GotoFrame failed: Target is not a MovieClip");
|
log::error!("GotoFrame failed: Target is not a MovieClip");
|
||||||
}
|
}
|
||||||
|
@ -1154,11 +1160,21 @@ impl<'gc> Avm1<'gc> {
|
||||||
match self.pop()? {
|
match self.pop()? {
|
||||||
Value::Number(frame) => {
|
Value::Number(frame) => {
|
||||||
// The frame on the stack is 1-based, not 0-based.
|
// The frame on the stack is 1-based, not 0-based.
|
||||||
clip.goto_frame(scene_offset + (frame as u16), !set_playing)
|
clip.goto_frame(
|
||||||
|
context.active_clip,
|
||||||
|
&mut context.action_queue,
|
||||||
|
scene_offset + (frame as u16),
|
||||||
|
!set_playing,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Value::String(frame_label) => {
|
Value::String(frame_label) => {
|
||||||
if let Some(frame) = clip.frame_label_to_number(&frame_label) {
|
if let Some(frame) = clip.frame_label_to_number(&frame_label) {
|
||||||
clip.goto_frame(scene_offset + frame, !set_playing)
|
clip.goto_frame(
|
||||||
|
context.active_clip,
|
||||||
|
&mut context.action_queue,
|
||||||
|
scene_offset + frame,
|
||||||
|
!set_playing,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"GotoFrame2: MovieClip {} does not contain frame label '{}'",
|
"GotoFrame2: MovieClip {} does not contain frame label '{}'",
|
||||||
|
@ -1183,7 +1199,7 @@ impl<'gc> Avm1<'gc> {
|
||||||
let mut display_object = clip.write(context.gc_context);
|
let mut display_object = clip.write(context.gc_context);
|
||||||
if let Some(clip) = display_object.as_movie_clip_mut() {
|
if let Some(clip) = display_object.as_movie_clip_mut() {
|
||||||
if let Some(frame) = clip.frame_label_to_number(label) {
|
if let Some(frame) = clip.frame_label_to_number(label) {
|
||||||
clip.goto_frame(frame, true);
|
clip.goto_frame(context.active_clip, &mut context.action_queue, frame, true);
|
||||||
} else {
|
} else {
|
||||||
log::warn!("GoToLabel: Frame label '{}' not found", label);
|
log::warn!("GoToLabel: Frame label '{}' not found", label);
|
||||||
}
|
}
|
||||||
|
@ -1340,7 +1356,7 @@ impl<'gc> Avm1<'gc> {
|
||||||
if let Some(clip) = context.target_clip {
|
if let Some(clip) = context.target_clip {
|
||||||
let mut display_object = clip.write(context.gc_context);
|
let mut display_object = clip.write(context.gc_context);
|
||||||
if let Some(clip) = display_object.as_movie_clip_mut() {
|
if let Some(clip) = display_object.as_movie_clip_mut() {
|
||||||
clip.next_frame();
|
clip.next_frame(context.active_clip, context.action_queue);
|
||||||
} else {
|
} else {
|
||||||
log::warn!("NextFrame: Target is not a MovieClip");
|
log::warn!("NextFrame: Target is not a MovieClip");
|
||||||
}
|
}
|
||||||
|
@ -1397,7 +1413,7 @@ impl<'gc> Avm1<'gc> {
|
||||||
if let Some(clip) = context.target_clip {
|
if let Some(clip) = context.target_clip {
|
||||||
let mut display_object = clip.write(context.gc_context);
|
let mut display_object = clip.write(context.gc_context);
|
||||||
if let Some(clip) = display_object.as_movie_clip_mut() {
|
if let Some(clip) = display_object.as_movie_clip_mut() {
|
||||||
clip.prev_frame();
|
clip.prev_frame(context.active_clip, context.action_queue);
|
||||||
} else {
|
} else {
|
||||||
log::warn!("PrevFrame: Target is not a MovieClip");
|
log::warn!("PrevFrame: Target is not a MovieClip");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::avm1::object::{Attribute::*, Object};
|
use crate::avm1::object::{Attribute::*, Object};
|
||||||
use crate::avm1::{ActionContext, Avm1, Value};
|
use crate::avm1::{ActionContext, Avm1, Value};
|
||||||
|
use crate::display_object::DisplayNode;
|
||||||
use crate::movie_clip::MovieClip;
|
use crate::movie_clip::MovieClip;
|
||||||
use enumset::EnumSet;
|
use enumset::EnumSet;
|
||||||
use gc_arena::{GcCell, MutationContext};
|
use gc_arena::{GcCell, MutationContext};
|
||||||
|
@ -29,14 +30,14 @@ macro_rules! with_movie_clip_mut {
|
||||||
$(
|
$(
|
||||||
$object.force_set_function(
|
$object.force_set_function(
|
||||||
$name,
|
$name,
|
||||||
|_avm, context, this, args| -> Value<'gc> {
|
|_avm, context: &mut ActionContext<'_, 'gc, '_>, this, args| -> Value<'gc> {
|
||||||
if let Some(display_object) = this.read().display_node() {
|
if let Some(display_object) = this.read().display_node() {
|
||||||
if let Some(movie_clip) = display_object.write(context.gc_context).as_movie_clip_mut() {
|
if let Some(movie_clip) = display_object.write(context.gc_context).as_movie_clip_mut() {
|
||||||
return $fn(movie_clip, args);
|
return $fn(movie_clip, context, display_object, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Undefined
|
Value::Undefined
|
||||||
},
|
} as crate::avm1::function::NativeFunction<'gc>,
|
||||||
$gc_context,
|
$gc_context,
|
||||||
DontDelete | ReadOnly | DontEnum,
|
DontDelete | ReadOnly | DontEnum,
|
||||||
);
|
);
|
||||||
|
@ -82,19 +83,19 @@ pub fn create_movie_object<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<
|
||||||
with_movie_clip_mut!(
|
with_movie_clip_mut!(
|
||||||
gc_context,
|
gc_context,
|
||||||
object,
|
object,
|
||||||
"nextFrame" => |movie_clip: &mut MovieClip, _args| {
|
"nextFrame" => |movie_clip: &mut MovieClip<'gc>, context: &mut ActionContext<'_, 'gc, '_>, cell: DisplayNode<'gc>, _args| {
|
||||||
movie_clip.next_frame();
|
movie_clip.next_frame(cell, &mut context.action_queue);
|
||||||
Value::Undefined
|
Value::Undefined
|
||||||
},
|
},
|
||||||
"prevFrame" => |movie_clip: &mut MovieClip, _args| {
|
"prevFrame" => |movie_clip: &mut MovieClip<'gc>, context: &mut ActionContext<'_, 'gc, '_>, cell: DisplayNode<'gc>, _args| {
|
||||||
movie_clip.prev_frame();
|
movie_clip.prev_frame(cell, &mut context.action_queue);
|
||||||
Value::Undefined
|
Value::Undefined
|
||||||
},
|
},
|
||||||
"play" => |movie_clip: &mut MovieClip, _args| {
|
"play" => |movie_clip: &mut MovieClip<'gc>, _context: &mut ActionContext<'_, 'gc, '_>, _cell: DisplayNode<'gc>, _args| {
|
||||||
movie_clip.play();
|
movie_clip.play();
|
||||||
Value::Undefined
|
Value::Undefined
|
||||||
},
|
},
|
||||||
"stop" => |movie_clip: &mut MovieClip, _args| {
|
"stop" => |movie_clip: &mut MovieClip<'gc>, _context: &mut ActionContext<'_, 'gc, '_>, _cell: DisplayNode<'gc>, _args| {
|
||||||
movie_clip.stop();
|
movie_clip.stop();
|
||||||
Value::Undefined
|
Value::Undefined
|
||||||
}
|
}
|
||||||
|
@ -103,11 +104,11 @@ pub fn create_movie_object<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<
|
||||||
with_movie_clip!(
|
with_movie_clip!(
|
||||||
gc_context,
|
gc_context,
|
||||||
object,
|
object,
|
||||||
"getBytesLoaded" => |_movie_clip: &MovieClip, _args| {
|
"getBytesLoaded" => |_movie_clip: &MovieClip<'gc>, _args| {
|
||||||
// TODO find a correct value
|
// TODO find a correct value
|
||||||
Value::Number(1.0)
|
Value::Number(1.0)
|
||||||
},
|
},
|
||||||
"getBytesTotal" => |_movie_clip: &MovieClip, _args| {
|
"getBytesTotal" => |_movie_clip: &MovieClip<'gc>, _args| {
|
||||||
// TODO find a correct value
|
// TODO find a correct value
|
||||||
Value::Number(1.0)
|
Value::Number(1.0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -401,6 +401,7 @@ mod tests {
|
||||||
target_clip: Some(root),
|
target_clip: Some(root),
|
||||||
target_path: Value::Undefined,
|
target_path: Value::Undefined,
|
||||||
rng: &mut SmallRng::from_seed([0u8; 16]),
|
rng: &mut SmallRng::from_seed([0u8; 16]),
|
||||||
|
action_queue: &mut crate::player::ActionQueue::new(),
|
||||||
audio: &mut NullAudioBackend::new(),
|
audio: &mut NullAudioBackend::new(),
|
||||||
navigator: &mut NullNavigatorBackend::new(),
|
navigator: &mut NullNavigatorBackend::new(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,6 +30,7 @@ where
|
||||||
target_path: Value::Undefined,
|
target_path: Value::Undefined,
|
||||||
rng: &mut SmallRng::from_seed([0u8; 16]),
|
rng: &mut SmallRng::from_seed([0u8; 16]),
|
||||||
audio: &mut NullAudioBackend::new(),
|
audio: &mut NullAudioBackend::new(),
|
||||||
|
action_queue: &mut crate::player::ActionQueue::new(),
|
||||||
navigator: &mut NullNavigatorBackend::new(),
|
navigator: &mut NullNavigatorBackend::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -137,7 +137,9 @@ impl<'gc> Button<'gc> {
|
||||||
for action in &self.static_data.actions {
|
for action in &self.static_data.actions {
|
||||||
if action.condition == condition && action.key_code == key_code {
|
if action.condition == condition && action.key_code == key_code {
|
||||||
// Note that AVM1 buttons run actions relative to their parent, not themselves.
|
// Note that AVM1 buttons run actions relative to their parent, not themselves.
|
||||||
context.actions.push((parent, action.action_data.clone()));
|
context
|
||||||
|
.action_queue
|
||||||
|
.queue_actions(parent, action.action_data.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,13 +192,6 @@ impl<'gc> DisplayObject<'gc> for Button<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_post_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
|
||||||
for child in self.children.values_mut() {
|
|
||||||
context.active_clip = *child;
|
|
||||||
child.write(context.gc_context).run_post_frame(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&self, context: &mut RenderContext<'_, 'gc>) {
|
fn render(&self, context: &mut RenderContext<'_, 'gc>) {
|
||||||
context.transform_stack.push(self.transform());
|
context.transform_stack.push(self.transform());
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,20 @@ pub struct DisplayObjectBase<'gc> {
|
||||||
transform: Transform,
|
transform: Transform,
|
||||||
name: String,
|
name: String,
|
||||||
clip_depth: Depth,
|
clip_depth: Depth,
|
||||||
|
|
||||||
|
/// The first child of this display object in order of execution.
|
||||||
|
/// This is differen than render order.
|
||||||
|
first_child: Option<DisplayNode<'gc>>,
|
||||||
|
|
||||||
|
/// The previous sibling of this display object in order of execution.
|
||||||
|
prev_sibling: Option<DisplayNode<'gc>>,
|
||||||
|
|
||||||
|
/// The next sibling of this display object in order of execution.
|
||||||
|
next_sibling: Option<DisplayNode<'gc>>,
|
||||||
|
|
||||||
|
/// Whether this child has been removed from the display list.
|
||||||
|
/// Necessary in AVM1 to throw away queued actions from removed movie clips.
|
||||||
|
removed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> Default for DisplayObjectBase<'gc> {
|
impl<'gc> Default for DisplayObjectBase<'gc> {
|
||||||
|
@ -25,6 +39,10 @@ impl<'gc> Default for DisplayObjectBase<'gc> {
|
||||||
transform: Default::default(),
|
transform: Default::default(),
|
||||||
name: Default::default(),
|
name: Default::default(),
|
||||||
clip_depth: Default::default(),
|
clip_depth: Default::default(),
|
||||||
|
first_child: None,
|
||||||
|
prev_sibling: None,
|
||||||
|
next_sibling: None,
|
||||||
|
removed: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,6 +97,30 @@ impl<'gc> DisplayObject<'gc> for DisplayObjectBase<'gc> {
|
||||||
fn set_parent(&mut self, parent: Option<DisplayNode<'gc>>) {
|
fn set_parent(&mut self, parent: Option<DisplayNode<'gc>>) {
|
||||||
self.parent = parent;
|
self.parent = parent;
|
||||||
}
|
}
|
||||||
|
fn first_child(&self) -> Option<DisplayNode<'gc>> {
|
||||||
|
self.first_child
|
||||||
|
}
|
||||||
|
fn set_first_child(&mut self, node: Option<DisplayNode<'gc>>) {
|
||||||
|
self.first_child = node;
|
||||||
|
}
|
||||||
|
fn prev_sibling(&self) -> Option<DisplayNode<'gc>> {
|
||||||
|
self.prev_sibling
|
||||||
|
}
|
||||||
|
fn set_prev_sibling(&mut self, node: Option<DisplayNode<'gc>>) {
|
||||||
|
self.prev_sibling = node;
|
||||||
|
}
|
||||||
|
fn next_sibling(&self) -> Option<DisplayNode<'gc>> {
|
||||||
|
self.next_sibling
|
||||||
|
}
|
||||||
|
fn set_next_sibling(&mut self, node: Option<DisplayNode<'gc>>) {
|
||||||
|
self.next_sibling = node;
|
||||||
|
}
|
||||||
|
fn removed(&self) -> bool {
|
||||||
|
self.removed
|
||||||
|
}
|
||||||
|
fn set_removed(&mut self, removed: bool) {
|
||||||
|
self.removed = removed;
|
||||||
|
}
|
||||||
fn box_clone(&self) -> Box<dyn DisplayObject<'gc>> {
|
fn box_clone(&self) -> Box<dyn DisplayObject<'gc>> {
|
||||||
Box::new(self.clone())
|
Box::new(self.clone())
|
||||||
}
|
}
|
||||||
|
@ -109,9 +151,22 @@ pub trait DisplayObject<'gc>: 'gc + Collect + Debug {
|
||||||
fn set_clip_depth(&mut self, depth: Depth);
|
fn set_clip_depth(&mut self, depth: Depth);
|
||||||
fn parent(&self) -> Option<DisplayNode<'gc>>;
|
fn parent(&self) -> Option<DisplayNode<'gc>>;
|
||||||
fn set_parent(&mut self, parent: Option<DisplayNode<'gc>>);
|
fn set_parent(&mut self, parent: Option<DisplayNode<'gc>>);
|
||||||
|
fn first_child(&self) -> Option<DisplayNode<'gc>>;
|
||||||
|
fn set_first_child(&mut self, node: Option<DisplayNode<'gc>>);
|
||||||
|
fn prev_sibling(&self) -> Option<DisplayNode<'gc>>;
|
||||||
|
fn set_prev_sibling(&mut self, node: Option<DisplayNode<'gc>>);
|
||||||
|
fn next_sibling(&self) -> Option<DisplayNode<'gc>>;
|
||||||
|
fn set_next_sibling(&mut self, node: Option<DisplayNode<'gc>>);
|
||||||
|
/// Iterates over the children of this display object in execution order.
|
||||||
|
/// This is different than render order.
|
||||||
|
fn children(&self) -> ChildIter<'gc> {
|
||||||
|
ChildIter {
|
||||||
|
cur_child: self.first_child(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn removed(&self) -> bool;
|
||||||
|
fn set_removed(&mut self, removed: bool);
|
||||||
fn run_frame(&mut self, _context: &mut UpdateContext<'_, 'gc, '_>) {}
|
fn run_frame(&mut self, _context: &mut UpdateContext<'_, 'gc, '_>) {}
|
||||||
fn run_post_frame(&mut self, _context: &mut UpdateContext<'_, 'gc, '_>) {}
|
|
||||||
fn render(&self, _context: &mut RenderContext<'_, 'gc>) {}
|
fn render(&self, _context: &mut RenderContext<'_, 'gc>) {}
|
||||||
|
|
||||||
fn as_button(&self) -> Option<&crate::button::Button<'gc>> {
|
fn as_button(&self) -> Option<&crate::button::Button<'gc>> {
|
||||||
|
@ -132,15 +187,15 @@ pub trait DisplayObject<'gc>: 'gc + Collect + Debug {
|
||||||
fn as_morph_shape_mut(&mut self) -> Option<&mut crate::morph_shape::MorphShape<'gc>> {
|
fn as_morph_shape_mut(&mut self) -> Option<&mut crate::morph_shape::MorphShape<'gc>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
fn apply_place_object(&mut self, place_object: swf::PlaceObject) {
|
fn apply_place_object(&mut self, place_object: &swf::PlaceObject) {
|
||||||
if let Some(matrix) = place_object.matrix {
|
if let Some(matrix) = &place_object.matrix {
|
||||||
self.set_matrix(&matrix.into());
|
self.set_matrix(&matrix.clone().into());
|
||||||
}
|
}
|
||||||
if let Some(color_transform) = place_object.color_transform {
|
if let Some(color_transform) = &place_object.color_transform {
|
||||||
self.set_color_transform(&color_transform.into());
|
self.set_color_transform(&color_transform.clone().into());
|
||||||
}
|
}
|
||||||
if let Some(name) = place_object.name {
|
if let Some(name) = &place_object.name {
|
||||||
self.set_name(&name);
|
self.set_name(name);
|
||||||
}
|
}
|
||||||
if let Some(clip_depth) = place_object.clip_depth {
|
if let Some(clip_depth) = place_object.clip_depth {
|
||||||
self.set_clip_depth(clip_depth);
|
self.set_clip_depth(clip_depth);
|
||||||
|
@ -251,6 +306,30 @@ macro_rules! impl_display_object {
|
||||||
fn set_parent(&mut self, parent: Option<crate::display_object::DisplayNode<'gc>>) {
|
fn set_parent(&mut self, parent: Option<crate::display_object::DisplayNode<'gc>>) {
|
||||||
self.$field.set_parent(parent)
|
self.$field.set_parent(parent)
|
||||||
}
|
}
|
||||||
|
fn first_child(&self) -> Option<DisplayNode<'gc>> {
|
||||||
|
self.$field.first_child()
|
||||||
|
}
|
||||||
|
fn set_first_child(&mut self, node: Option<DisplayNode<'gc>>) {
|
||||||
|
self.$field.set_first_child(node);
|
||||||
|
}
|
||||||
|
fn prev_sibling(&self) -> Option<DisplayNode<'gc>> {
|
||||||
|
self.$field.prev_sibling()
|
||||||
|
}
|
||||||
|
fn set_prev_sibling(&mut self, node: Option<DisplayNode<'gc>>) {
|
||||||
|
self.$field.set_prev_sibling(node);
|
||||||
|
}
|
||||||
|
fn next_sibling(&self) -> Option<DisplayNode<'gc>> {
|
||||||
|
self.$field.next_sibling()
|
||||||
|
}
|
||||||
|
fn set_next_sibling(&mut self, node: Option<DisplayNode<'gc>>) {
|
||||||
|
self.$field.set_next_sibling(node);
|
||||||
|
}
|
||||||
|
fn removed(&self) -> bool {
|
||||||
|
self.$field.removed()
|
||||||
|
}
|
||||||
|
fn set_removed(&mut self, value: bool) {
|
||||||
|
self.$field.set_removed(value)
|
||||||
|
}
|
||||||
fn box_clone(&self) -> Box<dyn crate::display_object::DisplayObject<'gc>> {
|
fn box_clone(&self) -> Box<dyn crate::display_object::DisplayObject<'gc>> {
|
||||||
Box::new(self.clone())
|
Box::new(self.clone())
|
||||||
}
|
}
|
||||||
|
@ -301,3 +380,18 @@ pub fn render_children<'gc>(
|
||||||
/// TODO(Herschel): The extra Box here is necessary to hold the trait object inside a GC pointer,
|
/// TODO(Herschel): The extra Box here is necessary to hold the trait object inside a GC pointer,
|
||||||
/// but this is an extra allocation... Can we avoid this, maybe with a DST?
|
/// but this is an extra allocation... Can we avoid this, maybe with a DST?
|
||||||
pub type DisplayNode<'gc> = GcCell<'gc, Box<dyn DisplayObject<'gc>>>;
|
pub type DisplayNode<'gc> = GcCell<'gc, Box<dyn DisplayObject<'gc>>>;
|
||||||
|
|
||||||
|
pub struct ChildIter<'gc> {
|
||||||
|
cur_child: Option<DisplayNode<'gc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> Iterator for ChildIter<'gc> {
|
||||||
|
type Item = DisplayNode<'gc>;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let cur = self.cur_child;
|
||||||
|
self.cur_child = self
|
||||||
|
.cur_child
|
||||||
|
.and_then(|display_cell| display_cell.read().next_sibling());
|
||||||
|
cur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::font::Font;
|
||||||
use crate::graphic::Graphic;
|
use crate::graphic::Graphic;
|
||||||
use crate::matrix::Matrix;
|
use crate::matrix::Matrix;
|
||||||
use crate::morph_shape::MorphShapeStatic;
|
use crate::morph_shape::MorphShapeStatic;
|
||||||
use crate::player::{RenderContext, UpdateContext};
|
use crate::player::{ActionQueue, RenderContext, UpdateContext};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::tag_utils::{self, DecodeResult, SwfStream};
|
use crate::tag_utils::{self, DecodeResult, SwfStream};
|
||||||
use crate::text::Text;
|
use crate::text::Text;
|
||||||
|
@ -26,7 +26,6 @@ pub struct MovieClip<'gc> {
|
||||||
static_data: Gc<'gc, MovieClipStatic>,
|
static_data: Gc<'gc, MovieClipStatic>,
|
||||||
tag_stream_pos: u64,
|
tag_stream_pos: u64,
|
||||||
is_playing: bool,
|
is_playing: bool,
|
||||||
goto_queue: Vec<FrameNumber>,
|
|
||||||
current_frame: FrameNumber,
|
current_frame: FrameNumber,
|
||||||
audio_stream: Option<AudioStreamHandle>,
|
audio_stream: Option<AudioStreamHandle>,
|
||||||
children: BTreeMap<Depth, DisplayNode<'gc>>,
|
children: BTreeMap<Depth, DisplayNode<'gc>>,
|
||||||
|
@ -41,7 +40,6 @@ impl<'gc> MovieClip<'gc> {
|
||||||
static_data: Gc::allocate(gc_context, MovieClipStatic::default()),
|
static_data: Gc::allocate(gc_context, MovieClipStatic::default()),
|
||||||
tag_stream_pos: 0,
|
tag_stream_pos: 0,
|
||||||
is_playing: false,
|
is_playing: false,
|
||||||
goto_queue: Vec::new(),
|
|
||||||
current_frame: 0,
|
current_frame: 0,
|
||||||
audio_stream: None,
|
audio_stream: None,
|
||||||
children: BTreeMap::new(),
|
children: BTreeMap::new(),
|
||||||
|
@ -73,7 +71,6 @@ impl<'gc> MovieClip<'gc> {
|
||||||
),
|
),
|
||||||
tag_stream_pos: 0,
|
tag_stream_pos: 0,
|
||||||
is_playing: true,
|
is_playing: true,
|
||||||
goto_queue: Vec::new(),
|
|
||||||
current_frame: 0,
|
current_frame: 0,
|
||||||
audio_stream: None,
|
audio_stream: None,
|
||||||
children: BTreeMap::new(),
|
children: BTreeMap::new(),
|
||||||
|
@ -85,9 +82,9 @@ impl<'gc> MovieClip<'gc> {
|
||||||
self.is_playing
|
self.is_playing
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_frame(&mut self) {
|
pub fn next_frame(&mut self, self_cell: DisplayNode<'gc>, action_queue: &mut ActionQueue<'gc>) {
|
||||||
if self.current_frame() < self.total_frames() {
|
if self.current_frame() < self.total_frames() {
|
||||||
self.goto_frame(self.current_frame + 1, true);
|
self.goto_frame(self_cell, action_queue, self.current_frame + 1, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,9 +95,9 @@ impl<'gc> MovieClip<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prev_frame(&mut self) {
|
pub fn prev_frame(&mut self, self_cell: DisplayNode<'gc>, action_queue: &mut ActionQueue<'gc>) {
|
||||||
if self.current_frame > 1 {
|
if self.current_frame > 1 {
|
||||||
self.goto_frame(self.current_frame - 1, true);
|
self.goto_frame(self_cell, action_queue, self.current_frame - 1, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,9 +107,15 @@ impl<'gc> MovieClip<'gc> {
|
||||||
|
|
||||||
/// Queues up a goto to the specified frame.
|
/// Queues up a goto to the specified frame.
|
||||||
/// `frame` should be 1-based.
|
/// `frame` should be 1-based.
|
||||||
pub fn goto_frame(&mut self, frame: FrameNumber, stop: bool) {
|
pub fn goto_frame(
|
||||||
|
&mut self,
|
||||||
|
self_cell: DisplayNode<'gc>,
|
||||||
|
action_queue: &mut ActionQueue<'gc>,
|
||||||
|
frame: FrameNumber,
|
||||||
|
stop: bool,
|
||||||
|
) {
|
||||||
if frame != self.current_frame {
|
if frame != self.current_frame {
|
||||||
self.goto_queue.push(frame);
|
action_queue.queue_goto(self_cell, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
if stop {
|
if stop {
|
||||||
|
@ -203,19 +206,6 @@ impl<'gc> MovieClip<'gc> {
|
||||||
self.static_data.frame_labels.get(frame_label).copied()
|
self.static_data.frame_labels.get(frame_label).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_goto_queue(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
|
||||||
let mut i = 0;
|
|
||||||
while i < self.goto_queue.len() {
|
|
||||||
let frame = self.goto_queue[i];
|
|
||||||
if self.current_frame != frame {
|
|
||||||
self.run_goto(context, frame);
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.goto_queue.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tag_stream_start(&self) -> u64 {
|
fn tag_stream_start(&self) -> u64 {
|
||||||
self.static_data.tag_stream_start
|
self.static_data.tag_stream_start
|
||||||
}
|
}
|
||||||
|
@ -235,6 +225,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
cursor.set_position(self.tag_stream_pos);
|
cursor.set_position(self.tag_stream_pos);
|
||||||
swf::read::Reader::new(cursor, context.swf_version)
|
swf::read::Reader::new(cursor, context.swf_version)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_frame_internal(
|
fn run_frame_internal(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
@ -292,13 +283,20 @@ impl<'gc> MovieClip<'gc> {
|
||||||
depth: Depth,
|
depth: Depth,
|
||||||
copy_previous_properties: bool,
|
copy_previous_properties: bool,
|
||||||
) -> Option<DisplayNode<'gc>> {
|
) -> Option<DisplayNode<'gc>> {
|
||||||
if let Ok(child) = context
|
if let Ok(child_cell) = context
|
||||||
.library
|
.library
|
||||||
.instantiate_display_object(id, context.gc_context)
|
.instantiate_display_object(id, context.gc_context)
|
||||||
{
|
{
|
||||||
let prev_child = self.children.insert(depth, child);
|
// Remove previous child from children list,
|
||||||
|
// and add new childonto front of the list.
|
||||||
|
let prev_child = self.children.insert(depth, child_cell);
|
||||||
|
if let Some(prev_child) = prev_child {
|
||||||
|
self.remove_child_from_exec_list(context.gc_context, prev_child);
|
||||||
|
}
|
||||||
|
self.add_child_to_exec_list(context.gc_context, child_cell);
|
||||||
{
|
{
|
||||||
let mut child = child.write(context.gc_context);
|
let mut child = child_cell.write(context.gc_context);
|
||||||
|
// Set initial properties for child.
|
||||||
child.set_parent(Some(context.active_clip));
|
child.set_parent(Some(context.active_clip));
|
||||||
child.set_place_frame(self.current_frame);
|
child.set_place_frame(self.current_frame);
|
||||||
if copy_previous_properties {
|
if copy_previous_properties {
|
||||||
|
@ -306,28 +304,72 @@ impl<'gc> MovieClip<'gc> {
|
||||||
child.copy_display_properties_from(prev_child);
|
child.copy_display_properties_from(prev_child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let prev_clip = context.active_clip;
|
||||||
|
// Run first frame.
|
||||||
|
context.active_clip = child_cell;
|
||||||
|
child.run_frame(context);
|
||||||
|
context.active_clip = prev_clip;
|
||||||
}
|
}
|
||||||
Some(child)
|
Some(child_cell)
|
||||||
} else {
|
} else {
|
||||||
log::error!("Unable to instantiate display node id {}", id);
|
log::error!("Unable to instantiate display node id {}", id);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Adds a child to the front of the execution list.
|
||||||
fn run_goto(&mut self, context: &mut UpdateContext<'_, 'gc, '_>, frame: FrameNumber) {
|
/// This does not affect the render list.
|
||||||
|
fn add_child_to_exec_list(
|
||||||
|
&mut self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
child_cell: DisplayNode<'gc>,
|
||||||
|
) {
|
||||||
|
if let Some(head) = self.first_child() {
|
||||||
|
head.write(gc_context).set_prev_sibling(Some(child_cell));
|
||||||
|
child_cell.write(gc_context).set_next_sibling(Some(head));
|
||||||
|
}
|
||||||
|
self.set_first_child(Some(child_cell));
|
||||||
|
}
|
||||||
|
/// Removes a child from the execution list.
|
||||||
|
/// This does not affect the render list.
|
||||||
|
fn remove_child_from_exec_list(
|
||||||
|
&mut self,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
child_cell: DisplayNode<'gc>,
|
||||||
|
) {
|
||||||
|
let mut child = child_cell.write(gc_context);
|
||||||
|
// Remove from children linked list.
|
||||||
|
let prev = child.prev_sibling();
|
||||||
|
let next = child.next_sibling();
|
||||||
|
if let Some(prev) = prev {
|
||||||
|
prev.write(gc_context).set_next_sibling(next);
|
||||||
|
}
|
||||||
|
if let Some(next) = next {
|
||||||
|
next.write(gc_context).set_prev_sibling(prev);
|
||||||
|
}
|
||||||
|
if let Some(head) = self.first_child() {
|
||||||
|
if GcCell::ptr_eq(head, child_cell) {
|
||||||
|
self.set_first_child(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Flag child as removed.
|
||||||
|
child.set_removed(true);
|
||||||
|
}
|
||||||
|
pub fn run_goto(&mut self, context: &mut UpdateContext<'_, 'gc, '_>, frame: FrameNumber) {
|
||||||
// Flash gotos are tricky:
|
// Flash gotos are tricky:
|
||||||
// 1) MovieClip timelines are stored as deltas from frame to frame,
|
// 1) Conceptually, a goto should act like the playhead is advancing forward or
|
||||||
// so we have to step through the intermediate frames to goto a target frame.
|
// backward to a frame.
|
||||||
// For rewinds, this means starting from frame 1.
|
// 2) However, MovieClip timelines are stored as deltas from frame to frame,
|
||||||
// 2) Objects that would persist over the goto should not be recreated and destroyed,
|
// so for rewinds, we must restart to frame 1 and play forward.
|
||||||
// they should keep their properties.
|
// 3) Objects that would persist over the goto conceptually should not be
|
||||||
// Particularly for rewinds, the object should persist if it was create
|
// destroyed and recreated; they should keep their properties.
|
||||||
|
// Particularly for rewinds, the object should persist if it was created
|
||||||
// *before* the frame we are going to. (DisplayNode::place_frame).
|
// *before* the frame we are going to. (DisplayNode::place_frame).
|
||||||
// 3) We want to avoid creating objects just to destroy them if they aren't on
|
// 4) We want to avoid creating objects just to destroy them if they aren't on
|
||||||
// the goto frame, so we should instead aggregate the deltas into a list
|
// the goto frame, so we should instead aggregate the deltas into a final list
|
||||||
// of commands at the end of the goto, and THEN create the needed objects.
|
// of commands, and THEN modify the children as necessary.
|
||||||
|
|
||||||
// This map will maintain a map of depth -> placement commands.
|
// This map will maintain a map of depth -> placement commands.
|
||||||
|
// TODO: Move this to UpdateContext to avoid allocations.
|
||||||
let mut goto_commands = fnv::FnvHashMap::default();
|
let mut goto_commands = fnv::FnvHashMap::default();
|
||||||
|
|
||||||
let is_rewind = if frame < self.current_frame() {
|
let is_rewind = if frame < self.current_frame() {
|
||||||
|
@ -335,6 +377,25 @@ impl<'gc> MovieClip<'gc> {
|
||||||
// when rewinding.
|
// when rewinding.
|
||||||
self.tag_stream_pos = 0;
|
self.tag_stream_pos = 0;
|
||||||
self.current_frame = 0;
|
self.current_frame = 0;
|
||||||
|
|
||||||
|
// Remove all display objects that were created after the desination frame.
|
||||||
|
// TODO: We want to do something like self.children.retain here,
|
||||||
|
// but BTreeMap::retain does not exist.
|
||||||
|
let children: smallvec::SmallVec<[_; 16]> = self
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(depth, clip)| {
|
||||||
|
if clip.read().place_frame() > frame {
|
||||||
|
Some((*depth, *clip))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
for (depth, child) in children {
|
||||||
|
self.children.remove(&depth);
|
||||||
|
self.remove_child_from_exec_list(context.gc_context, child);
|
||||||
|
}
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -343,6 +404,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
// Step through the intermediate frames, and aggregate the deltas of each frame.
|
// Step through the intermediate frames, and aggregate the deltas of each frame.
|
||||||
let mut frame_pos = self.tag_stream_pos;
|
let mut frame_pos = self.tag_stream_pos;
|
||||||
let mut reader = self.reader(context);
|
let mut reader = self.reader(context);
|
||||||
|
let gc_context = context.gc_context;
|
||||||
while self.current_frame < frame {
|
while self.current_frame < frame {
|
||||||
self.current_frame += 1;
|
self.current_frame += 1;
|
||||||
frame_pos = reader.get_inner().position();
|
frame_pos = reader.get_inner().position();
|
||||||
|
@ -362,36 +424,33 @@ impl<'gc> MovieClip<'gc> {
|
||||||
self.goto_place_object(reader, tag_len, 4, &mut goto_commands)
|
self.goto_place_object(reader, tag_len, 4, &mut goto_commands)
|
||||||
}
|
}
|
||||||
TagCode::RemoveObject => {
|
TagCode::RemoveObject => {
|
||||||
self.goto_remove_object(reader, 1, &mut goto_commands, is_rewind)
|
self.goto_remove_object(reader, 1, gc_context, &mut goto_commands, is_rewind)
|
||||||
}
|
}
|
||||||
TagCode::RemoveObject2 => {
|
TagCode::RemoveObject2 => {
|
||||||
self.goto_remove_object(reader, 2, &mut goto_commands, is_rewind)
|
self.goto_remove_object(reader, 2, gc_context, &mut goto_commands, is_rewind)
|
||||||
}
|
}
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
};
|
};
|
||||||
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::ShowFrame);
|
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::ShowFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
let prev_active_clip = context.active_clip;
|
// Run the list of goto commands to actually create and update the display objects.
|
||||||
|
let run_goto_command =
|
||||||
// Run the final list of commands.
|
|clip: &mut MovieClip<'gc>,
|
||||||
if is_rewind {
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
// TODO: We want to do something like self.children.retain here,
|
(&depth, params): (&Depth, &GotoPlaceObject)| {
|
||||||
// but BTreeMap::retain does not exist.
|
let (was_instantiated, child) = match clip.children.get_mut(&depth).copied() {
|
||||||
let mut children = std::mem::replace(&mut self.children, BTreeMap::new());
|
|
||||||
goto_commands.into_iter().for_each(|(depth, params)| {
|
|
||||||
let (was_instantiated, child) = match children.get_mut(&depth).copied() {
|
|
||||||
// For rewinds, if an object was created before the final frame,
|
// For rewinds, if an object was created before the final frame,
|
||||||
// it will exist on the final frame as well. Re-use this object
|
// it will exist on the final frame as well. Re-use this object
|
||||||
// instead of recreating.
|
// instead of recreating.
|
||||||
Some(prev_child) if prev_child.read().place_frame() <= frame => {
|
Some(prev_child) => (false, prev_child),
|
||||||
self.children.insert(depth, prev_child);
|
None => {
|
||||||
(false, prev_child)
|
if let Some(child) = clip.instantiate_child(
|
||||||
}
|
context,
|
||||||
_ => {
|
params.id(),
|
||||||
if let Some(child) =
|
depth,
|
||||||
self.instantiate_child(context, params.id(), depth, false)
|
params.modifies_original_item(),
|
||||||
{
|
) {
|
||||||
(true, child)
|
(true, child)
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
|
@ -400,61 +459,34 @@ impl<'gc> MovieClip<'gc> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply final delta to display pamareters.
|
// Apply final delta to display pamareters.
|
||||||
let child_node = child;
|
|
||||||
let mut child = child.write(context.gc_context);
|
let mut child = child.write(context.gc_context);
|
||||||
child.apply_place_object(params.place_object);
|
child.apply_place_object(¶ms.place_object);
|
||||||
if was_instantiated {
|
if was_instantiated {
|
||||||
// Set the placement frame for the new object to the frame
|
// Set the placement frame for the new object to the frame
|
||||||
// it is actually created on.
|
// it is actually created on.
|
||||||
child.set_place_frame(params.frame);
|
child.set_place_frame(params.frame);
|
||||||
// We must run newly created objects for one frame
|
|
||||||
// to ensure they place any children objects.
|
|
||||||
// TODO: This will probably move as our order-of-execution
|
|
||||||
// becomes more accurate.
|
|
||||||
context.active_clip = child_node;
|
|
||||||
child.run_frame(context);
|
|
||||||
context.active_clip = prev_active_clip;
|
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
} else {
|
|
||||||
goto_commands.into_iter().for_each(|(depth, params)| {
|
|
||||||
let id = params.id();
|
|
||||||
let child = if id != 0 {
|
|
||||||
if let Some(child) =
|
|
||||||
self.instantiate_child(context, id, depth, params.modifies_original_item())
|
|
||||||
{
|
|
||||||
child
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if let Some(child) = self.children.get_mut(&depth) {
|
|
||||||
*child
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply final delta to display pamareters.
|
// We have to be sure that queued actions are generated in the same order
|
||||||
let child_node = child;
|
// as if the playhead had reached this frame normally.
|
||||||
let mut child = child.write(context.gc_context);
|
// First, run frames for children that were created before this frame.
|
||||||
child.apply_place_object(params.place_object);
|
goto_commands
|
||||||
if id != 0 {
|
.iter()
|
||||||
// Set the placement frame for the new object to the frame
|
.filter(|(_, params)| params.frame < frame)
|
||||||
// it is actually created on.
|
.for_each(|goto| run_goto_command(self, context, goto));
|
||||||
child.set_place_frame(params.frame);
|
|
||||||
// We must run newly created objects for one frame
|
// Next, run the final frame for the parent clip.
|
||||||
// to ensure they place any children objects.
|
// Re-run the final frame without display tags (DoAction, StartSound, etc.)
|
||||||
// TODO: This will probably move as our order-of-execution
|
|
||||||
// becomes more accurate.
|
|
||||||
context.active_clip = child_node;
|
|
||||||
child.run_frame(context);
|
|
||||||
context.active_clip = prev_active_clip;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Re-run the final frame to run all other tags (DoAction, StartSound, etc.)
|
|
||||||
self.current_frame = frame - 1;
|
self.current_frame = frame - 1;
|
||||||
self.tag_stream_pos = frame_pos;
|
self.tag_stream_pos = frame_pos;
|
||||||
self.run_frame_internal(context, false);
|
self.run_frame_internal(context, false);
|
||||||
|
|
||||||
|
// Finally, run frames for children that are placed on this frame.
|
||||||
|
goto_commands
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, params)| params.frame >= frame)
|
||||||
|
.for_each(|goto| run_goto_command(self, context, goto));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles a PlaceObject tag when running a goto action.
|
/// Handles a PlaceObject tag when running a goto action.
|
||||||
|
@ -492,6 +524,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
&mut self,
|
&mut self,
|
||||||
reader: &mut SwfStream<&'a [u8]>,
|
reader: &mut SwfStream<&'a [u8]>,
|
||||||
version: u8,
|
version: u8,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
goto_commands: &mut fnv::FnvHashMap<Depth, GotoPlaceObject>,
|
goto_commands: &mut fnv::FnvHashMap<Depth, GotoPlaceObject>,
|
||||||
is_rewind: bool,
|
is_rewind: bool,
|
||||||
) -> DecodeResult {
|
) -> DecodeResult {
|
||||||
|
@ -507,7 +540,9 @@ impl<'gc> MovieClip<'gc> {
|
||||||
// Don't do this for rewinds, because they conceptually
|
// Don't do this for rewinds, because they conceptually
|
||||||
// start from an empty display list, and we also want to examine
|
// start from an empty display list, and we also want to examine
|
||||||
// the old children to decide if they persist (place_frame <= goto_frame).
|
// the old children to decide if they persist (place_frame <= goto_frame).
|
||||||
self.children.remove(&remove_object.depth);
|
if let Some(child) = self.children.remove(&remove_object.depth) {
|
||||||
|
self.remove_child_from_exec_list(gc_context, child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -521,24 +556,17 @@ impl<'gc> DisplayObject<'gc> for MovieClip<'gc> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
fn run_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
||||||
if self.is_playing {
|
// Children must run first.
|
||||||
self.run_frame_internal(context, true);
|
let prev_clip = context.active_clip;
|
||||||
}
|
for child in self.children() {
|
||||||
|
context.active_clip = child;
|
||||||
// TODO(Herschel): Verify order of execution for parent/children.
|
|
||||||
// Parent first? Children first? Sorted by depth?
|
|
||||||
for child in self.children.values_mut() {
|
|
||||||
context.active_clip = *child;
|
|
||||||
child.write(context.gc_context).run_frame(context);
|
child.write(context.gc_context).run_frame(context);
|
||||||
}
|
}
|
||||||
}
|
context.active_clip = prev_clip;
|
||||||
|
|
||||||
fn run_post_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
// Run myself.
|
||||||
self.run_goto_queue(context);
|
if self.is_playing {
|
||||||
|
self.run_frame_internal(context, true);
|
||||||
for child in self.children.values() {
|
|
||||||
context.active_clip = *child;
|
|
||||||
child.write(context.gc_context).run_post_frame(context);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1207,7 +1235,9 @@ impl<'gc, 'a> MovieClip<'gc> {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
};
|
};
|
||||||
context.actions.push((context.active_clip, slice));
|
context
|
||||||
|
.action_queue
|
||||||
|
.queue_actions(context.active_clip, slice);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1238,7 +1268,7 @@ impl<'gc, 'a> MovieClip<'gc> {
|
||||||
) {
|
) {
|
||||||
child
|
child
|
||||||
.write(context.gc_context)
|
.write(context.gc_context)
|
||||||
.apply_place_object(place_object);
|
.apply_place_object(&place_object);
|
||||||
child
|
child
|
||||||
} else {
|
} else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -1248,7 +1278,7 @@ impl<'gc, 'a> MovieClip<'gc> {
|
||||||
if let Some(child) = self.children.get_mut(&place_object.depth) {
|
if let Some(child) = self.children.get_mut(&place_object.depth) {
|
||||||
child
|
child
|
||||||
.write(context.gc_context)
|
.write(context.gc_context)
|
||||||
.apply_place_object(place_object);
|
.apply_place_object(&place_object);
|
||||||
*child
|
*child
|
||||||
} else {
|
} else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -1272,7 +1302,7 @@ impl<'gc, 'a> MovieClip<'gc> {
|
||||||
reader.read_remove_object_2()
|
reader.read_remove_object_2()
|
||||||
}?;
|
}?;
|
||||||
if let Some(child) = self.children.remove(&remove_object.depth) {
|
if let Some(child) = self.children.remove(&remove_object.depth) {
|
||||||
child.write(context.gc_context).set_parent(None);
|
self.remove_child_from_exec_list(context.gc_context, child);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use crate::events::{ButtonEvent, PlayerEvent};
|
||||||
use crate::library::Library;
|
use crate::library::Library;
|
||||||
use crate::movie_clip::MovieClip;
|
use crate::movie_clip::MovieClip;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::tag_utils::SwfSlice;
|
||||||
use crate::transform::TransformStack;
|
use crate::transform::TransformStack;
|
||||||
use gc_arena::{make_arena, ArenaParameters, Collect, GcCell, MutationContext};
|
use gc_arena::{make_arena, ArenaParameters, Collect, GcCell, MutationContext};
|
||||||
use log::info;
|
use log::info;
|
||||||
|
@ -25,6 +26,7 @@ struct GcRoot<'gc> {
|
||||||
root: DisplayNode<'gc>,
|
root: DisplayNode<'gc>,
|
||||||
mouse_hover_node: GcCell<'gc, Option<DisplayNode<'gc>>>, // TODO: Remove GcCell wrapped inside GcCell.
|
mouse_hover_node: GcCell<'gc, Option<DisplayNode<'gc>>>, // TODO: Remove GcCell wrapped inside GcCell.
|
||||||
avm: GcCell<'gc, Avm1<'gc>>,
|
avm: GcCell<'gc, Avm1<'gc>>,
|
||||||
|
action_queue: GcCell<'gc, ActionQueue<'gc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Error = Box<dyn std::error::Error>;
|
type Error = Box<dyn std::error::Error>;
|
||||||
|
@ -158,6 +160,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
||||||
),
|
),
|
||||||
mouse_hover_node: GcCell::allocate(gc_context, None),
|
mouse_hover_node: GcCell::allocate(gc_context, None),
|
||||||
avm: GcCell::allocate(gc_context, Avm1::new(gc_context, NEWEST_PLAYER_VERSION)),
|
avm: GcCell::allocate(gc_context, Avm1::new(gc_context, NEWEST_PLAYER_VERSION)),
|
||||||
|
action_queue: GcCell::allocate(gc_context, ActionQueue::new()),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
frame_rate: header.frame_rate.into(),
|
frame_rate: header.frame_rate.into(),
|
||||||
|
@ -304,7 +307,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
||||||
renderer,
|
renderer,
|
||||||
audio,
|
audio,
|
||||||
navigator,
|
navigator,
|
||||||
actions: vec![],
|
action_queue: gc_root.action_queue.write(gc_context),
|
||||||
gc_context,
|
gc_context,
|
||||||
active_clip: gc_root.root,
|
active_clip: gc_root.root,
|
||||||
};
|
};
|
||||||
|
@ -379,7 +382,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
||||||
renderer,
|
renderer,
|
||||||
audio,
|
audio,
|
||||||
navigator,
|
navigator,
|
||||||
actions: vec![],
|
action_queue: gc_root.action_queue.write(gc_context),
|
||||||
gc_context,
|
gc_context,
|
||||||
active_clip: gc_root.root,
|
active_clip: gc_root.root,
|
||||||
};
|
};
|
||||||
|
@ -446,7 +449,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
||||||
renderer,
|
renderer,
|
||||||
audio,
|
audio,
|
||||||
navigator,
|
navigator,
|
||||||
actions: vec![],
|
action_queue: gc_root.action_queue.write(gc_context),
|
||||||
gc_context,
|
gc_context,
|
||||||
active_clip: gc_root.root,
|
active_clip: gc_root.root,
|
||||||
};
|
};
|
||||||
|
@ -506,7 +509,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
||||||
renderer,
|
renderer,
|
||||||
audio,
|
audio,
|
||||||
navigator,
|
navigator,
|
||||||
actions: vec![],
|
action_queue: gc_root.action_queue.write(gc_context),
|
||||||
gc_context,
|
gc_context,
|
||||||
active_clip: gc_root.root,
|
active_clip: gc_root.root,
|
||||||
};
|
};
|
||||||
|
@ -578,26 +581,33 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
||||||
// I think this will eventually be cleaned up;
|
// I think this will eventually be cleaned up;
|
||||||
// Need to figure out the proper order of operations between ticking a clip
|
// Need to figure out the proper order of operations between ticking a clip
|
||||||
// and running the actions.
|
// and running the actions.
|
||||||
let mut actions = std::mem::replace(&mut update_context.actions, vec![]);
|
while let Some(clip_action) = update_context.action_queue.pop() {
|
||||||
while !actions.is_empty() {
|
match clip_action {
|
||||||
{
|
Action::Action {
|
||||||
let mut action_context = crate::avm1::ActionContext {
|
clip,
|
||||||
gc_context: update_context.gc_context,
|
actions: action,
|
||||||
global_time: update_context.global_time,
|
} => {
|
||||||
root,
|
// We don't run the action f the clip was removed after it queued the action.
|
||||||
player_version: update_context.player_version,
|
if clip.read().removed() {
|
||||||
start_clip: root,
|
continue;
|
||||||
active_clip: root,
|
}
|
||||||
target_clip: Some(root),
|
let mut action_context = crate::avm1::ActionContext {
|
||||||
target_path: crate::avm1::Value::Undefined,
|
gc_context: update_context.gc_context,
|
||||||
rng: update_context.rng,
|
global_time: update_context.global_time,
|
||||||
audio: update_context.audio,
|
root,
|
||||||
navigator: update_context.navigator,
|
player_version: update_context.player_version,
|
||||||
};
|
start_clip: root,
|
||||||
for (active_clip, action) in actions {
|
active_clip: root,
|
||||||
action_context.start_clip = active_clip;
|
target_clip: Some(root),
|
||||||
action_context.active_clip = active_clip;
|
target_path: crate::avm1::Value::Undefined,
|
||||||
action_context.target_clip = Some(active_clip);
|
action_queue: &mut update_context.action_queue,
|
||||||
|
rng: update_context.rng,
|
||||||
|
audio: update_context.audio,
|
||||||
|
navigator: update_context.navigator,
|
||||||
|
};
|
||||||
|
action_context.start_clip = clip;
|
||||||
|
action_context.active_clip = clip;
|
||||||
|
action_context.target_clip = Some(clip);
|
||||||
update_context.avm.insert_stack_frame_for_action(
|
update_context.avm.insert_stack_frame_for_action(
|
||||||
update_context.swf_version,
|
update_context.swf_version,
|
||||||
action,
|
action,
|
||||||
|
@ -605,14 +615,19 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
||||||
);
|
);
|
||||||
let _ = update_context.avm.run_stack_till_empty(&mut action_context);
|
let _ = update_context.avm.run_stack_till_empty(&mut action_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Action::Goto { clip, frame } => {
|
||||||
|
update_context.active_clip = clip;
|
||||||
|
let mut clip = clip.write(update_context.gc_context);
|
||||||
|
// We don't run the action if the clip was removed after it queued the action.
|
||||||
|
if clip.removed() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(movie_clip) = clip.as_movie_clip_mut() {
|
||||||
|
movie_clip.run_goto(update_context, frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run goto queues.
|
|
||||||
update_context.active_clip = root;
|
|
||||||
root.write(update_context.gc_context)
|
|
||||||
.run_post_frame(update_context);
|
|
||||||
|
|
||||||
actions = std::mem::replace(&mut update_context.actions, vec![]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -682,7 +697,7 @@ pub struct UpdateContext<'a, 'gc, 'gc_context> {
|
||||||
pub audio: &'a mut dyn AudioBackend,
|
pub audio: &'a mut dyn AudioBackend,
|
||||||
pub navigator: &'a mut dyn NavigatorBackend,
|
pub navigator: &'a mut dyn NavigatorBackend,
|
||||||
pub rng: &'a mut SmallRng,
|
pub rng: &'a mut SmallRng,
|
||||||
pub actions: Vec<(DisplayNode<'gc>, crate::tag_utils::SwfSlice)>,
|
pub action_queue: std::cell::RefMut<'a, ActionQueue<'gc>>,
|
||||||
pub active_clip: DisplayNode<'gc>,
|
pub active_clip: DisplayNode<'gc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -693,3 +708,54 @@ pub struct RenderContext<'a, 'gc> {
|
||||||
pub view_bounds: BoundingBox,
|
pub view_bounds: BoundingBox,
|
||||||
pub clip_depth_stack: Vec<Depth>,
|
pub clip_depth_stack: Vec<Depth>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum Action<'gc> {
|
||||||
|
Action {
|
||||||
|
clip: DisplayNode<'gc>,
|
||||||
|
actions: SwfSlice,
|
||||||
|
},
|
||||||
|
Goto {
|
||||||
|
clip: DisplayNode<'gc>,
|
||||||
|
frame: u16,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Action and gotos need to be queued up to execute at the end of the frame.
|
||||||
|
pub struct ActionQueue<'gc> {
|
||||||
|
queue: std::collections::VecDeque<Action<'gc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> ActionQueue<'gc> {
|
||||||
|
const DEFAULT_CAPACITY: usize = 32;
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
queue: std::collections::VecDeque::with_capacity(Self::DEFAULT_CAPACITY),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queue_actions(&mut self, clip: DisplayNode<'gc>, actions: SwfSlice) {
|
||||||
|
self.queue.push_back(Action::Action { clip, actions })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queue_goto(&mut self, clip: DisplayNode<'gc>, frame: u16) {
|
||||||
|
self.queue.push_back(Action::Goto { clip, frame })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop(&mut self) -> Option<Action<'gc>> {
|
||||||
|
self.queue.pop_front()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> Default for ActionQueue<'gc> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<'gc> Collect for ActionQueue<'gc> {
|
||||||
|
#[inline]
|
||||||
|
fn trace(&self, cc: gc_arena::CollectionContext) {
|
||||||
|
self.queue.iter().for_each(|o| o.trace(cc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue