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
This commit is contained in:
Aaron Hill 2024-09-01 17:14:08 -04:00
parent 94bdf6b9c6
commit 514b5ad774
5 changed files with 52 additions and 38 deletions

View File

@ -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

View File

@ -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.

View File

@ -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);

View File

@ -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

View File

@ -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 { .. })
}
}
}