From 514b5ad774948f17d01035e0470219ba2b0ff401 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sun, 1 Sep 2024 17:14:08 -0400 Subject: [PATCH] core: Replace `on_exit_frame` with iteration over Loaders This removes the need to traverse the entire display object tree - we instead just try to fire events (when ready) on corresponding `MovieClip`s for our `Loader::Movie` instances --- core/src/avm2/object/loaderinfo_object.rs | 8 ++++- core/src/display_object.rs | 11 ++----- core/src/display_object/movie_clip.rs | 36 ++++++++++------------- core/src/frame_lifecycle.rs | 6 ---- core/src/loader.rs | 29 +++++++++++++++++- 5 files changed, 52 insertions(+), 38 deletions(-) diff --git a/core/src/avm2/object/loaderinfo_object.rs b/core/src/avm2/object/loaderinfo_object.rs index 04a54cb58..b11861ff1 100644 --- a/core/src/avm2/object/loaderinfo_object.rs +++ b/core/src/avm2/object/loaderinfo_object.rs @@ -259,12 +259,15 @@ impl<'gc> LoaderInfoObject<'gc> { self.0.complete_event_fired.set(false); } + /// Fires the 'init' and 'complete' events if they haven't been fired yet. + /// Returns `true` if both events have been fired (either as a result of + /// this call, or due to a previous call). pub fn fire_init_and_complete_events( &self, context: &mut UpdateContext<'gc>, status: u16, redirected: bool, - ) { + ) -> bool { self.0.expose_content.set(true); if !self.0.init_event_fired.get() { self.0.init_event_fired.set(true); @@ -313,8 +316,11 @@ impl<'gc> LoaderInfoObject<'gc> { self.0.complete_event_fired.set(true); let complete_evt = EventObject::bare_default_event(context, "complete"); Avm2::dispatch_event(context, complete_evt, (*self).into()); + return true; } + return false; } + true } /// Unwrap this object's loader stream diff --git a/core/src/display_object.rs b/core/src/display_object.rs index 1cbd3fa5e..89453ef51 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -8,6 +8,7 @@ use crate::avm2::{ }; use crate::context::{RenderContext, UpdateContext}; use crate::drawing::Drawing; +use crate::loader::LoadManager; use crate::prelude::*; use crate::string::{AvmString, WString}; use crate::tag_utils::SwfMovie; @@ -2070,15 +2071,7 @@ pub trait TDisplayObject<'gc>: let dobject_constr = context.avm2.classes().display_object; Avm2::broadcast_event(context, exit_frame_evt, dobject_constr); - self.on_exit_frame(context); - } - - fn on_exit_frame(&self, context: &mut UpdateContext<'gc>) { - if let Some(container) = self.as_container() { - for child in container.iter_render_list() { - child.on_exit_frame(context); - } - } + LoadManager::run_exit_frame(context); } /// Called before the child is about to be rendered. diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index 211f1aa19..b737c84af 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -503,6 +503,21 @@ impl<'gc> MovieClip<'gc> { self.0.write(gc_context).set_initialized(true); } + /// Tries to fire events from our `LoaderInfo` object if we're ready - returns + /// `true` if both `init` and `complete` have been fired + pub fn try_fire_loaderinfo_events(self, context: &mut UpdateContext<'gc>) -> bool { + if self.0.read().initialized() { + if let Some(loader_info) = self + .loader_info() + .as_ref() + .and_then(|o| o.as_loader_info_object()) + { + return loader_info.fire_init_and_complete_events(context, 0, false); + } + } + false + } + /// Preload a chunk of the movie. /// /// A "chunk" is an implementor-chosen number of tags that are parsed @@ -2725,27 +2740,6 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> { } } - fn on_exit_frame(&self, context: &mut UpdateContext<'gc>) { - // Attempt to fire an "init" event on our `LoaderInfo`. - // This fires after we've exited our first frame, but before - // but before we enter a new frame. `loader_stream_init` - // keeps track if an "init" event has already been fired, - // so this becomes a no-op after the event has been fired. - if self.0.read().initialized() { - if let Some(loader_info) = self - .loader_info() - .as_ref() - .and_then(|o| o.as_loader_info_object()) - { - loader_info.fire_init_and_complete_events(context, 0, false); - } - } - - for child in self.iter_render_list() { - child.on_exit_frame(context); - } - } - fn render_self(&self, context: &mut RenderContext<'_, 'gc>) { self.0.read().drawing.render(context); self.render_children(context); diff --git a/core/src/frame_lifecycle.rs b/core/src/frame_lifecycle.rs index 4b0e59ddd..b38408946 100644 --- a/core/src/frame_lifecycle.rs +++ b/core/src/frame_lifecycle.rs @@ -96,9 +96,6 @@ pub fn run_all_phases_avm2(context: &mut UpdateContext<'_>) { stage.run_frame_scripts(context); *context.frame_phase = FramePhase::Exit; - Avm2::each_orphan_obj(context, |orphan, context| { - orphan.on_exit_frame(context); - }); stage.exit_frame(context); // We cannot easily remove dead `GcWeak` instances from the orphan list @@ -169,9 +166,6 @@ pub fn run_inner_goto_frame<'gc>( } *context.frame_phase = FramePhase::Exit; - Avm2::each_orphan_obj(context, |orphan, context| { - orphan.on_exit_frame(context); - }); stage.exit_frame(context); // We cannot easily remove dead `GcWeak` instances from the orphan list diff --git a/core/src/loader.rs b/core/src/loader.rs index f41537846..1117b9309 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -600,6 +600,30 @@ impl<'gc> LoadManager<'gc> { did_finish } + pub fn run_exit_frame(context: &mut UpdateContext<'gc>) { + // The root movie might not have come from a loader, so check it separately. + // `fire_init_and_complete_events` is idempotent, so we unconditionally call it here + if let Some(movie) = context + .stage + .child_by_index(0) + .and_then(|o| o.as_movie_clip()) + { + movie.try_fire_loaderinfo_events(context); + } + let handles: Vec<_> = context.load_manager.0.iter().map(|(h, _)| h).collect(); + for handle in handles { + let Some(Loader::Movie { target_clip, .. }) = context.load_manager.get_loader(handle) + else { + continue; + }; + if let Some(movie) = target_clip.as_movie_clip() { + if movie.try_fire_loaderinfo_events(context) { + context.load_manager.remove_loader(handle) + } + } + } + } + /// Display a dialog allowing a user to select a file /// /// Returns a future that will be resolved when a file is selected @@ -2764,7 +2788,10 @@ impl<'gc> Loader<'gc> { false, ); } - true + // If the movie was loaded from avm1, clean it up now. If a movie (including an AVM1 movie) + // was loaded from avm2, clean it up in `run_exit_frame`, after we have a chance to fire + // the AVM2-side events + matches!(vm_data, MovieLoaderVMData::Avm1 { .. }) } } }