ruffle/core/src/context.rs

532 lines
18 KiB
Rust
Raw Normal View History

//! Contexts and helper types passed between functions.
use crate::avm1::Avm1;
use crate::avm1::SystemProperties;
use crate::avm1::{Object as Avm1Object, Value as Avm1Value};
2023-03-26 23:42:15 +00:00
use crate::avm2::{Avm2, Object as Avm2Object, SoundChannelObject};
use crate::backend::{
audio::{AudioBackend, AudioManager, SoundHandle, SoundInstanceHandle},
log::LogBackend,
navigator::NavigatorBackend,
storage::StorageBackend,
ui::{InputManager, UiBackend},
};
use crate::context_menu::ContextMenuState;
use crate::display_object::{EditText, InteractiveObject, MovieClip, SoundTransform, Stage};
use crate::external::ExternalInterface;
use crate::focus_tracker::FocusTracker;
use crate::frame_lifecycle::FramePhase;
use crate::library::Library;
use crate::loader::LoadManager;
use crate::player::Player;
use crate::prelude::*;
use crate::streams::StreamManager;
use crate::stub::StubCollection;
use crate::tag_utils::{SwfMovie, SwfSlice};
use crate::timer::Timers;
use core::fmt;
use gc_arena::{Collect, MutationContext};
use instant::Instant;
use rand::rngs::SmallRng;
use ruffle_render::backend::RenderBackend;
use ruffle_render::commands::CommandList;
use ruffle_render::transform::TransformStack;
use ruffle_video::backend::VideoBackend;
use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Mutex, Weak};
use std::time::Duration;
/// `UpdateContext` holds shared data that is used by the various subsystems of Ruffle.
2021-06-21 16:29:52 +00:00
/// `Player` creates this when it begins a tick and passes it through the call stack to
/// children and the VM.
pub struct UpdateContext<'a, 'gc> {
/// The queue of actions that will be run after the display list updates.
/// Display objects and actions can push actions onto the queue.
pub action_queue: &'a mut ActionQueue<'gc>,
/// The mutation context to allocate and mutate `GcCell` types.
pub gc_context: MutationContext<'gc, 'a>,
/// A collection of stubs encountered during this movie.
pub stub_tracker: &'a mut StubCollection,
/// The library containing character definitions for this SWF.
/// Used to instantiate a `DisplayObject` of a given ID.
pub library: &'a mut Library<'gc>,
/// The version of the Flash Player we are emulating.
/// TODO: This is a little confusing because this represents the player's max SWF version,
/// which is an integer (e.g. 13), but "Flash Player version" is a triplet (11.6.0), and these
/// aren't in sync. It may be better to have separate `player_swf_version` and `player_version`
/// variables.
pub player_version: u8,
2021-06-21 16:29:52 +00:00
/// Requests that the player re-renders after this execution (e.g. due to `updateAfterEvent`).
2020-07-08 21:33:11 +00:00
pub needs_render: &'a mut bool,
/// The root SWF file.
pub swf: &'a Arc<SwfMovie>,
/// The audio backend, used by display objects and AVM to play audio.
2020-10-19 00:11:25 +00:00
pub audio: &'a mut dyn AudioBackend,
/// The audio manager, manging all actively playing sounds.
pub audio_manager: &'a mut AudioManager<'gc>,
/// The navigator backend, used by the AVM to make HTTP requests and visit webpages.
pub navigator: &'a mut (dyn NavigatorBackend + 'a),
/// The renderer, used by the display objects to draw themselves.
2020-05-11 07:02:49 +00:00
pub renderer: &'a mut dyn RenderBackend,
/// The UI backend, used to detect user interactions.
pub ui: &'a mut dyn UiBackend,
/// The storage backend, used for storing persistent state
pub storage: &'a mut dyn StorageBackend,
/// The logging backend, used for trace output capturing.
///
/// **DO NOT** use this field directly, use the `avm_trace` method instead.
pub log: &'a mut dyn LogBackend,
/// The video backend, used for video decoding
pub video: &'a mut dyn VideoBackend,
/// The RNG, used by the AVM `RandomNumber` opcode, `Math.random(),` and `random()`.
pub rng: &'a mut SmallRng,
/// The current player's stage (including all loaded levels)
pub stage: Stage<'gc>,
/// The display object that the mouse is currently hovering over.
pub mouse_over_object: Option<InteractiveObject<'gc>>,
2021-06-15 18:27:55 +00:00
/// If the mouse is down, the display object that the mouse is currently pressing.
pub mouse_down_object: Option<InteractiveObject<'gc>>,
2021-06-15 18:27:55 +00:00
/// The input manager, tracking keys state.
pub input: &'a InputManager,
/// The location of the mouse when it was last over the player.
pub mouse_position: &'a (Twips, Twips),
2019-12-21 23:37:27 +00:00
/// The object being dragged via a `startDrag` action.
pub drag_object: &'a mut Option<crate::player::DragObject<'gc>>,
/// Weak reference to the player.
///
/// Recipients of an update context may upgrade the reference to ensure
/// that the player lives across I/O boundaries.
pub player: Weak<Mutex<Player>>,
/// The player's load manager.
///
/// This is required for asynchronous behavior, such as fetching data from
/// a URL.
pub load_manager: &'a mut LoadManager<'gc>,
/// The system properties
pub system: &'a mut SystemProperties,
/// The current instance ID. Used to generate default `instanceN` names.
pub instance_counter: &'a mut i32,
/// Shared objects cache
pub avm1_shared_objects: &'a mut HashMap<String, Avm1Object<'gc>>,
/// Shared objects cache
pub avm2_shared_objects: &'a mut HashMap<String, Avm2Object<'gc>>,
/// Text fields with unbound variable bindings.
pub unbound_text_fields: &'a mut Vec<EditText<'gc>>,
2020-07-08 02:06:19 +00:00
/// Timed callbacks created with `setInterval`/`setTimeout`.
pub timers: &'a mut Timers<'gc>,
2020-07-28 00:57:42 +00:00
pub current_context_menu: &'a mut Option<ContextMenuState<'gc>>,
2020-07-28 00:57:42 +00:00
/// The AVM1 global state.
pub avm1: &'a mut Avm1<'gc>,
2020-07-28 03:19:43 +00:00
/// The AVM2 global state.
pub avm2: &'a mut Avm2<'gc>,
/// External interface for (for example) JavaScript <-> ActionScript interaction
pub external_interface: &'a mut ExternalInterface<'gc>,
/// The instant at which the SWF was launched.
pub start_time: Instant,
/// The instant at which the current update started.
pub update_start: Instant,
/// The maximum amount of time that can be called before a `Error::ExecutionTimeout`
/// is raised. This defaults to 15 seconds but can be changed.
pub max_execution_duration: Duration,
/// A tracker for the current keyboard focused element
pub focus_tracker: FocusTracker<'gc>,
/// How many times getTimer() was called so far. Used to detect busy-loops.
pub times_get_time_called: u32,
/// This frame's current fake time offset, used to pretend passage of time in time functions
pub time_offset: &'a mut u32,
2021-04-21 01:20:45 +00:00
/// The current stage frame rate.
pub frame_rate: &'a mut f64,
2023-04-11 10:13:56 +00:00
/// Whether movies are prevented from changing the stage frame rate.
pub forced_frame_rate: bool,
/// Amount of actions performed since the last timeout check
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,
/// Manager of in-progress media streams.
pub stream_manager: &'a mut StreamManager<'gc>,
}
/// Convenience methods for controlling audio.
impl<'a, 'gc> UpdateContext<'a, 'gc> {
pub fn global_sound_transform(&self) -> &SoundTransform {
self.audio_manager.global_sound_transform()
}
pub fn set_global_sound_transform(&mut self, sound_transform: SoundTransform) {
self.audio_manager
.set_global_sound_transform(sound_transform);
}
/// Get the local sound transform of a single sound instance.
pub fn local_sound_transform(&self, instance: SoundInstanceHandle) -> Option<&SoundTransform> {
self.audio_manager.local_sound_transform(instance)
}
/// Set the local sound transform of a single sound instance.
pub fn set_local_sound_transform(
&mut self,
instance: SoundInstanceHandle,
sound_transform: SoundTransform,
) {
self.audio_manager
.set_local_sound_transform(instance, sound_transform);
}
pub fn start_sound(
&mut self,
sound: SoundHandle,
settings: &swf::SoundInfo,
owner: Option<DisplayObject<'gc>>,
avm1_object: Option<crate::avm1::SoundObject<'gc>>,
) -> Option<SoundInstanceHandle> {
self.audio_manager
.start_sound(self.audio, sound, settings, owner, avm1_object)
}
pub fn attach_avm2_sound_channel(
&mut self,
instance: SoundInstanceHandle,
avm2_object: SoundChannelObject<'gc>,
) {
self.audio_manager
.attach_avm2_sound_channel(instance, avm2_object);
}
pub fn stop_sound(&mut self, instance: SoundInstanceHandle) {
self.audio_manager.stop_sound(self.audio, instance)
}
pub fn stop_sounds_with_handle(&mut self, sound: SoundHandle) {
self.audio_manager
.stop_sounds_with_handle(self.audio, sound)
}
pub fn stop_sounds_with_display_object(&mut self, display_object: DisplayObject<'gc>) {
self.audio_manager
.stop_sounds_with_display_object(self.audio, display_object)
}
pub fn stop_all_sounds(&mut self) {
self.audio_manager.stop_all_sounds(self.audio)
}
pub fn is_sound_playing(&mut self, sound: SoundInstanceHandle) -> bool {
self.audio_manager.is_sound_playing(sound)
}
pub fn is_sound_playing_with_handle(&mut self, sound: SoundHandle) -> bool {
self.audio_manager.is_sound_playing_with_handle(sound)
}
pub fn start_stream(
&mut self,
stream_handle: Option<SoundHandle>,
movie_clip: MovieClip<'gc>,
frame: u16,
data: crate::tag_utils::SwfSlice,
stream_info: &swf::SoundStreamHead,
) -> Option<SoundInstanceHandle> {
self.audio_manager.start_stream(
self.audio,
stream_handle,
movie_clip,
frame,
data,
stream_info,
)
}
pub fn set_sound_transforms_dirty(&mut self) {
self.audio_manager.set_sound_transforms_dirty()
}
}
impl<'a, 'gc> UpdateContext<'a, 'gc> {
/// Transform a borrowed update context into an owned update context with
/// a shorter internal lifetime.
///
/// This is particularly useful for structures that may wish to hold an
/// update context without adding further lifetimes for its borrowing.
/// Please note that you will not be able to use the original update
/// context until this reborrowed copy has fallen out of scope.
pub fn reborrow<'b>(&'b mut self) -> UpdateContext<'b, 'gc>
where
'a: 'b,
{
UpdateContext {
action_queue: self.action_queue,
gc_context: self.gc_context,
stub_tracker: self.stub_tracker,
library: self.library,
player_version: self.player_version,
needs_render: self.needs_render,
swf: self.swf,
audio: self.audio,
audio_manager: self.audio_manager,
navigator: self.navigator,
renderer: self.renderer,
log: self.log,
ui: self.ui,
video: self.video,
storage: self.storage,
rng: self.rng,
stage: self.stage,
mouse_over_object: self.mouse_over_object,
mouse_down_object: self.mouse_down_object,
input: self.input,
mouse_position: self.mouse_position,
drag_object: self.drag_object,
player: self.player.clone(),
load_manager: self.load_manager,
system: self.system,
instance_counter: self.instance_counter,
avm1_shared_objects: self.avm1_shared_objects,
avm2_shared_objects: self.avm2_shared_objects,
unbound_text_fields: self.unbound_text_fields,
timers: self.timers,
current_context_menu: self.current_context_menu,
avm1: self.avm1,
2020-07-28 03:19:43 +00:00
avm2: self.avm2,
external_interface: self.external_interface,
start_time: self.start_time,
update_start: self.update_start,
max_execution_duration: self.max_execution_duration,
focus_tracker: self.focus_tracker,
times_get_time_called: self.times_get_time_called,
time_offset: self.time_offset,
2021-04-21 01:20:45 +00:00
frame_rate: self.frame_rate,
2023-04-11 10:13:56 +00:00
forced_frame_rate: self.forced_frame_rate,
actions_since_timeout_check: self.actions_since_timeout_check,
frame_phase: self.frame_phase,
stream_manager: self.stream_manager,
}
}
pub fn is_action_script_3(&self) -> bool {
self.swf.is_action_script_3()
}
pub fn avm_trace(&self, message: &str) {
2021-12-10 18:30:40 +00:00
self.log.avm_trace(&message.replace('\r', "\n"));
}
}
/// A queued ActionScript call.
#[derive(Collect)]
#[collect(no_drop)]
pub struct QueuedAction<'gc> {
/// The movie clip this ActionScript is running on.
pub clip: DisplayObject<'gc>,
/// The type of action this is, along with the corresponding bytecode/method data.
pub action_type: ActionType<'gc>,
/// Whether this is an unload action, which can still run if the clip is removed.
pub is_unload: bool,
}
/// Action and gotos need to be queued up to execute at the end of the frame.
2021-03-13 19:33:30 +00:00
#[derive(Collect)]
#[collect(no_drop)]
pub struct ActionQueue<'gc> {
/// Each priority is kept in a separate bucket.
action_queue: [VecDeque<QueuedAction<'gc>>; ActionQueue::NUM_PRIORITIES],
}
impl<'gc> ActionQueue<'gc> {
const DEFAULT_CAPACITY: usize = 32;
const NUM_PRIORITIES: usize = 3;
/// Crates a new `ActionQueue` with an empty queue.
pub fn new() -> Self {
let action_queue = std::array::from_fn(|_| VecDeque::with_capacity(Self::DEFAULT_CAPACITY));
Self { action_queue }
}
/// Queues an action to run for the given movie clip.
/// The action will be skipped if the clip is removed before the action runs.
pub fn queue_action(
&mut self,
clip: DisplayObject<'gc>,
action_type: ActionType<'gc>,
is_unload: bool,
) {
let priority = action_type.priority();
let action = QueuedAction {
clip,
action_type,
is_unload,
};
debug_assert!(priority < Self::NUM_PRIORITIES);
if let Some(queue) = self.action_queue.get_mut(priority) {
queue.push_back(action)
}
}
/// Sorts and drains the actions from the queue.
pub fn pop_action(&mut self) -> Option<QueuedAction<'gc>> {
self.action_queue
.iter_mut()
.rev()
.find_map(VecDeque::pop_front)
}
}
impl<'gc> Default for ActionQueue<'gc> {
fn default() -> Self {
Self::new()
}
}
/// Shared data used during rendering.
/// `Player` creates this when it renders a frame and passes it down to display objects.
pub struct RenderContext<'a, 'gc> {
/// The renderer, used by the display objects to register themselves.
pub renderer: &'a mut dyn RenderBackend,
/// The command list, used by the display objects to draw themselves.
pub commands: CommandList,
/// The GC MutationContext, used to perform any GcCell writes
/// that must occur during rendering.
pub gc_context: MutationContext<'gc, 'a>,
/// The library, which provides access to fonts and other definitions when rendering.
pub library: &'a Library<'gc>,
/// The transform stack controls the matrix and color transform as we traverse the display hierarchy.
pub transform_stack: &'a mut TransformStack,
/// Whether we're rendering offscreen. This can disable some logic like Ruffle-side render culling
pub is_offscreen: bool,
/// The current player's stage (including all loaded levels)
pub stage: Stage<'gc>,
}
/// The type of action being run.
#[derive(Clone, Collect)]
#[collect(no_drop)]
pub enum ActionType<'gc> {
/// Normal frame or event actions.
Normal { bytecode: SwfSlice },
2021-06-21 16:29:52 +00:00
/// AVM1 initialize clip event.
Initialize { bytecode: SwfSlice },
2021-06-21 16:29:52 +00:00
/// Construct a movie with a custom class or on(construct) events.
Construct {
constructor: Option<Avm1Object<'gc>>,
events: Vec<SwfSlice>,
},
/// An event handler method, e.g. `onEnterFrame`.
Method {
object: Avm1Object<'gc>,
name: &'static str,
args: Vec<Avm1Value<'gc>>,
},
2021-06-21 16:29:52 +00:00
/// A system listener method.
NotifyListeners {
listener: &'static str,
method: &'static str,
args: Vec<Avm1Value<'gc>>,
},
}
impl ActionType<'_> {
fn priority(&self) -> usize {
match self {
ActionType::Initialize { .. } => 2,
ActionType::Construct { .. } => 1,
_ => 0,
}
}
}
impl fmt::Debug for ActionType<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ActionType::Normal { bytecode } => f
.debug_struct("ActionType::Normal")
.field("bytecode", bytecode)
.finish(),
ActionType::Initialize { bytecode } => f
.debug_struct("ActionType::Initialize")
.field("bytecode", bytecode)
.finish(),
ActionType::Construct {
constructor,
events,
} => f
.debug_struct("ActionType::Construct")
.field("constructor", constructor)
.field("events", events)
.finish(),
ActionType::Method { object, name, args } => f
.debug_struct("ActionType::Method")
.field("object", object)
.field("name", name)
.field("args", args)
.finish(),
ActionType::NotifyListeners {
listener,
method,
args,
} => f
.debug_struct("ActionType::NotifyListeners")
.field("listener", listener)
.field("method", method)
.field("args", args)
.finish(),
}
}
}