core: Track what part of the frame processing loop we're in
This commit is contained in:
parent
ee2454e09f
commit
ae530b5d6b
|
@ -15,6 +15,7 @@ use crate::context_menu::ContextMenuState;
|
||||||
use crate::display_object::{EditText, InteractiveObject, MovieClip, SoundTransform, Stage};
|
use crate::display_object::{EditText, InteractiveObject, MovieClip, SoundTransform, Stage};
|
||||||
use crate::external::ExternalInterface;
|
use crate::external::ExternalInterface;
|
||||||
use crate::focus_tracker::FocusTracker;
|
use crate::focus_tracker::FocusTracker;
|
||||||
|
use crate::frame_lifecycle::FramePhase;
|
||||||
use crate::library::Library;
|
use crate::library::Library;
|
||||||
use crate::loader::LoadManager;
|
use crate::loader::LoadManager;
|
||||||
use crate::player::Player;
|
use crate::player::Player;
|
||||||
|
@ -168,6 +169,11 @@ pub struct UpdateContext<'a, 'gc, 'gc_context> {
|
||||||
|
|
||||||
/// Amount of actions performed since the last timeout check
|
/// Amount of actions performed since the last timeout check
|
||||||
pub actions_since_timeout_check: &'a mut u16,
|
pub actions_since_timeout_check: &'a mut u16,
|
||||||
|
|
||||||
|
/// The current frame processing phase.
|
||||||
|
///
|
||||||
|
/// If we are not doing frame processing, then this is `FramePhase::Enter`.
|
||||||
|
pub frame_phase: &'a mut FramePhase,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience methods for controlling audio.
|
/// Convenience methods for controlling audio.
|
||||||
|
@ -327,6 +333,7 @@ impl<'a, 'gc, 'gc_context> UpdateContext<'a, 'gc, 'gc_context> {
|
||||||
time_offset: self.time_offset,
|
time_offset: self.time_offset,
|
||||||
frame_rate: self.frame_rate,
|
frame_rate: self.frame_rate,
|
||||||
actions_since_timeout_check: self.actions_since_timeout_check,
|
actions_since_timeout_check: self.actions_since_timeout_check,
|
||||||
|
frame_phase: self.frame_phase,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
//! Frame events management
|
||||||
|
//!
|
||||||
|
//! This module aids in keeping track of which frame execution phase we are in.
|
||||||
|
//!
|
||||||
|
//! For AVM2 code, display objects execute a series of discrete phases, and
|
||||||
|
//! each object is notified about the current frame phase in rendering order.
|
||||||
|
//! When objects are created, they are 'caught up' to the current frame phase
|
||||||
|
//! to ensure correct order of operations.
|
||||||
|
//!
|
||||||
|
//! AVM1 code (presumably, either on an AVM1 stage or within an `AVM1Movie`)
|
||||||
|
//! runs in one phase, with timeline operations executing with all phases
|
||||||
|
//! inline in the order that clips were originally created.
|
||||||
|
|
||||||
|
use crate::context::UpdateContext;
|
||||||
|
use crate::display_object::TDisplayObject;
|
||||||
|
|
||||||
|
/// Which phase of the frame we're currently in.
|
||||||
|
///
|
||||||
|
/// AVM2 frames exist in one of five phases: `Enter`, `Construct`, `Update`,
|
||||||
|
/// `FrameScripts`, or `Exit`.
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum FramePhase {
|
||||||
|
/// We're entering the next frame.
|
||||||
|
///
|
||||||
|
/// When movie clips enter a new frame, they must do two things:
|
||||||
|
///
|
||||||
|
/// - Remove all children that should not exist on the next frame.
|
||||||
|
/// - Increment their current frame number.
|
||||||
|
///
|
||||||
|
/// Once this phase ends, we fire `enterFrame` on the broadcast list.
|
||||||
|
Enter,
|
||||||
|
|
||||||
|
/// We're constructing children of existing display objects.
|
||||||
|
///
|
||||||
|
/// All `PlaceObject` tags should execute at this time.
|
||||||
|
///
|
||||||
|
/// Once we construct the frame, we fire `frameConstructed` on the
|
||||||
|
/// broadcast list.
|
||||||
|
Construct,
|
||||||
|
|
||||||
|
/// We're updating all display objects on the stage.
|
||||||
|
///
|
||||||
|
/// This roughly corresponds to `run_frame`; and should encompass all time
|
||||||
|
/// based display object changes that are not encompassed by the other
|
||||||
|
/// phases.
|
||||||
|
///
|
||||||
|
/// This frame phase also exists in AVM1 frames. In AVM1, it does the work
|
||||||
|
/// of `Enter`, `FrameScripts` (`DoAction` tags), and `Construct`.
|
||||||
|
Update,
|
||||||
|
|
||||||
|
/// We're running all queued frame scripts.
|
||||||
|
///
|
||||||
|
/// Frame scripts are the AS3 equivalent of old-style `DoAction` tags. They
|
||||||
|
/// are queued in the `Update` phase if the current timeline frame number
|
||||||
|
/// differs from the prior frame's one.
|
||||||
|
FrameScripts,
|
||||||
|
|
||||||
|
/// We're finishing frame processing.
|
||||||
|
///
|
||||||
|
/// When we exit a completed frame, we fire `exitFrame` on the broadcast
|
||||||
|
/// list.
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run one frame according to AVM1 frame order.
|
||||||
|
pub fn run_all_phases_avm1<'gc>(context: &mut UpdateContext<'_, 'gc, '_>) {
|
||||||
|
// In AVM1, we only ever execute the update phase, and all the work that
|
||||||
|
// would ordinarily be phased is instead run all at once in whatever order
|
||||||
|
// the SWF requests it.
|
||||||
|
*context.frame_phase = FramePhase::Update;
|
||||||
|
|
||||||
|
// AVM1 execution order is determined by the global execution list, based on instantiation order.
|
||||||
|
for clip in context.avm1.clip_exec_iter() {
|
||||||
|
if clip.removed() {
|
||||||
|
// Clean up removed objects from this frame or a previous frame.
|
||||||
|
// Can be safely removed while iterating here, because the iterator advances
|
||||||
|
// to the next node before returning the current node.
|
||||||
|
context.avm1.remove_from_exec_list(context.gc_context, clip);
|
||||||
|
} else {
|
||||||
|
clip.run_frame(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire "onLoadInit" events.
|
||||||
|
context
|
||||||
|
.load_manager
|
||||||
|
.movie_clip_on_load(context.action_queue);
|
||||||
|
|
||||||
|
*context.frame_phase = FramePhase::Enter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run one frame according to AVM2 frame order.
|
||||||
|
pub fn run_all_phases_avm2<'gc>(context: &mut UpdateContext<'_, 'gc, '_>) {
|
||||||
|
let stage = context.stage;
|
||||||
|
|
||||||
|
*context.frame_phase = FramePhase::Enter;
|
||||||
|
stage.enter_frame(context);
|
||||||
|
|
||||||
|
*context.frame_phase = FramePhase::Construct;
|
||||||
|
stage.construct_frame(context);
|
||||||
|
stage.frame_constructed(context);
|
||||||
|
|
||||||
|
*context.frame_phase = FramePhase::Update;
|
||||||
|
stage.run_frame_avm2(context);
|
||||||
|
|
||||||
|
*context.frame_phase = FramePhase::FrameScripts;
|
||||||
|
stage.run_frame_scripts(context);
|
||||||
|
|
||||||
|
*context.frame_phase = FramePhase::Exit;
|
||||||
|
stage.exit_frame(context);
|
||||||
|
|
||||||
|
*context.frame_phase = FramePhase::Enter;
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ pub(crate) mod either;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod focus_tracker;
|
pub mod focus_tracker;
|
||||||
mod font;
|
mod font;
|
||||||
|
mod frame_lifecycle;
|
||||||
mod html;
|
mod html;
|
||||||
mod library;
|
mod library;
|
||||||
pub mod loader;
|
pub mod loader;
|
||||||
|
|
|
@ -27,6 +27,7 @@ use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult, KeyCode, MouseBut
|
||||||
use crate::external::Value as ExternalValue;
|
use crate::external::Value as ExternalValue;
|
||||||
use crate::external::{ExternalInterface, ExternalInterfaceProvider};
|
use crate::external::{ExternalInterface, ExternalInterfaceProvider};
|
||||||
use crate::focus_tracker::FocusTracker;
|
use crate::focus_tracker::FocusTracker;
|
||||||
|
use crate::frame_lifecycle::{run_all_phases_avm1, run_all_phases_avm2, FramePhase};
|
||||||
use crate::library::Library;
|
use crate::library::Library;
|
||||||
use crate::loader::LoadManager;
|
use crate::loader::LoadManager;
|
||||||
use crate::locale::get_current_date_time;
|
use crate::locale::get_current_date_time;
|
||||||
|
@ -195,6 +196,8 @@ pub struct Player {
|
||||||
frame_rate: f64,
|
frame_rate: f64,
|
||||||
actions_since_timeout_check: u16,
|
actions_since_timeout_check: u16,
|
||||||
|
|
||||||
|
frame_phase: FramePhase,
|
||||||
|
|
||||||
/// A time budget for executing frames.
|
/// A time budget for executing frames.
|
||||||
/// Gained by passage of time between host frames, spent by executing SWF frames.
|
/// Gained by passage of time between host frames, spent by executing SWF frames.
|
||||||
/// This is how we support custom SWF framerates
|
/// This is how we support custom SWF framerates
|
||||||
|
@ -1257,31 +1260,9 @@ impl Player {
|
||||||
|
|
||||||
pub fn run_frame(&mut self) {
|
pub fn run_frame(&mut self) {
|
||||||
self.update(|context| {
|
self.update(|context| {
|
||||||
if context.is_action_script_3() {
|
match context.swf.avm_type() {
|
||||||
let stage = context.stage;
|
AvmType::Avm1 => run_all_phases_avm1(context),
|
||||||
stage.enter_frame(context);
|
AvmType::Avm2 => run_all_phases_avm2(context),
|
||||||
stage.construct_frame(context);
|
|
||||||
stage.frame_constructed(context);
|
|
||||||
stage.run_frame_avm2(context);
|
|
||||||
stage.run_frame_scripts(context);
|
|
||||||
stage.exit_frame(context);
|
|
||||||
} else {
|
|
||||||
// AVM1 execution order is determined by the global execution list, based on instantiation order.
|
|
||||||
for clip in context.avm1.clip_exec_iter() {
|
|
||||||
if clip.removed() {
|
|
||||||
// Clean up removed objects from this frame or a previous frame.
|
|
||||||
// Can be safely removed while iterating here, because the iterator advances
|
|
||||||
// to the next node before returning the current node.
|
|
||||||
context.avm1.remove_from_exec_list(context.gc_context, clip);
|
|
||||||
} else {
|
|
||||||
clip.run_frame(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fire "onLoadInit" events.
|
|
||||||
context
|
|
||||||
.load_manager
|
|
||||||
.movie_clip_on_load(context.action_queue);
|
|
||||||
}
|
}
|
||||||
context.update_sounds();
|
context.update_sounds();
|
||||||
});
|
});
|
||||||
|
@ -1539,6 +1520,7 @@ impl Player {
|
||||||
audio_manager,
|
audio_manager,
|
||||||
frame_rate: &mut self.frame_rate,
|
frame_rate: &mut self.frame_rate,
|
||||||
actions_since_timeout_check: &mut self.actions_since_timeout_check,
|
actions_since_timeout_check: &mut self.actions_since_timeout_check,
|
||||||
|
frame_phase: &mut self.frame_phase,
|
||||||
};
|
};
|
||||||
|
|
||||||
let old_frame_rate = *update_context.frame_rate;
|
let old_frame_rate = *update_context.frame_rate;
|
||||||
|
@ -1894,6 +1876,7 @@ impl PlayerBuilder {
|
||||||
|
|
||||||
// Timing
|
// Timing
|
||||||
frame_rate,
|
frame_rate,
|
||||||
|
frame_phase: FramePhase::Enter,
|
||||||
frame_accumulator: 0.0,
|
frame_accumulator: 0.0,
|
||||||
recent_run_frame_timings: VecDeque::with_capacity(10),
|
recent_run_frame_timings: VecDeque::with_capacity(10),
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
|
|
Loading…
Reference in New Issue