Skip to content

Commit

Permalink
core: Replace on_exit_frame with iteration over Loaders
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Aaron1011 committed Sep 15, 2024
1 parent 94bdf6b commit 514b5ad
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 38 deletions.
8 changes: 7 additions & 1 deletion core/src/avm2/object/loaderinfo_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
11 changes: 2 additions & 9 deletions core/src/display_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
36 changes: 15 additions & 21 deletions core/src/display_object/movie_clip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 0 additions & 6 deletions core/src/frame_lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
29 changes: 28 additions & 1 deletion core/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 { .. })
}
}
}
Expand Down

0 comments on commit 514b5ad

Please sign in to comment.