ruffle/web/src/lib.rs

148 lines
5.0 KiB
Rust
Raw Normal View History

2019-05-24 17:25:03 +00:00
//! Ruffle web frontend.
2019-05-03 02:11:47 +00:00
mod audio;
mod render;
use crate::{audio::WebAudioBackend, render::WebCanvasRenderBackend};
use generational_arena::{Arena, Index};
2019-04-28 01:15:43 +00:00
use js_sys::Uint8Array;
use std::{cell::RefCell, error::Error, num::NonZeroI32};
2019-05-24 17:25:03 +00:00
use wasm_bindgen::{prelude::*, JsValue};
2019-04-28 06:08:59 +00:00
use web_sys::HtmlCanvasElement;
2019-04-28 01:15:43 +00:00
thread_local! {
2019-05-24 17:25:03 +00:00
/// We store the actual instances of the ruffle core in a static pool.
/// This gives us a clear boundary between the JS side and Rust side, avoiding
/// issues with lifetimes and type paramters (which cannot be exported with wasm-bindgen).
static INSTANCES: RefCell<Arena<RuffleInstance>> = RefCell::new(Arena::new());
}
type AnimationHandler = Closure<FnMut(f64)>;
2019-05-24 17:25:03 +00:00
struct RuffleInstance {
core: ruffle_core::Player,
timestamp: f64,
animation_handler: Option<AnimationHandler>, // requestAnimationFrame callback
animation_handler_id: Option<NonZeroI32>, // requestAnimationFrame id
}
2019-05-24 17:25:03 +00:00
/// An opaque handle to a `RuffleInstance` inside the pool.
///
/// This type is exported to JS, and is used to interact with the library.
2019-04-28 01:15:43 +00:00
#[wasm_bindgen]
#[derive(Clone)]
2019-05-24 17:25:03 +00:00
pub struct Ruffle(Index);
2019-04-28 01:15:43 +00:00
#[wasm_bindgen]
2019-05-24 17:25:03 +00:00
impl Ruffle {
pub fn new(canvas: HtmlCanvasElement, swf_data: Uint8Array) -> Result<Ruffle, JsValue> {
Ruffle::new_internal(canvas, swf_data).map_err(|_| "Error creating player".into())
2019-04-28 01:15:43 +00:00
}
pub fn destroy(&mut self) -> Result<(), JsValue> {
// Remove instance from the active list.
2019-05-24 17:25:03 +00:00
if let Some(instance) = INSTANCES.with(|instances| {
let mut instances = instances.borrow_mut();
instances.remove(self.0)
}) {
// Cancel the animation handler, if it's still active.
2019-05-24 17:25:03 +00:00
if let Some(id) = 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-06 10:51:09 +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
2019-05-24 17:25:03 +00:00
impl Ruffle {
fn new_internal(canvas: HtmlCanvasElement, swf_data: Uint8Array) -> Result<Ruffle, 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
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.
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
.set_property("width", &format!("{}px", core.movie_width()))
2019-04-29 20:24:29 +00:00
.map_err(|_| "Unable to set style")?;
style
.set_property("height", &format!("{}px", core.movie_height()))
2019-04-29 20:24:29 +00:00
.map_err(|_| "Unable to set style")?;
let window = web_sys::window().ok_or_else(|| "Expected window")?;
let timestamp = window
.performance()
.ok_or_else(|| "Expected performance")?
.now();
// Create instance.
2019-05-24 17:25:03 +00:00
let instance = RuffleInstance {
core,
animation_handler: None,
animation_handler_id: None,
timestamp,
};
// Register the instance and create the animation frame closure.
2019-05-24 17:25:03 +00:00
let mut ruffle = INSTANCES.with(move |instances| {
let mut instances = instances.borrow_mut();
let index = instances.insert(instance);
let ruffle = Ruffle(index);
// Create the animation frame closure.
{
2019-05-24 17:25:03 +00:00
let mut ruffle = ruffle.clone();
let instance = instances.get_mut(index).unwrap();
instance.animation_handler = Some(Closure::wrap(Box::new(move |timestamp: f64| {
2019-05-24 17:25:03 +00:00
ruffle.tick(timestamp);
})
as Box<FnMut(f64)>));
}
2019-05-24 17:25:03 +00:00
ruffle
});
// Do an initial tick to start the animation loop.
2019-05-24 17:25:03 +00:00
ruffle.tick(timestamp);
2019-05-24 17:25:03 +00:00
Ok(ruffle)
}
fn tick(&mut self, timestamp: f64) {
2019-05-24 17:25:03 +00:00
use wasm_bindgen::JsCast;
INSTANCES.with(|instances| {
let mut instances = instances.borrow_mut();
if let Some(instance) = instances.get_mut(self.0) {
let dt = timestamp - instance.timestamp;
instance.timestamp = timestamp;
instance.core.tick(dt);
// Request next animation frame.
2019-05-24 17:25:03 +00:00
if let Some(handler) = &instance.animation_handler {
let window = web_sys::window().unwrap();
let id = window
.request_animation_frame(handler.as_ref().unchecked_ref())
.unwrap();
2019-05-24 17:25:03 +00:00
instance.animation_handler_id = NonZeroI32::new(id);
} else {
2019-05-24 17:25:03 +00:00
instance.animation_handler_id = None;
}
}
});
2019-04-28 06:08:59 +00:00
}
}