2020-07-02 21:37:18 +00:00
|
|
|
use crate::avm1::activation::{Activation, ActivationIdentifier};
|
2020-05-08 05:58:12 +00:00
|
|
|
use crate::avm1::debug::VariableDumper;
|
2020-05-31 02:49:07 +00:00
|
|
|
use crate::avm1::globals::system::SystemProperties;
|
2020-06-22 00:59:38 +00:00
|
|
|
use crate::avm1::object::Object;
|
2021-01-22 00:35:46 +00:00
|
|
|
use crate::avm1::property::Attribute;
|
2020-10-11 18:35:28 +00:00
|
|
|
use crate::avm1::{Avm1, AvmString, ScriptObject, TObject, Timers, Value};
|
2020-10-07 03:48:43 +00:00
|
|
|
use crate::avm2::{Avm2, Domain as Avm2Domain};
|
2021-01-23 02:03:59 +00:00
|
|
|
use crate::backend::{
|
|
|
|
audio::{AudioBackend, AudioManager},
|
2021-01-31 00:36:45 +00:00
|
|
|
locale::LocaleBackend,
|
2021-01-23 02:03:59 +00:00
|
|
|
log::LogBackend,
|
2021-01-31 00:36:45 +00:00
|
|
|
navigator::{NavigatorBackend, RequestOptions},
|
2021-01-23 02:03:59 +00:00
|
|
|
render::RenderBackend,
|
2021-01-31 00:36:45 +00:00
|
|
|
storage::StorageBackend,
|
|
|
|
ui::{MouseCursor, UiBackend},
|
2020-10-15 03:01:48 +00:00
|
|
|
video::VideoBackend,
|
2021-01-23 02:03:59 +00:00
|
|
|
};
|
2021-01-06 09:08:47 +00:00
|
|
|
use crate::config::Letterbox;
|
2020-04-25 09:05:01 +00:00
|
|
|
use crate::context::{ActionQueue, ActionType, RenderContext, UpdateContext};
|
2021-04-15 03:29:12 +00:00
|
|
|
use crate::display_object::{EditText, MorphShape, MovieClip, Stage};
|
2020-05-23 23:51:40 +00:00
|
|
|
use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult, KeyCode, PlayerEvent};
|
2020-08-28 15:24:26 +00:00
|
|
|
use crate::external::Value as ExternalValue;
|
2020-08-27 21:27:54 +00:00
|
|
|
use crate::external::{ExternalInterface, ExternalInterfaceProvider};
|
2020-10-29 23:09:57 +00:00
|
|
|
use crate::focus_tracker::FocusTracker;
|
2019-08-26 23:38:37 +00:00
|
|
|
use crate::library::Library;
|
2020-01-10 23:28:49 +00:00
|
|
|
use crate::loader::LoadManager;
|
2019-08-26 23:38:37 +00:00
|
|
|
use crate::prelude::*;
|
2020-10-11 18:35:28 +00:00
|
|
|
use crate::property_map::PropertyMap;
|
2020-06-30 19:57:51 +00:00
|
|
|
use crate::tag_utils::SwfMovie;
|
2019-08-26 23:38:37 +00:00
|
|
|
use crate::transform::TransformStack;
|
2020-12-15 16:39:11 +00:00
|
|
|
use crate::vminterface::{AvmType, Instantiator};
|
2020-07-12 22:16:48 +00:00
|
|
|
use gc_arena::{make_arena, ArenaParameters, Collect, GcCell};
|
2020-10-11 19:10:27 +00:00
|
|
|
use instant::Instant;
|
2019-08-26 23:38:37 +00:00
|
|
|
use log::info;
|
2019-09-17 03:37:11 +00:00
|
|
|
use rand::{rngs::SmallRng, SeedableRng};
|
2021-04-15 03:29:12 +00:00
|
|
|
use std::collections::{HashMap, VecDeque};
|
2019-12-24 10:41:35 +00:00
|
|
|
use std::convert::TryFrom;
|
2019-11-08 20:09:57 +00:00
|
|
|
use std::ops::DerefMut;
|
|
|
|
use std::sync::{Arc, Mutex, Weak};
|
2020-10-11 19:10:27 +00:00
|
|
|
use std::time::Duration;
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2020-05-23 20:38:54 +00:00
|
|
|
pub static DEVICE_FONT_TAG: &[u8] = include_bytes!("../assets/noto-sans-definefont3.bin");
|
2019-10-08 04:02:09 +00:00
|
|
|
|
2019-10-09 00:07:00 +00:00
|
|
|
/// The newest known Flash Player version, serves as a default to
|
|
|
|
/// `player_version`.
|
2019-10-09 23:46:15 +00:00
|
|
|
pub const NEWEST_PLAYER_VERSION: u8 = 32;
|
2019-10-09 00:07:00 +00:00
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
#[derive(Collect)]
|
2019-11-20 20:56:57 +00:00
|
|
|
#[collect(no_drop)]
|
2019-12-07 02:29:36 +00:00
|
|
|
struct GcRoot<'gc>(GcCell<'gc, GcRootData<'gc>>);
|
|
|
|
|
|
|
|
#[derive(Collect)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
struct GcRootData<'gc> {
|
|
|
|
library: Library<'gc>,
|
2019-12-02 02:00:01 +00:00
|
|
|
|
2021-04-15 03:29:12 +00:00
|
|
|
/// The root of the display object hierarchy.
|
2019-12-02 02:00:01 +00:00
|
|
|
///
|
2021-04-15 03:29:12 +00:00
|
|
|
/// It's children are the `level`s of AVM1, it may also be directly
|
|
|
|
/// accessed in AVM2.
|
|
|
|
stage: Stage<'gc>,
|
2020-02-24 07:41:55 +00:00
|
|
|
|
2019-12-08 18:49:23 +00:00
|
|
|
mouse_hovered_object: Option<DisplayObject<'gc>>, // TODO: Remove GcCell wrapped inside GcCell.
|
2019-12-21 23:37:27 +00:00
|
|
|
|
|
|
|
/// The object being dragged via a `startDrag` action.
|
|
|
|
drag_object: Option<DragObject<'gc>>,
|
|
|
|
|
2020-02-04 02:22:44 +00:00
|
|
|
/// Interpreter state for AVM1 code.
|
|
|
|
avm1: Avm1<'gc>,
|
|
|
|
|
|
|
|
/// Interpreter state for AVM2 code.
|
|
|
|
avm2: Avm2<'gc>,
|
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
action_queue: ActionQueue<'gc>,
|
2020-01-10 23:28:49 +00:00
|
|
|
|
|
|
|
/// Object which manages asynchronous processes that need to interact with
|
|
|
|
/// data in the GC arena.
|
|
|
|
load_manager: LoadManager<'gc>,
|
2020-06-22 00:59:38 +00:00
|
|
|
|
|
|
|
shared_objects: HashMap<String, Object<'gc>>,
|
2020-06-23 04:07:27 +00:00
|
|
|
|
|
|
|
/// Text fields with unbound variable bindings.
|
|
|
|
unbound_text_fields: Vec<EditText<'gc>>,
|
2020-07-08 02:06:19 +00:00
|
|
|
|
|
|
|
/// Timed callbacks created with `setInterval`/`setTimeout`.
|
|
|
|
timers: Timers<'gc>,
|
2020-08-27 22:16:53 +00:00
|
|
|
|
2020-11-11 09:55:46 +00:00
|
|
|
/// External interface for (for example) JavaScript <-> ActionScript interaction
|
2020-08-27 22:16:53 +00:00
|
|
|
external_interface: ExternalInterface<'gc>,
|
2020-10-29 23:09:57 +00:00
|
|
|
|
|
|
|
/// A tracker for the current keyboard focused element
|
|
|
|
focus_tracker: FocusTracker<'gc>,
|
2021-01-23 02:03:59 +00:00
|
|
|
|
|
|
|
/// Manager of active sound instances.
|
|
|
|
audio_manager: AudioManager<'gc>,
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
impl<'gc> GcRootData<'gc> {
|
2019-12-08 18:49:23 +00:00
|
|
|
/// Splits out parameters for creating an `UpdateContext`
|
|
|
|
/// (because we can borrow fields of `self` independently)
|
2020-06-22 00:59:38 +00:00
|
|
|
#[allow(clippy::type_complexity)]
|
2019-12-08 18:49:23 +00:00
|
|
|
fn update_context_params(
|
2019-12-07 02:29:36 +00:00
|
|
|
&mut self,
|
|
|
|
) -> (
|
2021-04-15 03:29:12 +00:00
|
|
|
Stage<'gc>,
|
2019-12-07 02:29:36 +00:00
|
|
|
&mut Library<'gc>,
|
|
|
|
&mut ActionQueue<'gc>,
|
|
|
|
&mut Avm1<'gc>,
|
2020-02-04 02:22:44 +00:00
|
|
|
&mut Avm2<'gc>,
|
2019-12-21 23:37:27 +00:00
|
|
|
&mut Option<DragObject<'gc>>,
|
2020-01-10 23:28:49 +00:00
|
|
|
&mut LoadManager<'gc>,
|
2020-06-22 00:59:38 +00:00
|
|
|
&mut HashMap<String, Object<'gc>>,
|
2020-06-23 04:07:27 +00:00
|
|
|
&mut Vec<EditText<'gc>>,
|
2020-07-08 02:06:19 +00:00
|
|
|
&mut Timers<'gc>,
|
2020-08-27 22:16:53 +00:00
|
|
|
&mut ExternalInterface<'gc>,
|
2021-01-23 02:03:59 +00:00
|
|
|
&mut AudioManager<'gc>,
|
2019-12-07 02:29:36 +00:00
|
|
|
) {
|
|
|
|
(
|
2021-04-15 03:29:12 +00:00
|
|
|
self.stage,
|
2019-12-07 02:29:36 +00:00
|
|
|
&mut self.library,
|
|
|
|
&mut self.action_queue,
|
2020-02-04 02:22:44 +00:00
|
|
|
&mut self.avm1,
|
|
|
|
&mut self.avm2,
|
2019-12-21 23:37:27 +00:00
|
|
|
&mut self.drag_object,
|
2020-01-10 23:28:49 +00:00
|
|
|
&mut self.load_manager,
|
2020-06-22 00:59:38 +00:00
|
|
|
&mut self.shared_objects,
|
2020-06-23 04:07:27 +00:00
|
|
|
&mut self.unbound_text_fields,
|
2020-07-08 02:06:19 +00:00
|
|
|
&mut self.timers,
|
2020-08-27 22:16:53 +00:00
|
|
|
&mut self.external_interface,
|
2021-01-23 02:03:59 +00:00
|
|
|
&mut self.audio_manager,
|
2019-12-07 02:29:36 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2019-10-08 04:02:09 +00:00
|
|
|
type Error = Box<dyn std::error::Error>;
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
make_arena!(GcArena, GcRoot);
|
|
|
|
|
2019-11-08 20:09:57 +00:00
|
|
|
type Audio = Box<dyn AudioBackend>;
|
|
|
|
type Navigator = Box<dyn NavigatorBackend>;
|
|
|
|
type Renderer = Box<dyn RenderBackend>;
|
2020-06-12 18:27:20 +00:00
|
|
|
type Storage = Box<dyn StorageBackend>;
|
2020-07-08 18:26:00 +00:00
|
|
|
type Locale = Box<dyn LocaleBackend>;
|
2020-09-05 16:19:03 +00:00
|
|
|
type Log = Box<dyn LogBackend>;
|
2021-02-12 13:03:17 +00:00
|
|
|
type Ui = Box<dyn UiBackend>;
|
2020-10-15 03:01:48 +00:00
|
|
|
type Video = Box<dyn VideoBackend>;
|
2019-11-08 20:09:57 +00:00
|
|
|
|
|
|
|
pub struct Player {
|
2019-10-09 00:07:00 +00:00
|
|
|
/// The version of the player we're emulating.
|
|
|
|
///
|
|
|
|
/// This serves a few purposes, primarily for compatibility:
|
|
|
|
///
|
|
|
|
/// * ActionScript can query the player version, ostensibly for graceful
|
|
|
|
/// degradation on older platforms. Certain SWF files broke with the
|
|
|
|
/// release of Flash Player 10 because the version string contains two
|
|
|
|
/// digits. This allows the user to play those old files.
|
|
|
|
/// * Player-specific behavior that was not properly versioned in Flash
|
|
|
|
/// Player can be enabled by setting a particular player version.
|
|
|
|
player_version: u8,
|
|
|
|
|
2019-11-12 20:05:18 +00:00
|
|
|
swf: Arc<SwfMovie>,
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2021-01-09 19:36:56 +00:00
|
|
|
warn_on_unsupported_content: bool,
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
is_playing: bool,
|
2020-05-02 11:25:21 +00:00
|
|
|
needs_render: bool,
|
2019-08-26 23:38:37 +00:00
|
|
|
|
|
|
|
renderer: Renderer,
|
2021-01-31 00:36:45 +00:00
|
|
|
audio: Audio,
|
|
|
|
navigator: Navigator,
|
|
|
|
storage: Storage,
|
2020-07-08 18:26:00 +00:00
|
|
|
locale: Locale,
|
2020-09-05 16:19:03 +00:00
|
|
|
log: Log,
|
2021-02-12 13:03:17 +00:00
|
|
|
ui: Ui,
|
2020-10-15 03:01:48 +00:00
|
|
|
video: Video,
|
2021-01-31 00:36:45 +00:00
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
transform_stack: TransformStack,
|
|
|
|
|
2019-09-02 03:03:50 +00:00
|
|
|
rng: SmallRng,
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
gc_arena: GcArena,
|
|
|
|
|
|
|
|
frame_rate: f64,
|
2021-01-04 20:19:20 +00:00
|
|
|
|
|
|
|
/// A time budget for executing frames.
|
|
|
|
/// Gained by passage of time between host frames, spent by executing SWF frames.
|
|
|
|
/// This is how we support custom SWF framerates
|
|
|
|
/// and compensate for small lags by "catching up" (up to MAX_FRAMES_PER_TICK).
|
2019-08-26 23:38:37 +00:00
|
|
|
frame_accumulator: f64,
|
2021-02-03 19:43:59 +00:00
|
|
|
recent_run_frame_timings: VecDeque<f64>,
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2021-01-04 20:19:20 +00:00
|
|
|
/// Faked time passage for fooling hand-written busy-loop FPS limiters.
|
|
|
|
time_offset: u32,
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
mouse_pos: (Twips, Twips),
|
|
|
|
is_mouse_down: bool,
|
2019-11-08 20:09:57 +00:00
|
|
|
|
2020-02-25 09:45:38 +00:00
|
|
|
/// The current mouse cursor icon.
|
|
|
|
mouse_cursor: MouseCursor,
|
|
|
|
|
2020-05-27 00:52:22 +00:00
|
|
|
system: SystemProperties,
|
|
|
|
|
2020-06-18 01:18:40 +00:00
|
|
|
/// The current instance ID. Used to generate default `instanceN` names.
|
|
|
|
instance_counter: i32,
|
|
|
|
|
2020-07-08 02:06:19 +00:00
|
|
|
/// Time remaining until the next timer will fire.
|
|
|
|
time_til_next_timer: Option<f64>,
|
|
|
|
|
2020-09-15 22:28:41 +00:00
|
|
|
/// The maximum amount of time that can be called before a `Error::ExecutionTimeout`
|
|
|
|
/// is raised. This defaults to 15 seconds but can be changed.
|
|
|
|
max_execution_duration: Duration,
|
|
|
|
|
2019-11-08 20:09:57 +00:00
|
|
|
/// Self-reference to ourselves.
|
|
|
|
///
|
|
|
|
/// This is a weak reference that is upgraded and handed out in various
|
|
|
|
/// contexts to other parts of the player. It can be used to ensure the
|
|
|
|
/// player lives across `await` calls in async code.
|
|
|
|
self_reference: Option<Weak<Mutex<Self>>>,
|
2020-12-25 21:42:14 +00:00
|
|
|
|
|
|
|
/// The current frame of the main timeline, if available.
|
|
|
|
/// The first frame is frame 1.
|
|
|
|
current_frame: Option<u16>,
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2020-12-15 16:39:11 +00:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2019-11-08 20:09:57 +00:00
|
|
|
impl Player {
|
2020-10-15 03:01:48 +00:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2019-08-26 23:38:37 +00:00
|
|
|
pub fn new(
|
2020-07-23 03:18:30 +00:00
|
|
|
renderer: Renderer,
|
2019-08-26 23:38:37 +00:00
|
|
|
audio: Audio,
|
2019-09-01 19:24:04 +00:00
|
|
|
navigator: Navigator,
|
2020-06-15 16:52:49 +00:00
|
|
|
storage: Storage,
|
2020-07-08 18:26:00 +00:00
|
|
|
locale: Locale,
|
2020-10-15 03:01:48 +00:00
|
|
|
video: Video,
|
2020-09-05 16:19:03 +00:00
|
|
|
log: Log,
|
2021-02-12 13:03:17 +00:00
|
|
|
ui: Ui,
|
2019-11-08 20:09:57 +00:00
|
|
|
) -> Result<Arc<Mutex<Self>>, Error> {
|
2020-07-23 03:18:30 +00:00
|
|
|
let fake_movie = Arc::new(SwfMovie::empty(NEWEST_PLAYER_VERSION));
|
|
|
|
let movie_width = 550;
|
|
|
|
let movie_height = 400;
|
|
|
|
let frame_rate = 12.0;
|
2021-04-23 22:52:16 +00:00
|
|
|
// Disable script timeout in debug builds by default.
|
|
|
|
let max_execution_duration = if cfg!(debug_assertions) { u64::MAX } else { 15 };
|
2019-08-26 23:38:37 +00:00
|
|
|
|
|
|
|
let mut player = Player {
|
2019-10-09 00:07:00 +00:00
|
|
|
player_version: NEWEST_PLAYER_VERSION,
|
|
|
|
|
2020-07-31 00:11:33 +00:00
|
|
|
swf: fake_movie.clone(),
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2021-01-09 19:36:56 +00:00
|
|
|
warn_on_unsupported_content: true,
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
is_playing: false,
|
2020-05-02 11:25:21 +00:00
|
|
|
needs_render: true,
|
2019-08-26 23:38:37 +00:00
|
|
|
|
|
|
|
transform_stack: TransformStack::new(),
|
|
|
|
|
2020-11-26 07:46:27 +00:00
|
|
|
rng: SmallRng::seed_from_u64(chrono::Utc::now().timestamp_millis() as u64),
|
2019-09-02 03:03:50 +00:00
|
|
|
|
2019-10-26 06:44:12 +00:00
|
|
|
gc_arena: GcArena::new(ArenaParameters::default(), |gc_context| {
|
2019-12-07 02:29:36 +00:00
|
|
|
GcRoot(GcCell::allocate(
|
|
|
|
gc_context,
|
|
|
|
GcRootData {
|
2021-01-10 15:45:54 +00:00
|
|
|
library: Library::empty(gc_context),
|
2021-04-17 01:53:55 +00:00
|
|
|
stage: Stage::empty(gc_context, movie_width, movie_height),
|
2019-12-08 18:49:23 +00:00
|
|
|
mouse_hovered_object: None,
|
2019-12-21 23:37:27 +00:00
|
|
|
drag_object: None,
|
2020-02-04 02:22:44 +00:00
|
|
|
avm1: Avm1::new(gc_context, NEWEST_PLAYER_VERSION),
|
2020-02-12 19:58:33 +00:00
|
|
|
avm2: Avm2::new(gc_context),
|
2019-12-07 02:29:36 +00:00
|
|
|
action_queue: ActionQueue::new(),
|
2020-01-10 23:28:49 +00:00
|
|
|
load_manager: LoadManager::new(),
|
2020-06-22 00:59:38 +00:00
|
|
|
shared_objects: HashMap::new(),
|
2020-06-23 04:07:27 +00:00
|
|
|
unbound_text_fields: Vec::new(),
|
2020-07-08 02:06:19 +00:00
|
|
|
timers: Timers::new(),
|
2020-08-27 22:16:53 +00:00
|
|
|
external_interface: ExternalInterface::new(),
|
2020-10-29 23:09:57 +00:00
|
|
|
focus_tracker: FocusTracker::new(gc_context),
|
2021-01-23 02:03:59 +00:00
|
|
|
audio_manager: AudioManager::new(),
|
2019-12-07 02:29:36 +00:00
|
|
|
},
|
|
|
|
))
|
2019-08-26 23:38:37 +00:00
|
|
|
}),
|
|
|
|
|
2020-07-23 03:18:30 +00:00
|
|
|
frame_rate,
|
2019-08-26 23:38:37 +00:00
|
|
|
frame_accumulator: 0.0,
|
2021-02-03 19:43:59 +00:00
|
|
|
recent_run_frame_timings: VecDeque::with_capacity(10),
|
2021-01-04 20:19:20 +00:00
|
|
|
time_offset: 0,
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2021-03-12 08:10:14 +00:00
|
|
|
mouse_pos: (Twips::zero(), Twips::zero()),
|
2019-08-26 23:38:37 +00:00
|
|
|
is_mouse_down: false,
|
2020-02-25 09:45:38 +00:00
|
|
|
mouse_cursor: MouseCursor::Arrow,
|
2019-12-17 05:21:59 +00:00
|
|
|
|
|
|
|
renderer,
|
|
|
|
audio,
|
|
|
|
navigator,
|
2020-07-08 18:26:00 +00:00
|
|
|
locale,
|
2020-09-05 16:19:03 +00:00
|
|
|
log,
|
2021-01-31 00:36:45 +00:00
|
|
|
ui,
|
2020-10-15 03:01:48 +00:00
|
|
|
video,
|
2019-11-08 20:09:57 +00:00
|
|
|
self_reference: None,
|
2020-05-31 02:49:07 +00:00
|
|
|
system: SystemProperties::default(),
|
2020-06-18 01:18:40 +00:00
|
|
|
instance_counter: 0,
|
2020-07-08 02:06:19 +00:00
|
|
|
time_til_next_timer: None,
|
2020-06-15 16:52:49 +00:00
|
|
|
storage,
|
2021-04-23 22:52:16 +00:00
|
|
|
max_execution_duration: Duration::from_secs(max_execution_duration),
|
2020-12-25 21:42:14 +00:00
|
|
|
current_frame: None,
|
2019-08-26 23:38:37 +00:00
|
|
|
};
|
|
|
|
|
2020-09-04 04:15:27 +00:00
|
|
|
player.mutate_with_update_context(|context| {
|
|
|
|
// Instantiate an empty root before the main movie loads.
|
2020-11-25 00:35:15 +00:00
|
|
|
let fake_root = MovieClip::from_movie(context.gc_context, fake_movie);
|
2020-07-21 04:24:02 +00:00
|
|
|
fake_root.post_instantiation(
|
|
|
|
context,
|
|
|
|
fake_root.into(),
|
|
|
|
None,
|
|
|
|
Instantiator::Movie,
|
|
|
|
false,
|
|
|
|
);
|
2021-04-15 03:29:12 +00:00
|
|
|
context.stage.replace_at_depth(context, fake_root.into(), 0);
|
2020-09-04 04:15:27 +00:00
|
|
|
|
2021-04-16 21:55:57 +00:00
|
|
|
let result = Avm2::load_player_globals(context);
|
|
|
|
|
2021-04-17 01:53:55 +00:00
|
|
|
let stage = context.stage;
|
2021-04-16 21:55:57 +00:00
|
|
|
stage.build_matrices(context);
|
|
|
|
|
|
|
|
result
|
2020-09-04 04:15:27 +00:00
|
|
|
})?;
|
|
|
|
|
2020-07-23 03:28:19 +00:00
|
|
|
player.audio.set_frame_rate(frame_rate);
|
2020-07-23 03:18:30 +00:00
|
|
|
let player_box = Arc::new(Mutex::new(player));
|
|
|
|
let mut player_lock = player_box.lock().unwrap();
|
|
|
|
player_lock.self_reference = Some(Arc::downgrade(&player_box));
|
2020-12-15 16:39:11 +00:00
|
|
|
|
2020-07-23 03:18:30 +00:00
|
|
|
std::mem::drop(player_lock);
|
|
|
|
|
|
|
|
Ok(player_box)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Fetch the root movie.
|
|
|
|
///
|
|
|
|
/// This should not be called if a root movie fetch has already been kicked
|
|
|
|
/// off.
|
2021-04-21 21:26:06 +00:00
|
|
|
pub fn fetch_root_movie(
|
|
|
|
&mut self,
|
|
|
|
movie_url: &str,
|
|
|
|
parameters: PropertyMap<String>,
|
|
|
|
on_metadata: Box<dyn FnOnce(&swf::Header)>,
|
|
|
|
) {
|
2020-07-28 03:19:43 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2020-07-23 03:18:30 +00:00
|
|
|
let fetch = context.navigator.fetch(movie_url, RequestOptions::get());
|
|
|
|
let process = context.load_manager.load_root_movie(
|
|
|
|
context.player.clone().unwrap(),
|
|
|
|
fetch,
|
|
|
|
movie_url.to_string(),
|
2020-10-11 18:35:28 +00:00
|
|
|
parameters,
|
2021-04-21 21:26:06 +00:00
|
|
|
on_metadata,
|
2020-07-23 03:18:30 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
context.navigator.spawn_future(process);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Change the root movie.
|
|
|
|
///
|
|
|
|
/// This should only be called once, as it makes no attempt at removing
|
|
|
|
/// previous stage contents. If you need to load a new root movie, you
|
|
|
|
/// should destroy and recreate the player instance.
|
|
|
|
pub fn set_root_movie(&mut self, movie: Arc<SwfMovie>) {
|
|
|
|
info!(
|
|
|
|
"Loaded SWF version {}, with a resolution of {}x{}",
|
|
|
|
movie.header().version,
|
|
|
|
movie.header().stage_size.x_max,
|
|
|
|
movie.header().stage_size.y_max
|
|
|
|
);
|
|
|
|
|
|
|
|
self.frame_rate = movie.header().frame_rate.into();
|
|
|
|
self.swf = movie;
|
2020-09-04 04:15:27 +00:00
|
|
|
self.instance_counter = 0;
|
2020-07-23 03:18:30 +00:00
|
|
|
|
2020-07-28 03:19:43 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2021-04-17 01:53:55 +00:00
|
|
|
context.stage.set_stage_size(
|
|
|
|
context.gc_context,
|
|
|
|
context.swf.width(),
|
|
|
|
context.swf.height(),
|
|
|
|
);
|
2020-10-07 03:48:43 +00:00
|
|
|
let domain = Avm2Domain::movie_domain(context.gc_context, context.avm2.global_domain());
|
|
|
|
context
|
|
|
|
.library
|
|
|
|
.library_for_movie_mut(context.swf.clone())
|
|
|
|
.set_avm2_domain(domain);
|
2020-10-06 02:24:59 +00:00
|
|
|
|
2020-11-25 00:35:15 +00:00
|
|
|
let root: DisplayObject =
|
2020-07-23 03:18:30 +00:00
|
|
|
MovieClip::from_movie(context.gc_context, context.swf.clone()).into();
|
2020-10-06 02:24:59 +00:00
|
|
|
|
2020-01-20 21:38:23 +00:00
|
|
|
root.set_depth(context.gc_context, 0);
|
2020-10-11 18:35:28 +00:00
|
|
|
let flashvars = if !context.swf.parameters().is_empty() {
|
|
|
|
let object = ScriptObject::object(context.gc_context, None);
|
|
|
|
for (key, value) in context.swf.parameters().iter() {
|
|
|
|
object.define_value(
|
|
|
|
context.gc_context,
|
|
|
|
key,
|
|
|
|
AvmString::new(context.gc_context, value).into(),
|
2021-01-22 00:35:46 +00:00
|
|
|
Attribute::empty(),
|
2020-10-11 18:35:28 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
Some(object.into())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2021-04-09 01:22:43 +00:00
|
|
|
|
2020-10-11 18:35:28 +00:00
|
|
|
root.post_instantiation(context, root, flashvars, Instantiator::Movie, false);
|
2020-11-28 18:03:53 +00:00
|
|
|
root.set_default_root_name(context);
|
2021-04-15 03:29:12 +00:00
|
|
|
context.stage.replace_at_depth(context, root, 0);
|
2020-06-11 02:29:27 +00:00
|
|
|
|
2020-07-23 03:18:30 +00:00
|
|
|
// Load and parse the device font.
|
2021-04-10 19:44:41 +00:00
|
|
|
if context.library.device_font().is_none() {
|
|
|
|
let device_font =
|
|
|
|
Self::load_device_font(context.gc_context, DEVICE_FONT_TAG, context.renderer);
|
|
|
|
if let Err(e) = &device_font {
|
|
|
|
log::error!("Unable to load device font: {}", e);
|
|
|
|
}
|
|
|
|
context.library.set_device_font(device_font.ok());
|
2021-01-23 00:03:25 +00:00
|
|
|
}
|
2020-07-23 03:18:30 +00:00
|
|
|
|
|
|
|
// Set the version parameter on the root.
|
2020-07-26 02:11:38 +00:00
|
|
|
let mut activation = Activation::from_stub(
|
2020-07-28 01:27:02 +00:00
|
|
|
context.reborrow(),
|
2020-07-02 21:37:18 +00:00
|
|
|
ActivationIdentifier::root("[Version Setter]"),
|
2020-06-30 19:57:51 +00:00
|
|
|
);
|
2020-07-27 23:19:05 +00:00
|
|
|
let object = root.object().coerce_to_object(&mut activation);
|
2020-07-28 00:57:42 +00:00
|
|
|
let version_string = activation
|
|
|
|
.context
|
|
|
|
.system
|
|
|
|
.get_version_string(activation.context.avm1);
|
2020-06-30 19:57:51 +00:00
|
|
|
object.define_value(
|
2020-07-27 23:19:05 +00:00
|
|
|
activation.context.gc_context,
|
2020-06-30 19:57:51 +00:00
|
|
|
"$version",
|
2020-07-27 23:19:05 +00:00
|
|
|
AvmString::new(activation.context.gc_context, version_string).into(),
|
2021-01-22 00:35:46 +00:00
|
|
|
Attribute::empty(),
|
2020-06-18 22:15:26 +00:00
|
|
|
);
|
2021-04-16 21:55:57 +00:00
|
|
|
|
2021-04-17 01:53:55 +00:00
|
|
|
let stage = activation.context.stage;
|
2021-04-16 21:55:57 +00:00
|
|
|
stage.build_matrices(&mut activation.context);
|
2019-10-07 08:36:05 +00:00
|
|
|
});
|
|
|
|
|
2020-07-23 03:18:30 +00:00
|
|
|
self.preload();
|
2020-07-23 03:28:19 +00:00
|
|
|
self.audio.set_frame_rate(self.frame_rate);
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2021-02-03 19:43:59 +00:00
|
|
|
/// Get rough estimate of the max # of times we can update the frame.
|
|
|
|
///
|
|
|
|
/// In some cases, we might want to update several times in a row.
|
|
|
|
/// For example, if the game runs at 60FPS, but the host runs at 30FPS
|
|
|
|
/// Or if for some reason the we miss a couple of frames.
|
|
|
|
/// However, if the code is simply slow, this is the opposite of what we want;
|
|
|
|
/// If run_frame() consistently takes say 100ms, we don't want `tick` to try to "catch up",
|
|
|
|
/// as this will only make it worse.
|
|
|
|
///
|
|
|
|
/// This rough heuristic manages this job; for example if average run_frame()
|
|
|
|
/// takes more than 1/3 of frame_time, we shouldn't run it more than twice in a row.
|
|
|
|
/// This logic is far from perfect, as it doesn't take into account
|
|
|
|
/// that things like rendering also take time. But for now it's good enough.
|
|
|
|
fn max_frames_per_tick(&self) -> u32 {
|
|
|
|
const MAX_FRAMES_PER_TICK: u32 = 5;
|
|
|
|
|
|
|
|
if self.recent_run_frame_timings.is_empty() {
|
|
|
|
5
|
|
|
|
} else {
|
|
|
|
let frame_time = 1000.0 / self.frame_rate;
|
|
|
|
let average_run_frame_time = self.recent_run_frame_timings.iter().sum::<f64>()
|
|
|
|
/ self.recent_run_frame_timings.len() as f64;
|
|
|
|
((frame_time / average_run_frame_time) as u32)
|
|
|
|
.max(1)
|
|
|
|
.min(MAX_FRAMES_PER_TICK)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_frame_timing(&mut self, elapsed: f64) {
|
|
|
|
self.recent_run_frame_timings.push_back(elapsed);
|
|
|
|
if self.recent_run_frame_timings.len() >= 10 {
|
|
|
|
self.recent_run_frame_timings.pop_front();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
pub fn tick(&mut self, dt: f64) {
|
|
|
|
// Don't run until preloading is complete.
|
|
|
|
// TODO: Eventually we want to stream content similar to the Flash player.
|
|
|
|
if !self.audio.is_loading_complete() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.is_playing() {
|
|
|
|
self.frame_accumulator += dt;
|
|
|
|
let frame_time = 1000.0 / self.frame_rate;
|
|
|
|
|
2021-02-03 19:43:59 +00:00
|
|
|
let max_frames_per_tick = self.max_frames_per_tick();
|
2019-08-26 23:38:37 +00:00
|
|
|
let mut frame = 0;
|
2021-02-03 19:43:59 +00:00
|
|
|
|
|
|
|
while frame < max_frames_per_tick && self.frame_accumulator >= frame_time {
|
|
|
|
let timer = Instant::now();
|
2019-08-26 23:38:37 +00:00
|
|
|
self.run_frame();
|
2021-02-03 19:43:59 +00:00
|
|
|
let elapsed = timer.elapsed().as_millis() as f64;
|
2021-01-04 20:19:20 +00:00
|
|
|
|
2021-02-03 19:43:59 +00:00
|
|
|
self.add_frame_timing(elapsed);
|
|
|
|
|
|
|
|
self.frame_accumulator -= frame_time;
|
|
|
|
frame += 1;
|
2021-01-04 20:19:20 +00:00
|
|
|
// The script probably tried implementing an FPS limiter with a busy loop.
|
|
|
|
// We fooled the busy loop by pretending that more time has passed that actually did.
|
|
|
|
// Then we need to actually pass this time, by decreasing frame_accumulator
|
|
|
|
// to delay the future frame.
|
|
|
|
if self.time_offset > 0 {
|
|
|
|
self.frame_accumulator -= self.time_offset as f64;
|
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2021-01-04 20:19:20 +00:00
|
|
|
// Now that we're done running code,
|
|
|
|
// we can stop pretending that more time passed than actually did.
|
|
|
|
// Note: update_timers(dt) doesn't need to see this either.
|
|
|
|
// Timers will run at correct times and see correct time.
|
|
|
|
// Also note that in Flash, a blocking busy loop would delay setTimeout
|
|
|
|
// and cancel some setInterval callbacks, but here busy loops don't block
|
|
|
|
// so timer callbacks won't get cancelled/delayed.
|
|
|
|
self.time_offset = 0;
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
// Sanity: If we had too many frames to tick, just reset the accumulator
|
|
|
|
// to prevent running at turbo speed.
|
|
|
|
if self.frame_accumulator >= frame_time {
|
|
|
|
self.frame_accumulator = 0.0;
|
|
|
|
}
|
|
|
|
|
2020-07-08 02:06:19 +00:00
|
|
|
self.update_timers(dt);
|
2019-08-26 23:38:37 +00:00
|
|
|
self.audio.tick();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-29 03:35:26 +00:00
|
|
|
/// Returns the approximate duration of time until the next frame is due to run.
|
|
|
|
/// This is only an approximation to be used for sleep durations.
|
|
|
|
pub fn time_til_next_frame(&self) -> std::time::Duration {
|
|
|
|
let frame_time = 1000.0 / self.frame_rate;
|
2020-07-08 02:06:19 +00:00
|
|
|
let mut dt = if self.frame_accumulator <= 0.0 {
|
2019-10-29 03:35:26 +00:00
|
|
|
frame_time
|
|
|
|
} else if self.frame_accumulator >= frame_time {
|
|
|
|
0.0
|
|
|
|
} else {
|
|
|
|
frame_time - self.frame_accumulator
|
|
|
|
};
|
2020-07-08 02:06:19 +00:00
|
|
|
|
|
|
|
if let Some(time_til_next_timer) = self.time_til_next_timer {
|
|
|
|
dt = dt.min(time_til_next_timer)
|
|
|
|
}
|
|
|
|
|
|
|
|
dt = dt.max(0.0);
|
|
|
|
|
2019-10-29 03:35:26 +00:00
|
|
|
std::time::Duration::from_micros(dt as u64 * 1000)
|
|
|
|
}
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
pub fn is_playing(&self) -> bool {
|
|
|
|
self.is_playing
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_is_playing(&mut self, v: bool) {
|
|
|
|
if v {
|
|
|
|
// Allow auto-play after user gesture for web backends.
|
2020-09-23 01:18:22 +00:00
|
|
|
self.audio.play();
|
2020-09-18 22:52:35 +00:00
|
|
|
} else {
|
2020-09-23 01:18:22 +00:00
|
|
|
self.audio.pause();
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
self.is_playing = v;
|
|
|
|
}
|
|
|
|
|
2020-05-02 11:25:21 +00:00
|
|
|
pub fn needs_render(&self) -> bool {
|
|
|
|
self.needs_render
|
|
|
|
}
|
|
|
|
|
2021-04-16 21:55:57 +00:00
|
|
|
pub fn background_color(&mut self) -> Option<Color> {
|
|
|
|
self.mutate_with_update_context(|context| context.stage.background_color())
|
2021-01-13 08:54:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_background_color(&mut self, color: Option<Color>) {
|
2021-04-16 21:55:57 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
context
|
|
|
|
.stage
|
|
|
|
.set_background_color(context.gc_context, color)
|
|
|
|
})
|
2021-01-13 08:54:23 +00:00
|
|
|
}
|
|
|
|
|
2021-04-16 21:55:57 +00:00
|
|
|
pub fn letterbox(&mut self) -> Letterbox {
|
|
|
|
self.mutate_with_update_context(|context| context.stage.letterbox())
|
2021-01-06 00:41:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_letterbox(&mut self, letterbox: Letterbox) {
|
2021-04-16 21:55:57 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
context.stage.set_letterbox(context.gc_context, letterbox)
|
|
|
|
})
|
2021-01-07 07:58:21 +00:00
|
|
|
}
|
|
|
|
|
2021-01-09 19:36:56 +00:00
|
|
|
pub fn warn_on_unsupported_content(&self) -> bool {
|
|
|
|
self.warn_on_unsupported_content
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_warn_on_unsupported_content(&mut self, warn_on_unsupported_content: bool) {
|
|
|
|
self.warn_on_unsupported_content = warn_on_unsupported_content
|
|
|
|
}
|
|
|
|
|
2021-04-17 01:53:55 +00:00
|
|
|
pub fn movie_width(&mut self) -> u32 {
|
|
|
|
self.mutate_with_update_context(|context| context.stage.stage_size().0)
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2021-04-17 01:53:55 +00:00
|
|
|
pub fn movie_height(&mut self) -> u32 {
|
|
|
|
self.mutate_with_update_context(|context| context.stage.stage_size().1)
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2021-04-17 01:53:55 +00:00
|
|
|
pub fn viewport_dimensions(&mut self) -> (u32, u32) {
|
|
|
|
self.mutate_with_update_context(|context| context.stage.viewport_size())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_viewport_dimensions(&mut self, width: u32, height: u32) {
|
2021-04-16 21:55:57 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2021-04-17 01:53:55 +00:00
|
|
|
let stage = context.stage;
|
|
|
|
stage.set_viewport_size(context, width, height);
|
2021-04-16 21:55:57 +00:00
|
|
|
})
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_event(&mut self, event: PlayerEvent) {
|
2020-05-02 11:25:21 +00:00
|
|
|
let mut needs_render = self.needs_render;
|
2021-04-16 21:55:57 +00:00
|
|
|
let inverse_view_matrix =
|
|
|
|
self.mutate_with_update_context(|context| context.stage.inverse_view_matrix());
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2020-07-25 18:40:03 +00:00
|
|
|
if cfg!(feature = "avm_debug") {
|
|
|
|
if let PlayerEvent::KeyDown {
|
|
|
|
key_code: KeyCode::V,
|
|
|
|
} = event
|
|
|
|
{
|
2021-01-31 00:36:45 +00:00
|
|
|
if self.ui.is_key_down(KeyCode::Control) && self.ui.is_key_down(KeyCode::Alt) {
|
2020-07-28 03:19:43 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2020-07-25 18:40:03 +00:00
|
|
|
let mut dumper = VariableDumper::new(" ");
|
2021-04-15 03:29:12 +00:00
|
|
|
let levels: Vec<_> = context.stage.iter_depth_list().collect();
|
2020-07-25 18:40:03 +00:00
|
|
|
|
2020-07-26 02:11:38 +00:00
|
|
|
let mut activation = Activation::from_stub(
|
2020-07-28 01:27:02 +00:00
|
|
|
context.reborrow(),
|
2020-07-25 18:40:03 +00:00
|
|
|
ActivationIdentifier::root("[Variable Dumper]"),
|
|
|
|
);
|
|
|
|
|
2020-06-30 19:57:51 +00:00
|
|
|
dumper.print_variables(
|
2020-07-25 18:40:03 +00:00
|
|
|
"Global Variables:",
|
|
|
|
"_global",
|
2020-07-28 00:57:42 +00:00
|
|
|
&activation.context.avm1.global_object_cell(),
|
2020-06-30 19:57:51 +00:00
|
|
|
&mut activation,
|
|
|
|
);
|
2020-07-26 02:11:38 +00:00
|
|
|
|
2020-07-25 18:40:03 +00:00
|
|
|
for (level, display_object) in levels {
|
2020-07-27 23:19:05 +00:00
|
|
|
let object = display_object.object().coerce_to_object(&mut activation);
|
2020-07-25 18:40:03 +00:00
|
|
|
dumper.print_variables(
|
|
|
|
&format!("Level #{}:", level),
|
|
|
|
&format!("_level{}", level),
|
|
|
|
&object,
|
|
|
|
&mut activation,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
log::info!("Variable dump:\n{}", dumper.output());
|
|
|
|
});
|
|
|
|
}
|
2020-05-08 05:58:12 +00:00
|
|
|
}
|
|
|
|
|
2020-07-23 20:19:52 +00:00
|
|
|
if let PlayerEvent::KeyDown {
|
|
|
|
key_code: KeyCode::D,
|
|
|
|
} = event
|
|
|
|
{
|
2021-01-31 00:36:45 +00:00
|
|
|
if self.ui.is_key_down(KeyCode::Control) && self.ui.is_key_down(KeyCode::Alt) {
|
2020-07-28 03:19:43 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2020-07-28 00:57:42 +00:00
|
|
|
if context.avm1.show_debug_output() {
|
2020-07-23 20:19:52 +00:00
|
|
|
log::info!(
|
|
|
|
"AVM Debugging turned off! Press CTRL+ALT+D to turn off again."
|
|
|
|
);
|
2020-07-28 00:57:42 +00:00
|
|
|
context.avm1.set_show_debug_output(false);
|
2020-07-28 03:19:43 +00:00
|
|
|
context.avm2.set_show_debug_output(false);
|
2020-07-23 20:19:52 +00:00
|
|
|
} else {
|
|
|
|
log::info!(
|
|
|
|
"AVM Debugging turned on! Press CTRL+ALT+D to turn on again."
|
|
|
|
);
|
2020-07-28 00:57:42 +00:00
|
|
|
context.avm1.set_show_debug_output(true);
|
2020-07-28 03:19:43 +00:00
|
|
|
context.avm2.set_show_debug_output(true);
|
2020-07-23 20:19:52 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
// Update mouse position from mouse events.
|
|
|
|
if let PlayerEvent::MouseMove { x, y }
|
|
|
|
| PlayerEvent::MouseDown { x, y }
|
|
|
|
| PlayerEvent::MouseUp { x, y } = event
|
|
|
|
{
|
2021-04-16 21:55:57 +00:00
|
|
|
self.mouse_pos = inverse_view_matrix * (Twips::from_pixels(x), Twips::from_pixels(y));
|
2019-08-26 23:38:37 +00:00
|
|
|
if self.update_roll_over() {
|
|
|
|
needs_render = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-24 10:41:35 +00:00
|
|
|
// Propagate button events.
|
|
|
|
let button_event = match event {
|
|
|
|
// ASCII characters convert directly to keyPress button events.
|
|
|
|
PlayerEvent::TextInput { codepoint }
|
|
|
|
if codepoint as u32 >= 32 && codepoint as u32 <= 126 =>
|
|
|
|
{
|
2020-05-23 23:51:40 +00:00
|
|
|
Some(ClipEvent::KeyPress {
|
2019-12-24 10:41:35 +00:00
|
|
|
key_code: ButtonKeyCode::try_from(codepoint as u8).unwrap(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Special keys have custom values for keyPress.
|
|
|
|
PlayerEvent::KeyDown { key_code } => {
|
|
|
|
if let Some(key_code) = crate::events::key_code_to_button_key_code(key_code) {
|
2020-05-23 23:51:40 +00:00
|
|
|
Some(ClipEvent::KeyPress { key_code })
|
2019-12-24 10:41:35 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
};
|
|
|
|
|
|
|
|
if button_event.is_some() {
|
2020-07-28 03:19:43 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2021-04-15 03:29:12 +00:00
|
|
|
let levels: Vec<_> = context.stage.iter_depth_list().collect();
|
|
|
|
for (_depth, level) in levels {
|
2020-01-30 04:21:06 +00:00
|
|
|
if let Some(button_event) = button_event {
|
2020-07-28 00:57:42 +00:00
|
|
|
let state = level.handle_clip_event(context, button_event);
|
2020-05-23 23:51:40 +00:00
|
|
|
if state == ClipEventResult::Handled {
|
2020-01-30 04:21:06 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2019-12-24 10:41:35 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2019-12-16 10:31:54 +00:00
|
|
|
|
2020-10-31 21:59:52 +00:00
|
|
|
if let PlayerEvent::TextInput { codepoint } = event {
|
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
if let Some(text) = context.focus_tracker.get().and_then(|o| o.as_edit_text()) {
|
|
|
|
text.text_input(codepoint, context);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-12-15 16:39:11 +00:00
|
|
|
// Propagate clip events.
|
2020-08-22 00:03:38 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
let (clip_event, listener) = match event {
|
|
|
|
PlayerEvent::KeyDown { .. } => {
|
|
|
|
(Some(ClipEvent::KeyDown), Some(("Key", "onKeyDown", vec![])))
|
|
|
|
}
|
|
|
|
PlayerEvent::KeyUp { .. } => {
|
|
|
|
(Some(ClipEvent::KeyUp), Some(("Key", "onKeyUp", vec![])))
|
|
|
|
}
|
|
|
|
PlayerEvent::MouseMove { .. } => (
|
|
|
|
Some(ClipEvent::MouseMove),
|
|
|
|
Some(("Mouse", "onMouseMove", vec![])),
|
|
|
|
),
|
|
|
|
PlayerEvent::MouseUp { .. } => (
|
|
|
|
Some(ClipEvent::MouseUp),
|
|
|
|
Some(("Mouse", "onMouseUp", vec![])),
|
|
|
|
),
|
|
|
|
PlayerEvent::MouseDown { .. } => (
|
|
|
|
Some(ClipEvent::MouseDown),
|
|
|
|
Some(("Mouse", "onMouseDown", vec![])),
|
|
|
|
),
|
|
|
|
PlayerEvent::MouseWheel { delta } => {
|
|
|
|
let delta = Value::from(delta.lines());
|
|
|
|
(None, Some(("Mouse", "onMouseWheel", vec![delta])))
|
|
|
|
}
|
|
|
|
_ => (None, None),
|
|
|
|
};
|
2019-12-16 21:58:31 +00:00
|
|
|
|
2020-08-22 00:03:38 +00:00
|
|
|
// Fire clip event on all clips.
|
|
|
|
if let Some(clip_event) = clip_event {
|
2021-04-15 03:29:12 +00:00
|
|
|
let levels: Vec<_> = context.stage.iter_depth_list().collect();
|
|
|
|
for (_depth, level) in levels {
|
2020-08-22 00:03:38 +00:00
|
|
|
level.handle_clip_event(context, clip_event);
|
2019-12-16 21:58:31 +00:00
|
|
|
}
|
2020-08-22 00:03:38 +00:00
|
|
|
}
|
2019-12-16 21:58:31 +00:00
|
|
|
|
2020-09-19 14:27:24 +00:00
|
|
|
// Fire event listener on appropriate object
|
2020-08-22 00:03:38 +00:00
|
|
|
if let Some((listener_type, event_name, args)) = listener {
|
|
|
|
context.action_queue.queue_actions(
|
2021-04-17 19:38:11 +00:00
|
|
|
context.stage.root_clip(),
|
2020-08-22 00:03:38 +00:00
|
|
|
ActionType::NotifyListeners {
|
|
|
|
listener: listener_type,
|
|
|
|
method: event_name,
|
|
|
|
args,
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
2019-12-16 10:31:54 +00:00
|
|
|
|
2019-12-08 18:49:23 +00:00
|
|
|
let mut is_mouse_down = self.is_mouse_down;
|
2020-07-28 03:19:43 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2019-12-08 18:49:23 +00:00
|
|
|
if let Some(node) = context.mouse_hovered_object {
|
2020-06-28 06:34:19 +00:00
|
|
|
if node.removed() {
|
|
|
|
context.mouse_hovered_object = None;
|
|
|
|
}
|
|
|
|
}
|
2020-05-21 03:48:27 +00:00
|
|
|
|
2020-06-28 06:34:19 +00:00
|
|
|
match event {
|
|
|
|
PlayerEvent::MouseDown { .. } => {
|
|
|
|
is_mouse_down = true;
|
|
|
|
needs_render = true;
|
2020-11-25 00:35:15 +00:00
|
|
|
if let Some(node) = context.mouse_hovered_object {
|
2020-07-28 00:57:42 +00:00
|
|
|
node.handle_clip_event(context, ClipEvent::Press);
|
2020-06-28 06:34:19 +00:00
|
|
|
}
|
|
|
|
}
|
2020-05-23 23:51:40 +00:00
|
|
|
|
2020-06-28 06:34:19 +00:00
|
|
|
PlayerEvent::MouseUp { .. } => {
|
|
|
|
is_mouse_down = false;
|
|
|
|
needs_render = true;
|
2020-11-25 00:35:15 +00:00
|
|
|
if let Some(node) = context.mouse_hovered_object {
|
2020-07-28 00:57:42 +00:00
|
|
|
node.handle_clip_event(context, ClipEvent::Release);
|
2020-06-28 04:24:49 +00:00
|
|
|
}
|
2020-05-21 03:48:27 +00:00
|
|
|
}
|
2020-06-28 06:34:19 +00:00
|
|
|
|
|
|
|
_ => (),
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2020-07-28 03:19:43 +00:00
|
|
|
Self::run_actions(context);
|
2019-08-26 23:38:37 +00:00
|
|
|
});
|
2019-12-08 18:49:23 +00:00
|
|
|
self.is_mouse_down = is_mouse_down;
|
2020-07-08 21:33:11 +00:00
|
|
|
if needs_render {
|
|
|
|
self.needs_render = true;
|
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-12-21 23:37:27 +00:00
|
|
|
/// Update dragged object, if any.
|
|
|
|
fn update_drag(&mut self) {
|
|
|
|
let mouse_pos = self.mouse_pos;
|
2020-07-28 03:19:43 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2019-12-21 23:37:27 +00:00
|
|
|
if let Some(drag_object) = &mut context.drag_object {
|
2019-12-22 00:03:12 +00:00
|
|
|
if drag_object.display_object.removed() {
|
|
|
|
// Be sure to clear the drag if the object was removed.
|
|
|
|
*context.drag_object = None;
|
|
|
|
} else {
|
|
|
|
let mut drag_point = (
|
|
|
|
mouse_pos.0 + drag_object.offset.0,
|
|
|
|
mouse_pos.1 + drag_object.offset.1,
|
|
|
|
);
|
|
|
|
if let Some(parent) = drag_object.display_object.parent() {
|
|
|
|
drag_point = parent.global_to_local(drag_point);
|
|
|
|
}
|
|
|
|
drag_point = drag_object.constraint.clamp(drag_point);
|
|
|
|
drag_object
|
|
|
|
.display_object
|
|
|
|
.set_x(context.gc_context, drag_point.0.to_pixels());
|
|
|
|
drag_object
|
|
|
|
.display_object
|
|
|
|
.set_y(context.gc_context, drag_point.1.to_pixels());
|
2019-12-21 23:37:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-11-10 03:40:07 +00:00
|
|
|
/// Checks to see if a recent update has caused the current mouse hover
|
|
|
|
/// node to change.
|
2019-08-26 23:38:37 +00:00
|
|
|
fn update_roll_over(&mut self) -> bool {
|
|
|
|
// TODO: While the mouse is down, maintain the hovered node.
|
|
|
|
if self.is_mouse_down {
|
|
|
|
return false;
|
|
|
|
}
|
2019-12-08 18:49:23 +00:00
|
|
|
let mouse_pos = self.mouse_pos;
|
2019-11-13 03:00:19 +00:00
|
|
|
|
2020-02-25 09:45:38 +00:00
|
|
|
let mut new_cursor = self.mouse_cursor;
|
2020-07-28 03:19:43 +00:00
|
|
|
let hover_changed = self.mutate_with_update_context(|context| {
|
2019-11-13 03:00:19 +00:00
|
|
|
// Check hovered object.
|
|
|
|
let mut new_hovered = None;
|
2021-04-15 03:29:12 +00:00
|
|
|
let levels: Vec<_> = context.stage.iter_depth_list().collect();
|
|
|
|
for (_depth, level) in levels.iter().rev() {
|
2019-11-13 03:00:19 +00:00
|
|
|
if new_hovered.is_none() {
|
2021-03-29 11:22:23 +00:00
|
|
|
new_hovered = level.mouse_pick(context, *level, mouse_pos);
|
2019-11-13 03:00:19 +00:00
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-08 18:49:23 +00:00
|
|
|
let cur_hovered = context.mouse_hovered_object;
|
2019-11-13 03:00:19 +00:00
|
|
|
|
2019-12-08 18:49:23 +00:00
|
|
|
if cur_hovered.map(|d| d.as_ptr()) != new_hovered.map(|d| d.as_ptr()) {
|
2019-08-26 23:38:37 +00:00
|
|
|
// RollOut of previous node.
|
2020-11-25 00:35:15 +00:00
|
|
|
if let Some(node) = cur_hovered {
|
2020-06-28 04:24:49 +00:00
|
|
|
if !node.removed() {
|
2020-07-28 00:57:42 +00:00
|
|
|
node.handle_clip_event(context, ClipEvent::RollOut);
|
2020-06-28 04:24:49 +00:00
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2020-09-19 14:27:24 +00:00
|
|
|
// RollOver on new node.I still
|
2020-02-25 09:45:38 +00:00
|
|
|
new_cursor = MouseCursor::Arrow;
|
2020-11-25 00:35:15 +00:00
|
|
|
if let Some(node) = new_hovered {
|
2020-10-29 21:14:00 +00:00
|
|
|
new_cursor = node.mouse_cursor();
|
2020-07-28 00:57:42 +00:00
|
|
|
node.handle_clip_event(context, ClipEvent::RollOver);
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-12-08 18:49:23 +00:00
|
|
|
context.mouse_hovered_object = new_hovered;
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2020-07-28 03:19:43 +00:00
|
|
|
Self::run_actions(context);
|
2019-08-26 23:38:37 +00:00
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
2020-02-25 09:45:38 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// Update mouse cursor if it has changed.
|
|
|
|
if new_cursor != self.mouse_cursor {
|
|
|
|
self.mouse_cursor = new_cursor;
|
2021-01-31 00:36:45 +00:00
|
|
|
self.ui.set_mouse_cursor(new_cursor)
|
2020-02-25 09:45:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
hover_changed
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-11-13 03:00:19 +00:00
|
|
|
/// Preload the first movie in the player.
|
|
|
|
///
|
|
|
|
/// This should only be called once. Further movie loads should preload the
|
|
|
|
/// specific `MovieClip` referenced.
|
2019-08-26 23:38:37 +00:00
|
|
|
fn preload(&mut self) {
|
2020-12-15 16:39:11 +00:00
|
|
|
let mut is_action_script_3 = false;
|
2020-07-28 03:19:43 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
2019-09-05 05:44:35 +00:00
|
|
|
let mut morph_shapes = fnv::FnvHashMap::default();
|
2021-04-17 19:38:11 +00:00
|
|
|
let root = context.stage.root_clip();
|
2019-12-09 22:19:35 +00:00
|
|
|
root.as_movie_clip()
|
2019-09-06 22:19:59 +00:00
|
|
|
.unwrap()
|
2020-07-28 00:57:42 +00:00
|
|
|
.preload(context, &mut morph_shapes);
|
2019-09-05 05:44:35 +00:00
|
|
|
|
2020-12-15 16:39:11 +00:00
|
|
|
let lib = context
|
|
|
|
.library
|
|
|
|
.library_for_movie_mut(root.as_movie_clip().unwrap().movie().unwrap());
|
|
|
|
|
|
|
|
is_action_script_3 = lib.avm_type() == AvmType::Avm2;
|
2019-09-05 05:44:35 +00:00
|
|
|
// Finalize morph shapes.
|
|
|
|
for (id, static_data) in morph_shapes {
|
2019-12-08 18:49:23 +00:00
|
|
|
let morph_shape = MorphShape::new(context.gc_context, static_data);
|
2020-12-15 16:39:11 +00:00
|
|
|
lib.register_character(id, crate::character::Character::MorphShape(morph_shape));
|
2019-09-05 05:44:35 +00:00
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
});
|
2021-01-09 19:36:56 +00:00
|
|
|
if is_action_script_3 && self.warn_on_unsupported_content {
|
2021-01-31 01:57:44 +00:00
|
|
|
self.ui.display_unsupported_message();
|
2020-12-15 16:39:11 +00:00
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn run_frame(&mut self) {
|
2020-07-28 03:19:43 +00:00
|
|
|
self.update(|update_context| {
|
2020-02-24 07:41:55 +00:00
|
|
|
// TODO: In what order are levels run?
|
2021-04-17 00:45:08 +00:00
|
|
|
let stage = update_context.stage;
|
|
|
|
|
|
|
|
stage.exit_frame(update_context);
|
|
|
|
stage.enter_frame(update_context);
|
|
|
|
stage.construct_frame(update_context);
|
|
|
|
stage.frame_constructed(update_context);
|
|
|
|
stage.run_frame(update_context);
|
|
|
|
stage.run_frame_scripts(update_context);
|
2021-01-19 02:50:21 +00:00
|
|
|
|
2021-01-24 08:16:07 +00:00
|
|
|
update_context.update_sounds();
|
2020-05-02 11:25:21 +00:00
|
|
|
});
|
|
|
|
self.needs_render = true;
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn render(&mut self) {
|
2021-04-16 21:55:57 +00:00
|
|
|
let (renderer, ui, transform_stack) =
|
|
|
|
(&mut self.renderer, &mut self.ui, &mut self.transform_stack);
|
2019-08-26 23:38:37 +00:00
|
|
|
|
|
|
|
self.gc_arena.mutate(|_gc_context, gc_root| {
|
2019-12-07 02:29:36 +00:00
|
|
|
let root_data = gc_root.0.read();
|
2019-08-26 23:38:37 +00:00
|
|
|
let mut render_context = RenderContext {
|
2019-11-08 20:09:57 +00:00
|
|
|
renderer: renderer.deref_mut(),
|
2021-04-16 21:55:57 +00:00
|
|
|
ui: ui.deref_mut(),
|
2019-12-07 02:29:36 +00:00
|
|
|
library: &root_data.library,
|
2019-08-26 23:38:37 +00:00
|
|
|
transform_stack,
|
2021-04-16 22:48:27 +00:00
|
|
|
stage: root_data.stage,
|
2019-09-08 02:33:06 +00:00
|
|
|
clip_depth_stack: vec![],
|
2020-10-25 04:08:25 +00:00
|
|
|
allow_mask: true,
|
2019-08-26 23:38:37 +00:00
|
|
|
};
|
2019-11-13 03:00:19 +00:00
|
|
|
|
2021-04-15 03:29:12 +00:00
|
|
|
root_data.stage.render(&mut render_context);
|
2019-08-26 23:38:37 +00:00
|
|
|
});
|
|
|
|
|
2020-05-02 11:25:21 +00:00
|
|
|
self.needs_render = false;
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2020-12-25 21:42:14 +00:00
|
|
|
/// The current frame of the main timeline, if available.
|
|
|
|
/// The first frame is frame 1.
|
|
|
|
pub fn current_frame(&self) -> Option<u16> {
|
|
|
|
self.current_frame
|
|
|
|
}
|
|
|
|
|
2019-10-30 00:01:53 +00:00
|
|
|
pub fn audio(&self) -> &Audio {
|
|
|
|
&self.audio
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn audio_mut(&mut self) -> &mut Audio {
|
|
|
|
&mut self.audio
|
|
|
|
}
|
|
|
|
|
|
|
|
// The frame rate of the current movie in FPS.
|
|
|
|
pub fn frame_rate(&self) -> f64 {
|
|
|
|
self.frame_rate
|
|
|
|
}
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
pub fn renderer(&self) -> &Renderer {
|
|
|
|
&self.renderer
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn renderer_mut(&mut self) -> &mut Renderer {
|
|
|
|
&mut self.renderer
|
|
|
|
}
|
|
|
|
|
2021-02-05 03:11:46 +00:00
|
|
|
pub fn storage(&self) -> &Storage {
|
|
|
|
&self.storage
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn storage_mut(&mut self) -> &mut Storage {
|
|
|
|
&mut self.storage
|
|
|
|
}
|
|
|
|
|
2020-10-15 23:23:36 +00:00
|
|
|
pub fn destroy(self) -> Renderer {
|
|
|
|
self.renderer
|
|
|
|
}
|
|
|
|
|
2021-02-12 13:03:17 +00:00
|
|
|
pub fn ui(&self) -> &Ui {
|
2021-01-31 00:36:45 +00:00
|
|
|
&self.ui
|
2019-12-18 21:25:54 +00:00
|
|
|
}
|
|
|
|
|
2021-02-12 13:03:17 +00:00
|
|
|
pub fn ui_mut(&mut self) -> &mut Ui {
|
2021-01-31 00:36:45 +00:00
|
|
|
&mut self.ui
|
2019-12-18 21:25:54 +00:00
|
|
|
}
|
|
|
|
|
2020-07-08 18:26:00 +00:00
|
|
|
pub fn locale(&self) -> &Locale {
|
|
|
|
&self.locale
|
|
|
|
}
|
|
|
|
|
2021-04-14 22:00:16 +00:00
|
|
|
pub fn run_actions<'gc>(context: &mut UpdateContext<'_, 'gc, '_>) {
|
2020-04-25 09:05:01 +00:00
|
|
|
// Note that actions can queue further actions, so a while loop is necessary here.
|
|
|
|
while let Some(actions) = context.action_queue.pop_action() {
|
2019-12-01 20:05:19 +00:00
|
|
|
// We don't run frame actions if the clip was removed after it queued the action.
|
2019-12-16 02:10:28 +00:00
|
|
|
if !actions.is_unload && actions.clip.removed() {
|
2019-10-26 07:58:24 +00:00
|
|
|
continue;
|
2019-10-18 04:10:13 +00:00
|
|
|
}
|
2019-11-13 03:00:19 +00:00
|
|
|
|
2019-12-16 02:10:28 +00:00
|
|
|
match actions.action_type {
|
|
|
|
// DoAction/clip event code
|
2020-11-09 07:43:41 +00:00
|
|
|
ActionType::Normal { bytecode } | ActionType::Initialize { bytecode } => {
|
2020-07-26 02:11:38 +00:00
|
|
|
Avm1::run_stack_frame_for_action(
|
2019-12-16 02:10:28 +00:00
|
|
|
actions.clip,
|
2020-07-02 21:37:18 +00:00
|
|
|
"[Frame]",
|
2019-11-12 20:05:18 +00:00
|
|
|
context.swf.header().version,
|
2019-12-16 02:10:28 +00:00
|
|
|
bytecode,
|
|
|
|
context,
|
|
|
|
);
|
|
|
|
}
|
2020-04-20 12:27:23 +00:00
|
|
|
// Change the prototype of a movieclip & run constructor events
|
|
|
|
ActionType::Construct {
|
|
|
|
constructor: Some(constructor),
|
|
|
|
events,
|
|
|
|
} => {
|
2020-07-26 02:11:38 +00:00
|
|
|
let version = context.swf.version();
|
|
|
|
let globals = context.avm1.global_object_cell();
|
|
|
|
|
2020-07-01 22:09:43 +00:00
|
|
|
let mut activation = Activation::from_nothing(
|
2020-07-28 01:27:02 +00:00
|
|
|
context.reborrow(),
|
2020-07-02 21:37:18 +00:00
|
|
|
ActivationIdentifier::root("[Construct]"),
|
2020-07-26 02:11:38 +00:00
|
|
|
version,
|
|
|
|
globals,
|
2020-07-25 16:47:58 +00:00
|
|
|
actions.clip,
|
2020-06-30 19:57:51 +00:00
|
|
|
);
|
2021-04-01 07:36:15 +00:00
|
|
|
if let Ok(prototype) = constructor.get("prototype", &mut activation) {
|
2020-06-30 19:57:51 +00:00
|
|
|
if let Value::Object(object) = actions.clip.object() {
|
2021-04-01 07:39:52 +00:00
|
|
|
object.set_proto(activation.context.gc_context, prototype);
|
2020-06-30 19:57:51 +00:00
|
|
|
for event in events {
|
|
|
|
let _ = activation.run_child_frame_for_action(
|
2020-07-02 21:37:18 +00:00
|
|
|
"[Actions]",
|
2020-06-30 19:57:51 +00:00
|
|
|
actions.clip,
|
2020-07-27 23:19:05 +00:00
|
|
|
activation.context.swf.header().version,
|
2020-06-30 19:57:51 +00:00
|
|
|
event,
|
|
|
|
);
|
2020-04-20 12:27:23 +00:00
|
|
|
}
|
2020-06-30 19:57:51 +00:00
|
|
|
|
2020-07-27 23:19:05 +00:00
|
|
|
let _ = constructor.construct_on_existing(&mut activation, object, &[]);
|
2020-04-12 18:50:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-20 12:27:23 +00:00
|
|
|
// Run constructor events without changing the prototype
|
|
|
|
ActionType::Construct {
|
|
|
|
constructor: None,
|
|
|
|
events,
|
|
|
|
} => {
|
|
|
|
for event in events {
|
2020-07-26 02:11:38 +00:00
|
|
|
Avm1::run_stack_frame_for_action(
|
2020-04-20 12:27:23 +00:00
|
|
|
actions.clip,
|
2020-07-02 21:37:18 +00:00
|
|
|
"[Construct]",
|
2020-04-20 12:27:23 +00:00
|
|
|
context.swf.header().version,
|
|
|
|
event,
|
|
|
|
context,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2019-12-16 02:10:28 +00:00
|
|
|
// Event handler method call (e.g. onEnterFrame)
|
2020-01-16 19:20:07 +00:00
|
|
|
ActionType::Method { object, name, args } => {
|
2020-07-26 02:11:38 +00:00
|
|
|
Avm1::run_stack_frame_for_method(
|
2020-01-16 19:20:07 +00:00
|
|
|
actions.clip,
|
|
|
|
object,
|
|
|
|
context.swf.header().version,
|
|
|
|
context,
|
|
|
|
name,
|
|
|
|
&args,
|
|
|
|
);
|
2019-12-16 02:10:28 +00:00
|
|
|
}
|
2019-12-16 21:58:31 +00:00
|
|
|
|
|
|
|
// Event handler method call (e.g. onEnterFrame)
|
2019-12-18 16:45:20 +00:00
|
|
|
ActionType::NotifyListeners {
|
|
|
|
listener,
|
|
|
|
method,
|
|
|
|
args,
|
|
|
|
} => {
|
2019-12-18 19:30:21 +00:00
|
|
|
// A native function ends up resolving immediately,
|
|
|
|
// so this doesn't require any further execution.
|
2020-07-26 02:11:38 +00:00
|
|
|
Avm1::notify_system_listeners(
|
2019-12-16 21:58:31 +00:00
|
|
|
actions.clip,
|
2019-11-12 20:05:18 +00:00
|
|
|
context.swf.version(),
|
2019-12-16 21:58:31 +00:00
|
|
|
context,
|
2019-12-18 16:45:20 +00:00
|
|
|
listener,
|
|
|
|
method,
|
|
|
|
&args,
|
2019-12-16 21:58:31 +00:00
|
|
|
);
|
|
|
|
}
|
2020-08-22 20:16:47 +00:00
|
|
|
|
|
|
|
ActionType::Callable2 {
|
|
|
|
callable,
|
|
|
|
reciever,
|
|
|
|
args,
|
|
|
|
} => {
|
|
|
|
if let Err(e) =
|
|
|
|
Avm2::run_stack_frame_for_callable(callable, reciever, &args[..], context)
|
|
|
|
{
|
|
|
|
log::error!("Unhandled AVM2 exception in event handler: {}", e);
|
|
|
|
}
|
|
|
|
}
|
2019-10-29 04:07:47 +00:00
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-08 18:49:23 +00:00
|
|
|
/// Runs the closure `f` with an `UpdateContext`.
|
|
|
|
/// This takes cares of populating the `UpdateContext` struct, avoiding borrow issues.
|
|
|
|
fn mutate_with_update_context<F, R>(&mut self, f: F) -> R
|
|
|
|
where
|
2020-07-28 03:19:43 +00:00
|
|
|
F: for<'a, 'gc> FnOnce(&mut UpdateContext<'a, 'gc, '_>) -> R,
|
2019-12-08 18:49:23 +00:00
|
|
|
{
|
|
|
|
// We have to do this piecewise borrowing of fields before the closure to avoid
|
|
|
|
// completely borrowing `self`.
|
|
|
|
let (
|
|
|
|
player_version,
|
2019-11-12 20:05:18 +00:00
|
|
|
swf,
|
2019-12-08 18:49:23 +00:00
|
|
|
renderer,
|
|
|
|
audio,
|
|
|
|
navigator,
|
2021-01-31 00:36:45 +00:00
|
|
|
ui,
|
2019-12-08 18:49:23 +00:00
|
|
|
rng,
|
2019-12-16 09:51:19 +00:00
|
|
|
mouse_position,
|
2019-11-08 20:09:57 +00:00
|
|
|
player,
|
2020-05-27 00:52:22 +00:00
|
|
|
system_properties,
|
2020-06-18 01:18:40 +00:00
|
|
|
instance_counter,
|
2020-06-12 18:27:20 +00:00
|
|
|
storage,
|
2020-07-08 18:26:00 +00:00
|
|
|
locale,
|
2020-09-05 16:19:03 +00:00
|
|
|
logging,
|
2020-10-15 03:01:48 +00:00
|
|
|
video,
|
2020-07-08 21:33:11 +00:00
|
|
|
needs_render,
|
2020-09-15 22:28:41 +00:00
|
|
|
max_execution_duration,
|
2020-12-25 21:42:14 +00:00
|
|
|
current_frame,
|
2021-01-04 20:19:20 +00:00
|
|
|
time_offset,
|
2019-12-08 18:49:23 +00:00
|
|
|
) = (
|
|
|
|
self.player_version,
|
2019-11-12 20:05:18 +00:00
|
|
|
&self.swf,
|
2019-11-08 20:09:57 +00:00
|
|
|
self.renderer.deref_mut(),
|
|
|
|
self.audio.deref_mut(),
|
|
|
|
self.navigator.deref_mut(),
|
2021-01-31 00:36:45 +00:00
|
|
|
self.ui.deref_mut(),
|
2019-12-08 18:49:23 +00:00
|
|
|
&mut self.rng,
|
2019-12-16 09:51:19 +00:00
|
|
|
&self.mouse_pos,
|
2019-11-08 20:09:57 +00:00
|
|
|
self.self_reference.clone(),
|
2020-05-27 00:52:22 +00:00
|
|
|
&mut self.system,
|
2020-06-18 01:18:40 +00:00
|
|
|
&mut self.instance_counter,
|
2020-06-12 18:27:20 +00:00
|
|
|
self.storage.deref_mut(),
|
2020-07-08 18:26:00 +00:00
|
|
|
self.locale.deref_mut(),
|
2020-09-05 16:19:03 +00:00
|
|
|
self.log.deref_mut(),
|
2020-10-15 03:01:48 +00:00
|
|
|
self.video.deref_mut(),
|
2020-07-08 21:33:11 +00:00
|
|
|
&mut self.needs_render,
|
2020-09-15 22:28:41 +00:00
|
|
|
self.max_execution_duration,
|
2020-12-25 21:42:14 +00:00
|
|
|
&mut self.current_frame,
|
2021-01-04 20:19:20 +00:00
|
|
|
&mut self.time_offset,
|
2019-12-08 18:49:23 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
self.gc_arena.mutate(|gc_context, gc_root| {
|
|
|
|
let mut root_data = gc_root.0.write(gc_context);
|
|
|
|
let mouse_hovered_object = root_data.mouse_hovered_object;
|
2020-10-29 23:09:57 +00:00
|
|
|
let focus_tracker = root_data.focus_tracker;
|
2020-06-23 04:07:27 +00:00
|
|
|
let (
|
2021-04-15 03:29:12 +00:00
|
|
|
stage,
|
2020-06-23 04:07:27 +00:00
|
|
|
library,
|
|
|
|
action_queue,
|
2020-02-04 02:22:44 +00:00
|
|
|
avm1,
|
|
|
|
avm2,
|
2020-06-23 04:07:27 +00:00
|
|
|
drag_object,
|
|
|
|
load_manager,
|
|
|
|
shared_objects,
|
|
|
|
unbound_text_fields,
|
2020-07-08 02:06:19 +00:00
|
|
|
timers,
|
2020-08-27 22:16:53 +00:00
|
|
|
external_interface,
|
2021-01-23 02:03:59 +00:00
|
|
|
audio_manager,
|
2020-06-23 04:07:27 +00:00
|
|
|
) = root_data.update_context_params();
|
2019-12-02 02:00:01 +00:00
|
|
|
|
2019-12-08 18:49:23 +00:00
|
|
|
let mut update_context = UpdateContext {
|
|
|
|
player_version,
|
2019-11-12 20:05:18 +00:00
|
|
|
swf,
|
2019-12-08 18:49:23 +00:00
|
|
|
library,
|
|
|
|
rng,
|
|
|
|
renderer,
|
|
|
|
audio,
|
|
|
|
navigator,
|
2021-01-31 00:36:45 +00:00
|
|
|
ui,
|
2019-12-08 18:49:23 +00:00
|
|
|
action_queue,
|
|
|
|
gc_context,
|
2021-04-15 03:29:12 +00:00
|
|
|
stage,
|
2019-12-08 18:49:23 +00:00
|
|
|
mouse_hovered_object,
|
2019-12-16 09:51:19 +00:00
|
|
|
mouse_position,
|
2019-12-21 23:37:27 +00:00
|
|
|
drag_object,
|
2019-11-08 20:09:57 +00:00
|
|
|
player,
|
2020-01-10 23:28:49 +00:00
|
|
|
load_manager,
|
2020-05-31 02:49:07 +00:00
|
|
|
system: system_properties,
|
2020-06-18 01:18:40 +00:00
|
|
|
instance_counter,
|
2020-06-15 16:52:49 +00:00
|
|
|
storage,
|
2020-07-08 18:26:00 +00:00
|
|
|
locale,
|
2020-09-05 16:19:03 +00:00
|
|
|
log: logging,
|
2020-10-15 03:01:48 +00:00
|
|
|
video,
|
2020-06-22 00:59:38 +00:00
|
|
|
shared_objects,
|
2020-06-23 04:07:27 +00:00
|
|
|
unbound_text_fields,
|
2020-07-08 02:06:19 +00:00
|
|
|
timers,
|
2020-07-08 21:33:11 +00:00
|
|
|
needs_render,
|
2020-07-28 00:57:42 +00:00
|
|
|
avm1,
|
2020-07-28 03:19:43 +00:00
|
|
|
avm2,
|
2020-08-27 21:47:08 +00:00
|
|
|
external_interface,
|
2020-09-15 22:28:41 +00:00
|
|
|
update_start: Instant::now(),
|
|
|
|
max_execution_duration,
|
2020-10-29 23:09:57 +00:00
|
|
|
focus_tracker,
|
2021-01-04 20:19:20 +00:00
|
|
|
times_get_time_called: 0,
|
|
|
|
time_offset,
|
2021-01-23 02:03:59 +00:00
|
|
|
audio_manager,
|
2019-12-08 18:49:23 +00:00
|
|
|
};
|
|
|
|
|
2020-07-28 03:19:43 +00:00
|
|
|
let ret = f(&mut update_context);
|
2019-12-08 18:49:23 +00:00
|
|
|
|
2020-12-25 21:42:14 +00:00
|
|
|
*current_frame = update_context
|
2021-04-15 03:29:12 +00:00
|
|
|
.stage
|
2021-04-17 19:38:11 +00:00
|
|
|
.root_clip()
|
|
|
|
.as_movie_clip()
|
2020-12-25 21:42:14 +00:00
|
|
|
.map(|clip| clip.current_frame());
|
|
|
|
|
2019-12-08 18:49:23 +00:00
|
|
|
// Hovered object may have been updated; copy it back to the GC root.
|
|
|
|
root_data.mouse_hovered_object = update_context.mouse_hovered_object;
|
2020-12-25 21:42:14 +00:00
|
|
|
|
2019-12-08 18:49:23 +00:00
|
|
|
ret
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-10-08 04:02:09 +00:00
|
|
|
/// Loads font data from the given buffer.
|
|
|
|
/// The buffer should be the `DefineFont3` info for the tag.
|
|
|
|
/// The tag header should not be included.
|
2020-05-23 20:38:54 +00:00
|
|
|
pub fn load_device_font<'gc>(
|
2019-12-17 05:21:59 +00:00
|
|
|
gc_context: gc_arena::MutationContext<'gc, '_>,
|
2019-10-08 04:02:09 +00:00
|
|
|
data: &[u8],
|
2020-07-23 03:18:30 +00:00
|
|
|
renderer: &mut dyn RenderBackend,
|
2019-12-17 05:21:59 +00:00
|
|
|
) -> Result<crate::font::Font<'gc>, Error> {
|
2019-10-08 04:02:09 +00:00
|
|
|
let mut reader = swf::read::Reader::new(data, 8);
|
2021-01-20 20:46:22 +00:00
|
|
|
let device_font = crate::font::Font::from_swf_tag(
|
|
|
|
gc_context,
|
|
|
|
renderer,
|
|
|
|
&reader.read_define_font_2(3)?,
|
|
|
|
reader.encoding(),
|
|
|
|
)?;
|
2019-10-08 04:02:09 +00:00
|
|
|
Ok(device_font)
|
|
|
|
}
|
2019-11-10 03:40:07 +00:00
|
|
|
|
|
|
|
/// Update the current state of the player.
|
|
|
|
///
|
|
|
|
/// The given function will be called with the current stage root, current
|
|
|
|
/// mouse hover node, AVM, and an update context.
|
|
|
|
///
|
|
|
|
/// This particular function runs necessary post-update bookkeeping, such
|
|
|
|
/// as executing any actions queued on the update context, keeping the
|
|
|
|
/// hover state up to date, and running garbage collection.
|
|
|
|
pub fn update<F, R>(&mut self, func: F) -> R
|
|
|
|
where
|
2020-07-28 03:19:43 +00:00
|
|
|
F: for<'a, 'gc, 'gc_context> FnOnce(&mut UpdateContext<'a, 'gc, 'gc_context>) -> R,
|
2019-11-10 03:40:07 +00:00
|
|
|
{
|
2021-04-03 13:46:29 +00:00
|
|
|
self.update_drag();
|
|
|
|
|
2020-07-28 03:19:43 +00:00
|
|
|
let rval = self.mutate_with_update_context(|context| {
|
|
|
|
let rval = func(context);
|
2019-11-10 03:40:07 +00:00
|
|
|
|
2020-07-28 03:19:43 +00:00
|
|
|
Self::run_actions(context);
|
2019-11-10 03:40:07 +00:00
|
|
|
|
|
|
|
rval
|
|
|
|
});
|
|
|
|
|
|
|
|
// Update mouse state (check for new hovered button, etc.)
|
|
|
|
self.update_roll_over();
|
|
|
|
|
|
|
|
// GC
|
|
|
|
self.gc_arena.collect_debt();
|
|
|
|
|
|
|
|
rval
|
|
|
|
}
|
2020-06-22 00:59:38 +00:00
|
|
|
|
|
|
|
pub fn flush_shared_objects(&mut self) {
|
2020-07-28 03:19:43 +00:00
|
|
|
self.update(|context| {
|
2020-07-26 02:11:38 +00:00
|
|
|
let mut activation =
|
|
|
|
Activation::from_stub(context.reborrow(), ActivationIdentifier::root("[Flush]"));
|
2020-07-27 23:19:05 +00:00
|
|
|
let shared_objects = activation.context.shared_objects.clone();
|
2020-06-30 19:57:51 +00:00
|
|
|
for so in shared_objects.values() {
|
2020-07-27 23:19:05 +00:00
|
|
|
let _ = crate::avm1::globals::shared_object::flush(&mut activation, *so, &[]);
|
2020-06-30 19:57:51 +00:00
|
|
|
}
|
2020-06-22 00:59:38 +00:00
|
|
|
});
|
|
|
|
}
|
2020-07-08 02:06:19 +00:00
|
|
|
|
|
|
|
/// Update all AVM-based timers (such as created via setInterval).
|
|
|
|
/// Returns the approximate amount of time until the next timer tick.
|
|
|
|
pub fn update_timers(&mut self, dt: f64) {
|
2020-07-28 00:57:42 +00:00
|
|
|
self.time_til_next_timer =
|
2020-07-28 03:19:43 +00:00
|
|
|
self.mutate_with_update_context(|context| Timers::update_timers(context, dt));
|
2020-07-08 02:06:19 +00:00
|
|
|
}
|
2020-08-22 00:03:38 +00:00
|
|
|
|
|
|
|
/// Returns whether this player consumes mouse wheel events.
|
|
|
|
/// Used by web to prevent scrolling.
|
|
|
|
pub fn should_prevent_scrolling(&mut self) -> bool {
|
|
|
|
self.mutate_with_update_context(|context| context.avm1.has_mouse_listener())
|
|
|
|
}
|
2020-08-27 21:27:54 +00:00
|
|
|
|
|
|
|
pub fn add_external_interface(&mut self, provider: Box<dyn ExternalInterfaceProvider>) {
|
2020-08-27 22:16:53 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
context.external_interface.add_provider(provider)
|
|
|
|
});
|
2020-08-27 21:27:54 +00:00
|
|
|
}
|
2020-08-28 10:24:19 +00:00
|
|
|
|
2020-08-28 15:24:26 +00:00
|
|
|
pub fn call_internal_interface(
|
|
|
|
&mut self,
|
|
|
|
name: &str,
|
|
|
|
args: impl IntoIterator<Item = ExternalValue>,
|
|
|
|
) -> ExternalValue {
|
2020-08-28 10:24:19 +00:00
|
|
|
self.mutate_with_update_context(|context| {
|
|
|
|
if let Some(callback) = context.external_interface.get_callback(name) {
|
2020-08-28 15:24:26 +00:00
|
|
|
callback.call(context, name, args)
|
|
|
|
} else {
|
|
|
|
ExternalValue::Null
|
2020-08-28 10:24:19 +00:00
|
|
|
}
|
2020-08-28 15:24:26 +00:00
|
|
|
})
|
2020-08-28 10:24:19 +00:00
|
|
|
}
|
2020-09-05 19:06:17 +00:00
|
|
|
|
2020-09-05 19:10:46 +00:00
|
|
|
pub fn log_backend(&self) -> &Log {
|
|
|
|
&self.log
|
2020-09-05 19:06:17 +00:00
|
|
|
}
|
2020-09-15 22:28:41 +00:00
|
|
|
|
|
|
|
pub fn max_execution_duration(&self) -> Duration {
|
|
|
|
self.max_execution_duration
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_max_execution_duration(&mut self, max_execution_duration: Duration) {
|
|
|
|
self.max_execution_duration = max_execution_duration
|
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
2019-12-21 23:37:27 +00:00
|
|
|
|
2021-02-18 02:38:55 +00:00
|
|
|
#[derive(Collect)]
|
|
|
|
#[collect(no_drop)]
|
2019-12-21 23:37:27 +00:00
|
|
|
pub struct DragObject<'gc> {
|
|
|
|
/// The display object being dragged.
|
|
|
|
pub display_object: DisplayObject<'gc>,
|
|
|
|
|
|
|
|
/// The offset from the mouse position to the center of the clip.
|
2021-02-18 02:38:55 +00:00
|
|
|
#[collect(require_static)]
|
2019-12-21 23:37:27 +00:00
|
|
|
pub offset: (Twips, Twips),
|
|
|
|
|
|
|
|
/// The bounding rectangle where the clip will be maintained.
|
2021-02-18 02:38:55 +00:00
|
|
|
#[collect(require_static)]
|
2019-12-21 23:37:27 +00:00
|
|
|
pub constraint: BoundingBox,
|
|
|
|
}
|