2019-05-03 02:11:47 +00:00
|
|
|
mod audio;
|
|
|
|
mod render;
|
|
|
|
mod shape_utils;
|
|
|
|
|
|
|
|
use crate::{audio::WebAudioBackend, render::WebCanvasRenderBackend};
|
2019-05-10 16:06:47 +00:00
|
|
|
use generational_arena::{Arena, Index};
|
2019-04-28 01:15:43 +00:00
|
|
|
use js_sys::Uint8Array;
|
2019-05-17 02:14:23 +00:00
|
|
|
use std::{cell::RefCell, error::Error, num::NonZeroI32};
|
|
|
|
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
2019-04-28 06:08:59 +00:00
|
|
|
use web_sys::HtmlCanvasElement;
|
2019-04-28 01:15:43 +00:00
|
|
|
|
2019-05-03 00:17:02 +00:00
|
|
|
thread_local! {
|
2019-05-17 02:14:23 +00:00
|
|
|
static PLAYERS: RefCell<Arena<PlayerInstance>> = RefCell::new(Arena::new());
|
|
|
|
}
|
|
|
|
|
|
|
|
type AnimationHandler = Closure<FnMut(f64)>;
|
|
|
|
|
|
|
|
struct PlayerInstance {
|
|
|
|
core: ruffle_core::Player,
|
|
|
|
timestamp: f64,
|
|
|
|
animation_handler: Option<AnimationHandler>, // requestAnimationFrame callback
|
|
|
|
animation_handler_id: Option<NonZeroI32>, // requestAnimationFrame id
|
2019-05-03 00:17:02 +00:00
|
|
|
}
|
|
|
|
|
2019-04-28 01:15:43 +00:00
|
|
|
#[wasm_bindgen]
|
2019-05-17 02:14:23 +00:00
|
|
|
#[derive(Clone)]
|
2019-05-10 16:06:47 +00:00
|
|
|
pub struct Player(Index);
|
2019-04-28 01:15:43 +00:00
|
|
|
|
|
|
|
#[wasm_bindgen]
|
|
|
|
impl Player {
|
2019-04-28 06:08:59 +00:00
|
|
|
pub fn new(canvas: HtmlCanvasElement, swf_data: Uint8Array) -> Result<Player, JsValue> {
|
|
|
|
Player::new_internal(canvas, swf_data).map_err(|_| "Error creating player".into())
|
2019-04-28 01:15:43 +00:00
|
|
|
}
|
|
|
|
|
2019-05-17 02:14:23 +00:00
|
|
|
pub fn destroy(&mut self) -> Result<(), JsValue> {
|
|
|
|
// Remove instance from the active list.
|
|
|
|
if let Some(player_instance) = PLAYERS.with(|players| {
|
2019-05-03 00:17:02 +00:00
|
|
|
let mut players = players.borrow_mut();
|
2019-05-17 02:14:23 +00:00
|
|
|
players.remove(self.0)
|
|
|
|
}) {
|
|
|
|
// Cancel the animation handler, if it's still active.
|
|
|
|
if let Some(id) = player_instance.animation_handler_id {
|
|
|
|
if let Some(window) = web_sys::window() {
|
|
|
|
return window.cancel_animation_frame(id.into());
|
|
|
|
}
|
2019-05-06 10:51:09 +00:00
|
|
|
}
|
2019-05-17 02:14:23 +00:00
|
|
|
}
|
2019-05-06 10:51:09 +00:00
|
|
|
|
2019-05-17 02:14:23 +00:00
|
|
|
// Player is dropped at this point.
|
|
|
|
Ok(())
|
2019-04-28 01:15:43 +00:00
|
|
|
}
|
|
|
|
}
|
2019-04-28 06:08:59 +00:00
|
|
|
|
|
|
|
impl Player {
|
2019-04-29 20:24:29 +00:00
|
|
|
fn new_internal(canvas: HtmlCanvasElement, swf_data: Uint8Array) -> Result<Player, Box<Error>> {
|
2019-04-30 08:53:21 +00:00
|
|
|
console_error_panic_hook::set_once();
|
2019-05-06 10:51:09 +00:00
|
|
|
let _ = console_log::init_with_level(log::Level::Trace);
|
2019-04-30 08:53:21 +00:00
|
|
|
|
2019-04-28 06:08:59 +00:00
|
|
|
let mut data = vec![0; swf_data.length() as usize];
|
|
|
|
swf_data.copy_to(&mut data[..]);
|
|
|
|
|
2019-04-29 20:24:29 +00:00
|
|
|
let renderer = WebCanvasRenderBackend::new(&canvas)?;
|
2019-04-30 08:53:21 +00:00
|
|
|
let audio = WebAudioBackend::new()?;
|
2019-04-28 06:08:59 +00:00
|
|
|
|
2019-05-17 02:14:23 +00:00
|
|
|
let core = ruffle_core::Player::new(Box::new(renderer), Box::new(audio), data)?;
|
2019-04-29 20:24:29 +00:00
|
|
|
|
|
|
|
// Update canvas size to match player size.
|
2019-05-17 02:14:23 +00:00
|
|
|
canvas.set_width(core.movie_width());
|
|
|
|
canvas.set_height(core.movie_height());
|
2019-04-29 20:24:29 +00:00
|
|
|
|
|
|
|
let style = canvas.style();
|
|
|
|
style
|
2019-05-17 02:14:23 +00:00
|
|
|
.set_property("width", &format!("{}px", core.movie_width()))
|
2019-04-29 20:24:29 +00:00
|
|
|
.map_err(|_| "Unable to set style")?;
|
|
|
|
style
|
2019-05-17 02:14:23 +00:00
|
|
|
.set_property("height", &format!("{}px", core.movie_height()))
|
2019-04-29 20:24:29 +00:00
|
|
|
.map_err(|_| "Unable to set style")?;
|
|
|
|
|
2019-05-17 02:14:23 +00:00
|
|
|
let window = web_sys::window().ok_or_else(|| "Expected window")?;
|
|
|
|
let timestamp = window
|
|
|
|
.performance()
|
|
|
|
.ok_or_else(|| "Expected performance")?
|
|
|
|
.now();
|
|
|
|
|
|
|
|
// Create instance.
|
|
|
|
let instance = PlayerInstance {
|
|
|
|
core,
|
|
|
|
animation_handler: None,
|
|
|
|
animation_handler_id: None,
|
|
|
|
timestamp,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Register the instance and create the animation frame closure.
|
|
|
|
let mut player = PLAYERS.with(move |players| {
|
2019-05-03 00:17:02 +00:00
|
|
|
let mut players = players.borrow_mut();
|
2019-05-17 02:14:23 +00:00
|
|
|
let index = players.insert(instance);
|
|
|
|
let player = Player(index);
|
|
|
|
|
|
|
|
// Create the animation frame closure.
|
|
|
|
{
|
|
|
|
let mut player = player.clone();
|
|
|
|
let instance = players.get_mut(index).unwrap();
|
|
|
|
instance.animation_handler = Some(Closure::wrap(Box::new(move |timestamp: f64| {
|
|
|
|
player.tick(timestamp);
|
|
|
|
})
|
|
|
|
as Box<FnMut(f64)>));
|
|
|
|
}
|
|
|
|
|
|
|
|
player
|
2019-05-03 00:17:02 +00:00
|
|
|
});
|
|
|
|
|
2019-05-17 02:14:23 +00:00
|
|
|
// Do an initial tick to start the animation loop.
|
|
|
|
player.tick(timestamp);
|
|
|
|
|
|
|
|
Ok(player)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tick(&mut self, timestamp: f64) {
|
|
|
|
PLAYERS.with(|players| {
|
|
|
|
let mut players = players.borrow_mut();
|
|
|
|
if let Some(player_instance) = players.get_mut(self.0) {
|
|
|
|
let dt = timestamp - player_instance.timestamp;
|
|
|
|
player_instance.timestamp = timestamp;
|
|
|
|
player_instance.core.tick(dt);
|
|
|
|
|
|
|
|
// Request next animation frame.
|
|
|
|
if let Some(handler) = &player_instance.animation_handler {
|
|
|
|
let window = web_sys::window().unwrap();
|
|
|
|
let id = window
|
|
|
|
.request_animation_frame(handler.as_ref().unchecked_ref())
|
|
|
|
.unwrap();
|
|
|
|
player_instance.animation_handler_id = NonZeroI32::new(id);
|
|
|
|
} else {
|
|
|
|
player_instance.animation_handler_id = None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2019-04-28 06:08:59 +00:00
|
|
|
}
|
|
|
|
}
|