core: Merge #103, improve execution order of ActionScript and gotos
core: Improve execution order of ActionScript and gotos
This commit is contained in:
commit
c4426bf377
|
@ -61,9 +61,14 @@ pub struct ActionContext<'a, 'gc, 'gc_context> {
|
|||
/// _names ("instanceN" etc. for unnamed clips).
|
||||
pub target_path: Value<'gc>,
|
||||
|
||||
pub background_color: &'a mut Color,
|
||||
pub rng: &'a mut SmallRng,
|
||||
pub action_queue: &'a mut crate::player::ActionQueue<'gc>,
|
||||
pub audio: &'a mut dyn crate::backend::audio::AudioBackend,
|
||||
pub library: &'a mut crate::library::Library<'gc>,
|
||||
pub navigator: &'a mut dyn crate::backend::navigator::NavigatorBackend,
|
||||
pub renderer: &'a mut dyn crate::backend::render::RenderBackend,
|
||||
pub swf_data: &'a std::sync::Arc<Vec<u8>>,
|
||||
}
|
||||
|
||||
pub struct Avm1<'gc> {
|
||||
|
@ -120,6 +125,29 @@ impl<'gc> Avm1<'gc> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Makes an `UpdateContext` from an `ActionContext`.
|
||||
/// TODO: The Contexts should probably be merged.
|
||||
fn update_context<'a, 'gc_context, 'b>(
|
||||
&self,
|
||||
context: &'b mut ActionContext<'a, 'gc, 'gc_context>,
|
||||
) -> crate::player::UpdateContext<'b, 'gc, 'gc_context> {
|
||||
crate::player::UpdateContext {
|
||||
player_version: context.player_version,
|
||||
global_time: context.global_time,
|
||||
swf_data: context.swf_data,
|
||||
swf_version: self.current_swf_version(),
|
||||
library: context.library,
|
||||
background_color: context.background_color,
|
||||
rng: context.rng,
|
||||
renderer: context.renderer,
|
||||
audio: context.audio,
|
||||
navigator: context.navigator,
|
||||
action_queue: context.action_queue,
|
||||
gc_context: context.gc_context,
|
||||
active_clip: context.active_clip,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the current locals pool into a set of form values.
|
||||
///
|
||||
/// This is necessary to support form submission from Flash via a couple of
|
||||
|
@ -1125,12 +1153,17 @@ impl<'gc> Avm1<'gc> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn action_goto_frame(&mut self, context: &mut ActionContext, frame: u16) -> Result<(), Error> {
|
||||
fn action_goto_frame(
|
||||
&mut self,
|
||||
context: &mut ActionContext<'_, 'gc, '_>,
|
||||
frame: u16,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(clip) = context.target_clip {
|
||||
let mut display_object = clip.write(context.gc_context);
|
||||
if let Some(clip) = display_object.as_movie_clip_mut() {
|
||||
let mut update_context = self.update_context(context);
|
||||
// The frame on the stack is 0-based, not 1-based.
|
||||
clip.goto_frame(frame + 1, true);
|
||||
clip.goto_frame(&mut update_context, frame + 1, true);
|
||||
} else {
|
||||
log::error!("GotoFrame failed: Target is not a MovieClip");
|
||||
}
|
||||
|
@ -1142,7 +1175,7 @@ impl<'gc> Avm1<'gc> {
|
|||
|
||||
fn action_goto_frame_2(
|
||||
&mut self,
|
||||
context: &mut ActionContext,
|
||||
context: &mut ActionContext<'_, 'gc, '_>,
|
||||
set_playing: bool,
|
||||
scene_offset: u16,
|
||||
) -> Result<(), Error> {
|
||||
|
@ -1151,14 +1184,19 @@ impl<'gc> Avm1<'gc> {
|
|||
if let Some(clip) = context.target_clip {
|
||||
let mut display_object = clip.write(context.gc_context);
|
||||
if let Some(clip) = display_object.as_movie_clip_mut() {
|
||||
let mut update_context = self.update_context(context);
|
||||
match self.pop()? {
|
||||
Value::Number(frame) => {
|
||||
// The frame on the stack is 1-based, not 0-based.
|
||||
clip.goto_frame(scene_offset + (frame as u16), !set_playing)
|
||||
clip.goto_frame(
|
||||
&mut update_context,
|
||||
scene_offset + (frame as u16),
|
||||
!set_playing,
|
||||
)
|
||||
}
|
||||
Value::String(frame_label) => {
|
||||
if let Some(frame) = clip.frame_label_to_number(&frame_label) {
|
||||
clip.goto_frame(scene_offset + frame, !set_playing)
|
||||
clip.goto_frame(&mut update_context, scene_offset + frame, !set_playing)
|
||||
} else {
|
||||
log::warn!(
|
||||
"GotoFrame2: MovieClip {} does not contain frame label '{}'",
|
||||
|
@ -1178,12 +1216,17 @@ impl<'gc> Avm1<'gc> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn action_goto_label(&mut self, context: &mut ActionContext, label: &str) -> Result<(), Error> {
|
||||
fn action_goto_label(
|
||||
&mut self,
|
||||
context: &mut ActionContext<'_, 'gc, '_>,
|
||||
label: &str,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(clip) = context.target_clip {
|
||||
let mut display_object = clip.write(context.gc_context);
|
||||
if let Some(clip) = display_object.as_movie_clip_mut() {
|
||||
if let Some(frame) = clip.frame_label_to_number(label) {
|
||||
clip.goto_frame(frame, true);
|
||||
let mut update_context = self.update_context(context);
|
||||
clip.goto_frame(&mut update_context, frame, true);
|
||||
} else {
|
||||
log::warn!("GoToLabel: Frame label '{}' not found", label);
|
||||
}
|
||||
|
@ -1336,11 +1379,12 @@ impl<'gc> Avm1<'gc> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn action_next_frame(&mut self, context: &mut ActionContext) -> Result<(), Error> {
|
||||
fn action_next_frame(&mut self, context: &mut ActionContext<'_, 'gc, '_>) -> Result<(), Error> {
|
||||
if let Some(clip) = context.target_clip {
|
||||
let mut display_object = clip.write(context.gc_context);
|
||||
if let Some(clip) = display_object.as_movie_clip_mut() {
|
||||
clip.next_frame();
|
||||
let mut update_context = self.update_context(context);
|
||||
clip.next_frame(&mut update_context);
|
||||
} else {
|
||||
log::warn!("NextFrame: Target is not a MovieClip");
|
||||
}
|
||||
|
@ -1393,11 +1437,12 @@ impl<'gc> Avm1<'gc> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn action_prev_frame(&mut self, context: &mut ActionContext) -> Result<(), Error> {
|
||||
fn action_prev_frame(&mut self, context: &mut ActionContext<'_, 'gc, '_>) -> Result<(), Error> {
|
||||
if let Some(clip) = context.target_clip {
|
||||
let mut display_object = clip.write(context.gc_context);
|
||||
if let Some(clip) = display_object.as_movie_clip_mut() {
|
||||
clip.prev_frame();
|
||||
let mut update_context = self.update_context(context);
|
||||
clip.prev_frame(&mut update_context);
|
||||
} else {
|
||||
log::warn!("PrevFrame: Target is not a MovieClip");
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::avm1::object::{Attribute::*, Object};
|
||||
use crate::avm1::{ActionContext, Avm1, Value};
|
||||
use crate::display_object::DisplayNode;
|
||||
use crate::movie_clip::MovieClip;
|
||||
use enumset::EnumSet;
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
|
@ -29,14 +30,14 @@ macro_rules! with_movie_clip_mut {
|
|||
$(
|
||||
$object.force_set_function(
|
||||
$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(movie_clip) = display_object.write(context.gc_context).as_movie_clip_mut() {
|
||||
return $fn(movie_clip, args);
|
||||
return $fn(movie_clip, avm, context, display_object, args);
|
||||
}
|
||||
}
|
||||
Value::Undefined
|
||||
},
|
||||
} as crate::avm1::function::NativeFunction<'gc>,
|
||||
$gc_context,
|
||||
DontDelete | ReadOnly | DontEnum,
|
||||
);
|
||||
|
@ -82,19 +83,21 @@ pub fn create_movie_object<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<
|
|||
with_movie_clip_mut!(
|
||||
gc_context,
|
||||
object,
|
||||
"nextFrame" => |movie_clip: &mut MovieClip, _args| {
|
||||
movie_clip.next_frame();
|
||||
"nextFrame" => |movie_clip: &mut MovieClip<'gc>, avm: &mut Avm1<'gc>, context: &mut ActionContext<'_, 'gc, '_>, _cell: DisplayNode<'gc>, _args| {
|
||||
let mut update_context = avm.update_context(context);
|
||||
movie_clip.next_frame(&mut update_context);
|
||||
Value::Undefined
|
||||
},
|
||||
"prevFrame" => |movie_clip: &mut MovieClip, _args| {
|
||||
movie_clip.prev_frame();
|
||||
"prevFrame" => |movie_clip: &mut MovieClip<'gc>, avm: &mut Avm1<'gc>, context: &mut ActionContext<'_, 'gc, '_>, _cell: DisplayNode<'gc>, _args| {
|
||||
let mut update_context = avm.update_context(context);
|
||||
movie_clip.prev_frame(&mut update_context);
|
||||
Value::Undefined
|
||||
},
|
||||
"play" => |movie_clip: &mut MovieClip, _args| {
|
||||
"play" => |movie_clip: &mut MovieClip<'gc>, _avm: &mut Avm1<'gc>, _context: &mut ActionContext<'_, 'gc, '_>, _cell: DisplayNode<'gc>, _args| {
|
||||
movie_clip.play();
|
||||
Value::Undefined
|
||||
},
|
||||
"stop" => |movie_clip: &mut MovieClip, _args| {
|
||||
"stop" => |movie_clip: &mut MovieClip<'gc>, _avm: &mut Avm1<'gc>, _context: &mut ActionContext<'_, 'gc, '_>, _cell: DisplayNode<'gc>, _args| {
|
||||
movie_clip.stop();
|
||||
Value::Undefined
|
||||
}
|
||||
|
@ -103,11 +106,11 @@ pub fn create_movie_object<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<
|
|||
with_movie_clip!(
|
||||
gc_context,
|
||||
object,
|
||||
"getBytesLoaded" => |_movie_clip: &MovieClip, _args| {
|
||||
"getBytesLoaded" => |_movie_clip: &MovieClip<'gc>, _args| {
|
||||
// TODO find a correct value
|
||||
Value::Number(1.0)
|
||||
},
|
||||
"getBytesTotal" => |_movie_clip: &MovieClip, _args| {
|
||||
"getBytesTotal" => |_movie_clip: &MovieClip<'gc>, _args| {
|
||||
// TODO find a correct value
|
||||
Value::Number(1.0)
|
||||
}
|
||||
|
|
|
@ -373,10 +373,14 @@ mod tests {
|
|||
use crate::avm1::activation::Activation;
|
||||
use crate::backend::audio::NullAudioBackend;
|
||||
use crate::backend::navigator::NullNavigatorBackend;
|
||||
use crate::backend::render::NullRenderer;
|
||||
use crate::display_object::DisplayObject;
|
||||
use crate::library::Library;
|
||||
use crate::movie_clip::MovieClip;
|
||||
use crate::prelude::*;
|
||||
use gc_arena::rootless_arena;
|
||||
use rand::{rngs::SmallRng, SeedableRng};
|
||||
use std::sync::Arc;
|
||||
|
||||
fn with_object<F, R>(swf_version: u8, test: F) -> R
|
||||
where
|
||||
|
@ -401,9 +405,20 @@ mod tests {
|
|||
target_clip: Some(root),
|
||||
target_path: Value::Undefined,
|
||||
rng: &mut SmallRng::from_seed([0u8; 16]),
|
||||
action_queue: &mut crate::player::ActionQueue::new(),
|
||||
audio: &mut NullAudioBackend::new(),
|
||||
background_color: &mut Color {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 0,
|
||||
},
|
||||
library: &mut Library::new(),
|
||||
navigator: &mut NullNavigatorBackend::new(),
|
||||
renderer: &mut NullRenderer::new(),
|
||||
swf_data: &mut Arc::new(vec![]),
|
||||
};
|
||||
|
||||
let object = GcCell::allocate(gc_context, Object::object(gc_context));
|
||||
|
||||
let globals = avm.global_object_cell();
|
||||
|
|
|
@ -2,10 +2,15 @@ use crate::avm1::activation::Activation;
|
|||
use crate::avm1::{ActionContext, Avm1, Object, Value};
|
||||
use crate::backend::audio::NullAudioBackend;
|
||||
use crate::backend::navigator::NullNavigatorBackend;
|
||||
use crate::backend::render::NullRenderer;
|
||||
use crate::display_object::DisplayObject;
|
||||
use crate::library::Library;
|
||||
use crate::movie_clip::MovieClip;
|
||||
use crate::player::ActionQueue;
|
||||
use crate::prelude::*;
|
||||
use gc_arena::{rootless_arena, GcCell};
|
||||
use rand::{rngs::SmallRng, SeedableRng};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn with_avm<F, R>(swf_version: u8, test: F) -> R
|
||||
where
|
||||
|
@ -30,7 +35,17 @@ where
|
|||
target_path: Value::Undefined,
|
||||
rng: &mut SmallRng::from_seed([0u8; 16]),
|
||||
audio: &mut NullAudioBackend::new(),
|
||||
action_queue: &mut ActionQueue::new(),
|
||||
background_color: &mut Color {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 0,
|
||||
},
|
||||
library: &mut Library::new(),
|
||||
navigator: &mut NullNavigatorBackend::new(),
|
||||
renderer: &mut NullRenderer::new(),
|
||||
swf_data: &mut Arc::new(vec![]),
|
||||
};
|
||||
|
||||
let globals = avm.global_object_cell();
|
||||
|
|
|
@ -56,6 +56,18 @@ pub enum Letterbox {
|
|||
|
||||
pub struct NullRenderer;
|
||||
|
||||
impl NullRenderer {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NullRenderer {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderBackend for NullRenderer {
|
||||
fn set_viewport_dimensions(&mut self, _width: u32, _height: u32) {}
|
||||
fn register_shape(&mut self, _shape: &swf::Shape) -> ShapeHandle {
|
||||
|
|
|
@ -137,7 +137,9 @@ impl<'gc> Button<'gc> {
|
|||
for action in &self.static_data.actions {
|
||||
if action.condition == condition && action.key_code == key_code {
|
||||
// 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>) {
|
||||
context.transform_stack.push(self.transform());
|
||||
|
||||
|
|
|
@ -14,6 +14,20 @@ pub struct DisplayObjectBase<'gc> {
|
|||
transform: Transform,
|
||||
name: String,
|
||||
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> {
|
||||
|
@ -25,6 +39,10 @@ impl<'gc> Default for DisplayObjectBase<'gc> {
|
|||
transform: Default::default(),
|
||||
name: 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>>) {
|
||||
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>> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
@ -109,9 +151,22 @@ pub trait DisplayObject<'gc>: 'gc + Collect + Debug {
|
|||
fn set_clip_depth(&mut self, depth: Depth);
|
||||
fn parent(&self) -> 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_post_frame(&mut self, _context: &mut UpdateContext<'_, 'gc, '_>) {}
|
||||
fn render(&self, _context: &mut RenderContext<'_, '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>> {
|
||||
None
|
||||
}
|
||||
fn apply_place_object(&mut self, place_object: swf::PlaceObject) {
|
||||
if let Some(matrix) = place_object.matrix {
|
||||
self.set_matrix(&matrix.into());
|
||||
fn apply_place_object(&mut self, place_object: &swf::PlaceObject) {
|
||||
if let Some(matrix) = &place_object.matrix {
|
||||
self.set_matrix(&matrix.clone().into());
|
||||
}
|
||||
if let Some(color_transform) = place_object.color_transform {
|
||||
self.set_color_transform(&color_transform.into());
|
||||
if let Some(color_transform) = &place_object.color_transform {
|
||||
self.set_color_transform(&color_transform.clone().into());
|
||||
}
|
||||
if let Some(name) = place_object.name {
|
||||
self.set_name(&name);
|
||||
if let Some(name) = &place_object.name {
|
||||
self.set_name(name);
|
||||
}
|
||||
if let Some(clip_depth) = place_object.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>>) {
|
||||
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>> {
|
||||
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,
|
||||
/// 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 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,16 +65,16 @@ impl<'gc> DisplayObject<'gc> for EditText<'gc> {
|
|||
transform.color_transform.g_mult = f32::from(color.g) / 255.0;
|
||||
transform.color_transform.b_mult = f32::from(color.b) / 255.0;
|
||||
transform.color_transform.a_mult = f32::from(color.a) / 255.0;
|
||||
let device_font = context.library.device_font();
|
||||
// If the font can't be found or has no glyph information, use the "device font" instead.
|
||||
// We're cheating a bit and not actually rendering text using the OS/web.
|
||||
// Instead, we embed an SWF version of Noto Sans to use as the "device font", and render
|
||||
// it the same as any other SWF outline text.
|
||||
let font = context
|
||||
if let Some(font) = context
|
||||
.library
|
||||
.get_font(font_id)
|
||||
.filter(|font| font.has_glyphs())
|
||||
.unwrap_or(device_font);
|
||||
.or_else(|| context.library.device_font())
|
||||
{
|
||||
let scale = if let Some(height) = static_data.height {
|
||||
transform.matrix.ty += f32::from(height);
|
||||
f32::from(height) / font.scale()
|
||||
|
@ -113,6 +113,7 @@ impl<'gc> DisplayObject<'gc> for EditText<'gc> {
|
|||
transform.matrix.tx += advance * scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
context.transform_stack.pop();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,15 +10,15 @@ use swf::CharacterId;
|
|||
pub struct Library<'gc> {
|
||||
characters: HashMap<CharacterId, Character<'gc>>,
|
||||
jpeg_tables: Option<Vec<u8>>,
|
||||
device_font: Box<Font>,
|
||||
device_font: Option<Box<Font>>,
|
||||
}
|
||||
|
||||
impl<'gc> Library<'gc> {
|
||||
pub fn new(device_font: Box<Font>) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Library {
|
||||
characters: HashMap::new(),
|
||||
jpeg_tables: None,
|
||||
device_font,
|
||||
device_font: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,8 +95,13 @@ impl<'gc> Library<'gc> {
|
|||
}
|
||||
|
||||
/// Returns the device font for use when a font is unavailable.
|
||||
pub fn device_font(&self) -> &Font {
|
||||
&*self.device_font
|
||||
pub fn device_font(&self) -> Option<&Font> {
|
||||
self.device_font.as_ref().map(AsRef::as_ref)
|
||||
}
|
||||
|
||||
/// Sets the device font.
|
||||
pub fn set_device_font(&mut self, font: Option<Box<Font>>) {
|
||||
self.device_font = font;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,3 +113,9 @@ unsafe impl<'gc> gc_arena::Collect for Library<'gc> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Library<'_> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ pub struct MovieClip<'gc> {
|
|||
static_data: Gc<'gc, MovieClipStatic>,
|
||||
tag_stream_pos: u64,
|
||||
is_playing: bool,
|
||||
goto_queue: Vec<FrameNumber>,
|
||||
current_frame: FrameNumber,
|
||||
audio_stream: Option<AudioStreamHandle>,
|
||||
children: BTreeMap<Depth, DisplayNode<'gc>>,
|
||||
|
@ -41,7 +40,6 @@ impl<'gc> MovieClip<'gc> {
|
|||
static_data: Gc::allocate(gc_context, MovieClipStatic::default()),
|
||||
tag_stream_pos: 0,
|
||||
is_playing: false,
|
||||
goto_queue: Vec::new(),
|
||||
current_frame: 0,
|
||||
audio_stream: None,
|
||||
children: BTreeMap::new(),
|
||||
|
@ -73,7 +71,6 @@ impl<'gc> MovieClip<'gc> {
|
|||
),
|
||||
tag_stream_pos: 0,
|
||||
is_playing: true,
|
||||
goto_queue: Vec::new(),
|
||||
current_frame: 0,
|
||||
audio_stream: None,
|
||||
children: BTreeMap::new(),
|
||||
|
@ -85,9 +82,9 @@ impl<'gc> MovieClip<'gc> {
|
|||
self.is_playing
|
||||
}
|
||||
|
||||
pub fn next_frame(&mut self) {
|
||||
pub fn next_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
||||
if self.current_frame() < self.total_frames() {
|
||||
self.goto_frame(self.current_frame + 1, true);
|
||||
self.goto_frame(context, 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, context: &mut UpdateContext<'_, 'gc, '_>) {
|
||||
if self.current_frame > 1 {
|
||||
self.goto_frame(self.current_frame - 1, true);
|
||||
self.goto_frame(context, self.current_frame - 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,9 +107,14 @@ impl<'gc> MovieClip<'gc> {
|
|||
|
||||
/// Queues up a goto to the specified frame.
|
||||
/// `frame` should be 1-based.
|
||||
pub fn goto_frame(&mut self, frame: FrameNumber, stop: bool) {
|
||||
pub fn goto_frame(
|
||||
&mut self,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
frame: FrameNumber,
|
||||
stop: bool,
|
||||
) {
|
||||
if frame != self.current_frame {
|
||||
self.goto_queue.push(frame);
|
||||
self.run_goto(context, frame);
|
||||
}
|
||||
|
||||
if stop {
|
||||
|
@ -203,19 +205,6 @@ impl<'gc> MovieClip<'gc> {
|
|||
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 {
|
||||
self.static_data.tag_stream_start
|
||||
}
|
||||
|
@ -235,6 +224,7 @@ impl<'gc> MovieClip<'gc> {
|
|||
cursor.set_position(self.tag_stream_pos);
|
||||
swf::read::Reader::new(cursor, context.swf_version)
|
||||
}
|
||||
|
||||
fn run_frame_internal(
|
||||
&mut self,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
|
@ -292,13 +282,20 @@ impl<'gc> MovieClip<'gc> {
|
|||
depth: Depth,
|
||||
copy_previous_properties: bool,
|
||||
) -> Option<DisplayNode<'gc>> {
|
||||
if let Ok(child) = context
|
||||
if let Ok(child_cell) = context
|
||||
.library
|
||||
.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_place_frame(self.current_frame);
|
||||
if copy_previous_properties {
|
||||
|
@ -306,28 +303,72 @@ impl<'gc> MovieClip<'gc> {
|
|||
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 {
|
||||
log::error!("Unable to instantiate display node id {}", id);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn run_goto(&mut self, context: &mut UpdateContext<'_, 'gc, '_>, frame: FrameNumber) {
|
||||
/// Adds a child to the front of the execution list.
|
||||
/// 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:
|
||||
// 1) MovieClip timelines are stored as deltas from frame to frame,
|
||||
// so we have to step through the intermediate frames to goto a target frame.
|
||||
// For rewinds, this means starting from frame 1.
|
||||
// 2) Objects that would persist over the goto should not be recreated and destroyed,
|
||||
// they should keep their properties.
|
||||
// Particularly for rewinds, the object should persist if it was create
|
||||
// 1) Conceptually, a goto should act like the playhead is advancing forward or
|
||||
// backward to a frame.
|
||||
// 2) However, MovieClip timelines are stored as deltas from frame to frame,
|
||||
// so for rewinds, we must restart to frame 1 and play forward.
|
||||
// 3) Objects that would persist over the goto conceptually should not be
|
||||
// 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).
|
||||
// 3) 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
|
||||
// of commands at the end of the goto, and THEN create the needed objects.
|
||||
// 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 final list
|
||||
// of commands, and THEN modify the children as necessary.
|
||||
|
||||
// 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 is_rewind = if frame < self.current_frame() {
|
||||
|
@ -335,6 +376,25 @@ impl<'gc> MovieClip<'gc> {
|
|||
// when rewinding.
|
||||
self.tag_stream_pos = 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
|
||||
} else {
|
||||
false
|
||||
|
@ -343,6 +403,7 @@ impl<'gc> MovieClip<'gc> {
|
|||
// Step through the intermediate frames, and aggregate the deltas of each frame.
|
||||
let mut frame_pos = self.tag_stream_pos;
|
||||
let mut reader = self.reader(context);
|
||||
let gc_context = context.gc_context;
|
||||
while self.current_frame < frame {
|
||||
self.current_frame += 1;
|
||||
frame_pos = reader.get_inner().position();
|
||||
|
@ -362,36 +423,33 @@ impl<'gc> MovieClip<'gc> {
|
|||
self.goto_place_object(reader, tag_len, 4, &mut goto_commands)
|
||||
}
|
||||
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 => {
|
||||
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(()),
|
||||
};
|
||||
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::ShowFrame);
|
||||
}
|
||||
|
||||
let prev_active_clip = context.active_clip;
|
||||
|
||||
// Run the final list of commands.
|
||||
if is_rewind {
|
||||
// TODO: We want to do something like self.children.retain here,
|
||||
// but BTreeMap::retain does not exist.
|
||||
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() {
|
||||
// Run the list of goto commands to actually create and update the display objects.
|
||||
let run_goto_command =
|
||||
|clip: &mut MovieClip<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
(&depth, params): (&Depth, &GotoPlaceObject)| {
|
||||
let (was_instantiated, child) = match clip.children.get_mut(&depth).copied() {
|
||||
// For rewinds, if an object was created before the final frame,
|
||||
// it will exist on the final frame as well. Re-use this object
|
||||
// instead of recreating.
|
||||
Some(prev_child) if prev_child.read().place_frame() <= frame => {
|
||||
self.children.insert(depth, prev_child);
|
||||
(false, prev_child)
|
||||
}
|
||||
_ => {
|
||||
if let Some(child) =
|
||||
self.instantiate_child(context, params.id(), depth, false)
|
||||
{
|
||||
Some(prev_child) => (false, prev_child),
|
||||
None => {
|
||||
if let Some(child) = clip.instantiate_child(
|
||||
context,
|
||||
params.id(),
|
||||
depth,
|
||||
params.modifies_original_item(),
|
||||
) {
|
||||
(true, child)
|
||||
} else {
|
||||
return;
|
||||
|
@ -400,61 +458,34 @@ impl<'gc> MovieClip<'gc> {
|
|||
};
|
||||
|
||||
// Apply final delta to display pamareters.
|
||||
let child_node = child;
|
||||
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 {
|
||||
// Set the placement frame for the new object to the frame
|
||||
// it is actually created on.
|
||||
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.
|
||||
let child_node = child;
|
||||
let mut child = child.write(context.gc_context);
|
||||
child.apply_place_object(params.place_object);
|
||||
if id != 0 {
|
||||
// Set the placement frame for the new object to the frame
|
||||
// it is actually created on.
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
// Re-run the final frame to run all other tags (DoAction, StartSound, etc.)
|
||||
// We have to be sure that queued actions are generated in the same order
|
||||
// as if the playhead had reached this frame normally.
|
||||
// First, run frames for children that were created before this frame.
|
||||
goto_commands
|
||||
.iter()
|
||||
.filter(|(_, params)| params.frame < frame)
|
||||
.for_each(|goto| run_goto_command(self, context, goto));
|
||||
|
||||
// Next, run the final frame for the parent clip.
|
||||
// Re-run the final frame without display tags (DoAction, StartSound, etc.)
|
||||
self.current_frame = frame - 1;
|
||||
self.tag_stream_pos = frame_pos;
|
||||
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.
|
||||
|
@ -492,6 +523,7 @@ impl<'gc> MovieClip<'gc> {
|
|||
&mut self,
|
||||
reader: &mut SwfStream<&'a [u8]>,
|
||||
version: u8,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
goto_commands: &mut fnv::FnvHashMap<Depth, GotoPlaceObject>,
|
||||
is_rewind: bool,
|
||||
) -> DecodeResult {
|
||||
|
@ -507,7 +539,9 @@ impl<'gc> MovieClip<'gc> {
|
|||
// Don't do this for rewinds, because they conceptually
|
||||
// start from an empty display list, and we also want to examine
|
||||
// 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(())
|
||||
}
|
||||
|
@ -521,24 +555,17 @@ impl<'gc> DisplayObject<'gc> for MovieClip<'gc> {
|
|||
}
|
||||
|
||||
fn run_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
||||
if self.is_playing {
|
||||
self.run_frame_internal(context, true);
|
||||
}
|
||||
|
||||
// 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;
|
||||
// Children must run first.
|
||||
let prev_clip = context.active_clip;
|
||||
for child in self.children() {
|
||||
context.active_clip = child;
|
||||
child.write(context.gc_context).run_frame(context);
|
||||
}
|
||||
}
|
||||
context.active_clip = prev_clip;
|
||||
|
||||
fn run_post_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
||||
self.run_goto_queue(context);
|
||||
|
||||
for child in self.children.values() {
|
||||
context.active_clip = *child;
|
||||
child.write(context.gc_context).run_post_frame(context);
|
||||
// Run myself.
|
||||
if self.is_playing {
|
||||
self.run_frame_internal(context, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1207,7 +1234,9 @@ impl<'gc, 'a> MovieClip<'gc> {
|
|||
start,
|
||||
end,
|
||||
};
|
||||
context.actions.push((context.active_clip, slice));
|
||||
context
|
||||
.action_queue
|
||||
.queue_actions(context.active_clip, slice);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1238,7 +1267,7 @@ impl<'gc, 'a> MovieClip<'gc> {
|
|||
) {
|
||||
child
|
||||
.write(context.gc_context)
|
||||
.apply_place_object(place_object);
|
||||
.apply_place_object(&place_object);
|
||||
child
|
||||
} else {
|
||||
return Ok(());
|
||||
|
@ -1248,7 +1277,7 @@ impl<'gc, 'a> MovieClip<'gc> {
|
|||
if let Some(child) = self.children.get_mut(&place_object.depth) {
|
||||
child
|
||||
.write(context.gc_context)
|
||||
.apply_place_object(place_object);
|
||||
.apply_place_object(&place_object);
|
||||
*child
|
||||
} else {
|
||||
return Ok(());
|
||||
|
@ -1272,7 +1301,7 @@ impl<'gc, 'a> MovieClip<'gc> {
|
|||
reader.read_remove_object_2()
|
||||
}?;
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::events::{ButtonEvent, PlayerEvent};
|
|||
use crate::library::Library;
|
||||
use crate::movie_clip::MovieClip;
|
||||
use crate::prelude::*;
|
||||
use crate::tag_utils::SwfSlice;
|
||||
use crate::transform::TransformStack;
|
||||
use gc_arena::{make_arena, ArenaParameters, Collect, GcCell, MutationContext};
|
||||
use log::info;
|
||||
|
@ -25,6 +26,7 @@ struct GcRoot<'gc> {
|
|||
root: DisplayNode<'gc>,
|
||||
mouse_hover_node: GcCell<'gc, Option<DisplayNode<'gc>>>, // TODO: Remove GcCell wrapped inside GcCell.
|
||||
avm: GcCell<'gc, Avm1<'gc>>,
|
||||
action_queue: GcCell<'gc, ActionQueue<'gc>>,
|
||||
}
|
||||
|
||||
type Error = Box<dyn std::error::Error>;
|
||||
|
@ -117,8 +119,14 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
|||
|
||||
// Load and parse the device font.
|
||||
// TODO: We could use lazy_static here.
|
||||
let device_font = Self::load_device_font(DEVICE_FONT_TAG, &mut renderer)
|
||||
.expect("Unable to load device font");
|
||||
let device_font = match Self::load_device_font(DEVICE_FONT_TAG, &mut renderer) {
|
||||
Ok(font) => Some(font),
|
||||
Err(e) => {
|
||||
log::error!("Unable to load device font: {}", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let mut player = Player {
|
||||
player_version: NEWEST_PLAYER_VERSION,
|
||||
|
||||
|
@ -143,8 +151,11 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
|||
|
||||
rng: SmallRng::from_seed([0u8; 16]), // TODO(Herschel): Get a proper seed on all platforms.
|
||||
|
||||
gc_arena: GcArena::new(ArenaParameters::default(), |gc_context| GcRoot {
|
||||
library: GcCell::allocate(gc_context, Library::new(device_font)),
|
||||
gc_arena: GcArena::new(ArenaParameters::default(), |gc_context| {
|
||||
let mut library = Library::new();
|
||||
library.set_device_font(device_font);
|
||||
GcRoot {
|
||||
library: GcCell::allocate(gc_context, library),
|
||||
root: GcCell::allocate(
|
||||
gc_context,
|
||||
Box::new(MovieClip::new_with_data(
|
||||
|
@ -158,6 +169,8 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
|||
),
|
||||
mouse_hover_node: GcCell::allocate(gc_context, None),
|
||||
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(),
|
||||
|
@ -297,14 +310,13 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
|||
global_time,
|
||||
swf_data,
|
||||
swf_version,
|
||||
library: gc_root.library.write(gc_context),
|
||||
library: &mut *gc_root.library.write(gc_context),
|
||||
background_color,
|
||||
avm: gc_root.avm.write(gc_context),
|
||||
rng,
|
||||
renderer,
|
||||
audio,
|
||||
navigator,
|
||||
actions: vec![],
|
||||
action_queue: &mut *gc_root.action_queue.write(gc_context),
|
||||
gc_context,
|
||||
active_clip: gc_root.root,
|
||||
};
|
||||
|
@ -331,7 +343,11 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
|||
}
|
||||
}
|
||||
|
||||
Self::run_actions(&mut update_context, gc_root.root);
|
||||
Self::run_actions(
|
||||
&mut *gc_root.avm.write(gc_context),
|
||||
&mut update_context,
|
||||
gc_root.root,
|
||||
);
|
||||
});
|
||||
|
||||
if needs_render {
|
||||
|
@ -372,14 +388,13 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
|||
global_time,
|
||||
swf_data,
|
||||
swf_version,
|
||||
library: gc_root.library.write(gc_context),
|
||||
library: &mut *gc_root.library.write(gc_context),
|
||||
background_color,
|
||||
avm: gc_root.avm.write(gc_context),
|
||||
rng,
|
||||
renderer,
|
||||
audio,
|
||||
navigator,
|
||||
actions: vec![],
|
||||
action_queue: &mut *gc_root.action_queue.write(gc_context),
|
||||
gc_context,
|
||||
active_clip: gc_root.root,
|
||||
};
|
||||
|
@ -402,7 +417,11 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
|||
|
||||
*cur_hover_node = new_hover_node;
|
||||
|
||||
Self::run_actions(&mut update_context, gc_root.root);
|
||||
Self::run_actions(
|
||||
&mut *gc_root.avm.write(gc_context),
|
||||
&mut update_context,
|
||||
gc_root.root,
|
||||
);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
@ -439,14 +458,13 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
|||
global_time,
|
||||
swf_data,
|
||||
swf_version,
|
||||
library: gc_root.library.write(gc_context),
|
||||
library: &mut *gc_root.library.write(gc_context),
|
||||
background_color,
|
||||
avm: gc_root.avm.write(gc_context),
|
||||
rng,
|
||||
renderer,
|
||||
audio,
|
||||
navigator,
|
||||
actions: vec![],
|
||||
action_queue: &mut *gc_root.action_queue.write(gc_context),
|
||||
gc_context,
|
||||
active_clip: gc_root.root,
|
||||
};
|
||||
|
@ -499,14 +517,13 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
|||
global_time,
|
||||
swf_data,
|
||||
swf_version,
|
||||
library: gc_root.library.write(gc_context),
|
||||
library: &mut *gc_root.library.write(gc_context),
|
||||
background_color,
|
||||
avm: gc_root.avm.write(gc_context),
|
||||
rng,
|
||||
renderer,
|
||||
audio,
|
||||
navigator,
|
||||
actions: vec![],
|
||||
action_queue: &mut *gc_root.action_queue.write(gc_context),
|
||||
gc_context,
|
||||
active_clip: gc_root.root,
|
||||
};
|
||||
|
@ -516,7 +533,11 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
|||
.write(gc_context)
|
||||
.run_frame(&mut update_context);
|
||||
|
||||
Self::run_actions(&mut update_context, gc_root.root);
|
||||
Self::run_actions(
|
||||
&mut *gc_root.avm.write(gc_context),
|
||||
&mut update_context,
|
||||
gc_root.root,
|
||||
);
|
||||
});
|
||||
|
||||
// Update mouse state (check for new hovered button, etc.)
|
||||
|
@ -548,7 +569,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
|||
self.gc_arena.mutate(|_gc_context, gc_root| {
|
||||
let mut render_context = RenderContext {
|
||||
renderer,
|
||||
library: gc_root.library.read(),
|
||||
library: &*gc_root.library.read(),
|
||||
transform_stack,
|
||||
view_bounds,
|
||||
clip_depth_stack: vec![],
|
||||
|
@ -573,14 +594,20 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
|||
&mut self.renderer
|
||||
}
|
||||
|
||||
fn run_actions<'gc>(update_context: &mut UpdateContext<'_, 'gc, '_>, root: DisplayNode<'gc>) {
|
||||
fn run_actions<'gc>(
|
||||
avm: &mut Avm1<'gc>,
|
||||
update_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
root: DisplayNode<'gc>,
|
||||
) {
|
||||
// TODO: Loop here because goto-ing a frame can queue up for actions.
|
||||
// I think this will eventually be cleaned up;
|
||||
// Need to figure out the proper order of operations between ticking a clip
|
||||
// and running the actions.
|
||||
let mut actions = std::mem::replace(&mut update_context.actions, vec![]);
|
||||
while !actions.is_empty() {
|
||||
{
|
||||
while let Some(actions) = update_context.action_queue.pop() {
|
||||
// We don't run the action f the clip was removed after it queued the action.
|
||||
if actions.clip.read().removed() {
|
||||
continue;
|
||||
}
|
||||
let mut action_context = crate::avm1::ActionContext {
|
||||
gc_context: update_context.gc_context,
|
||||
global_time: update_context.global_time,
|
||||
|
@ -590,29 +617,25 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
|
|||
active_clip: root,
|
||||
target_clip: Some(root),
|
||||
target_path: crate::avm1::Value::Undefined,
|
||||
action_queue: &mut update_context.action_queue,
|
||||
rng: update_context.rng,
|
||||
audio: update_context.audio,
|
||||
background_color: update_context.background_color,
|
||||
library: update_context.library,
|
||||
navigator: update_context.navigator,
|
||||
renderer: update_context.renderer,
|
||||
swf_data: update_context.swf_data,
|
||||
};
|
||||
for (active_clip, action) in actions {
|
||||
action_context.start_clip = active_clip;
|
||||
action_context.active_clip = active_clip;
|
||||
action_context.target_clip = Some(active_clip);
|
||||
update_context.avm.insert_stack_frame_for_action(
|
||||
|
||||
action_context.start_clip = actions.clip;
|
||||
action_context.active_clip = actions.clip;
|
||||
action_context.target_clip = Some(actions.clip);
|
||||
avm.insert_stack_frame_for_action(
|
||||
update_context.swf_version,
|
||||
action,
|
||||
actions.actions,
|
||||
&mut action_context,
|
||||
);
|
||||
let _ = update_context.avm.run_stack_till_empty(&mut action_context);
|
||||
}
|
||||
}
|
||||
|
||||
// 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![]);
|
||||
let _ = avm.run_stack_till_empty(&mut action_context);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -674,22 +697,62 @@ pub struct UpdateContext<'a, 'gc, 'gc_context> {
|
|||
pub swf_version: u8,
|
||||
pub swf_data: &'a Arc<Vec<u8>>,
|
||||
pub global_time: u64,
|
||||
pub library: std::cell::RefMut<'a, Library<'gc>>,
|
||||
pub library: &'a mut Library<'gc>,
|
||||
pub gc_context: MutationContext<'gc, 'gc_context>,
|
||||
pub background_color: &'a mut Color,
|
||||
pub avm: std::cell::RefMut<'a, Avm1<'gc>>,
|
||||
pub renderer: &'a mut dyn RenderBackend,
|
||||
pub audio: &'a mut dyn AudioBackend,
|
||||
pub navigator: &'a mut dyn NavigatorBackend,
|
||||
pub rng: &'a mut SmallRng,
|
||||
pub actions: Vec<(DisplayNode<'gc>, crate::tag_utils::SwfSlice)>,
|
||||
pub action_queue: &'a mut ActionQueue<'gc>,
|
||||
pub active_clip: DisplayNode<'gc>,
|
||||
}
|
||||
|
||||
pub struct RenderContext<'a, 'gc> {
|
||||
pub renderer: &'a mut dyn RenderBackend,
|
||||
pub library: std::cell::Ref<'a, Library<'gc>>,
|
||||
pub library: &'a Library<'gc>,
|
||||
pub transform_stack: &'a mut TransformStack,
|
||||
pub view_bounds: BoundingBox,
|
||||
pub clip_depth_stack: Vec<Depth>,
|
||||
}
|
||||
|
||||
pub struct QueuedActions<'gc> {
|
||||
clip: DisplayNode<'gc>,
|
||||
actions: SwfSlice,
|
||||
}
|
||||
|
||||
/// Action and gotos need to be queued up to execute at the end of the frame.
|
||||
pub struct ActionQueue<'gc> {
|
||||
queue: std::collections::VecDeque<QueuedActions<'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(QueuedActions { clip, actions })
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Option<QueuedActions<'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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,9 +13,10 @@ type Error = Box<dyn std::error::Error>;
|
|||
|
||||
// This macro generates test cases for a given list of SWFs.
|
||||
macro_rules! swf_tests {
|
||||
($(($name:ident, $path:expr, $num_frames:literal),)*) => {
|
||||
($($(#[$attr:meta])* ($name:ident, $path:expr, $num_frames:literal),)*) => {
|
||||
$(
|
||||
#[test]
|
||||
$(#[$attr])*
|
||||
fn $name() -> Result<(), Error> {
|
||||
test_swf(
|
||||
concat!("tests/swfs/", $path, "/test.swf"),
|
||||
|
@ -24,7 +25,7 @@ macro_rules! swf_tests {
|
|||
)
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// List of SWFs to test.
|
||||
|
@ -32,6 +33,9 @@ macro_rules! swf_tests {
|
|||
// The test folder is a relative to core/tests/swfs
|
||||
// Inside the folder is expected to be "test.swf" and "output.txt" with the correct output.
|
||||
swf_tests! {
|
||||
(execution_order1, "avm1/execution_order1", 3),
|
||||
(execution_order2, "avm1/execution_order2", 15),
|
||||
(execution_order3, "avm1/execution_order3", 5),
|
||||
(single_frame, "avm1/single_frame", 2),
|
||||
(looping, "avm1/looping", 6),
|
||||
(goto_advance1, "avm1/goto_advance1", 10),
|
||||
|
@ -41,6 +45,7 @@ swf_tests! {
|
|||
(goto_rewind1, "avm1/goto_rewind1", 10),
|
||||
(goto_rewind2, "avm1/goto_rewind2", 10),
|
||||
(goto_rewind3, "avm1/goto_rewind3", 10),
|
||||
(goto_execution_order, "avm1/goto_execution_order", 3),
|
||||
(greaterthan_swf5, "avm1/greaterthan_swf5", 1),
|
||||
(greaterthan_swf8, "avm1/greaterthan_swf8", 1),
|
||||
(strictly_equals, "avm1/strictly_equals", 1),
|
||||
|
@ -74,7 +79,14 @@ fn test_swf(swf_path: &str, num_frames: u32, expected_output_path: &str) -> Resu
|
|||
player.run_frame();
|
||||
}
|
||||
|
||||
assert_eq!(trace_log(), expected_output);
|
||||
let trace_log = trace_log();
|
||||
if trace_log != expected_output {
|
||||
println!(
|
||||
"Ruffle output:\n{}\nExpected output:\n{}",
|
||||
trace_log, expected_output
|
||||
);
|
||||
panic!("Ruffle output did not match expected output.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
root 1
|
||||
child 1
|
||||
child 2
|
||||
root 2
|
||||
root 3
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,7 @@
|
|||
root frame 1
|
||||
gotoAndPlay(2)
|
||||
root 2
|
||||
childA frame 1
|
||||
childB frame 1
|
||||
childB frame 2
|
||||
childA frame 2
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,4 @@
|
|||
root frame 1
|
||||
gotoAndPlay(3)
|
||||
childA frame 1
|
||||
root frame 3
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,2 @@
|
|||
1
|
||||
2
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue