core: Re-implement `NullExecutor`

Make it a thin abstraction layer over either the `futures` or `wasm-bindgen-futures`
crates, as already done in `render/wgpu/src/uniform_buffer.rs`,
instead of a hand-made single-thread executor.

Ideally this would also be usable on desktop, but I didn't manage to
get `LocalPool` working with `winit` (it needs to post a task to the
`EventLoopProxy` as a wake procedure).
This commit is contained in:
relrelb 2022-03-15 23:28:13 +02:00 committed by Mike Welsh
parent bb63ac2de7
commit 161071e8c4
5 changed files with 73 additions and 115 deletions

2
Cargo.lock generated
View File

@ -2971,6 +2971,7 @@ dependencies = [
"flash-lso", "flash-lso",
"flate2", "flate2",
"fnv", "fnv",
"futures",
"gc-arena", "gc-arena",
"generational-arena", "generational-arena",
"gif", "gif",
@ -3001,6 +3002,7 @@ dependencies = [
"symphonia", "symphonia",
"thiserror", "thiserror",
"url", "url",
"wasm-bindgen-futures",
"weak-table", "weak-table",
] ]

View File

@ -53,6 +53,12 @@ nihav_duck = { git = "https://github.com/ruffle-rs/nihav-vp6", rev = "9416fcc9fc
version = "0.2.2" version = "0.2.2"
default-features = false # can't use rayon on web default-features = false # can't use rayon on web
[target.'cfg(not(target_family = "wasm"))'.dependencies.futures]
version = "0.3.21"
[target.'cfg(target_family = "wasm")'.dependencies.wasm-bindgen-futures]
version = "0.4"
[dev-dependencies] [dev-dependencies]
approx = "0.5.1" approx = "0.5.1"

View File

@ -4,14 +4,10 @@ use crate::loader::Error;
use crate::string::WStr; use crate::string::WStr;
use indexmap::IndexMap; use indexmap::IndexMap;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::VecDeque;
use std::fs; use std::fs;
use std::future::Future; use std::future::Future;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::pin::Pin; use std::pin::Pin;
use std::ptr::null;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use swf::avm1::types::SendVarsMethod; use swf::avm1::types::SendVarsMethod;
use url::{ParseError, Url}; use url::{ParseError, Url};
@ -204,103 +200,72 @@ pub trait NavigatorBackend {
fn pre_process_url(&self, url: Url) -> Url; fn pre_process_url(&self, url: Url) -> Url;
} }
/// A null implementation of an event loop that only supports blocking. #[cfg(not(target_family = "wasm"))]
pub struct NullExecutor { pub struct NullExecutor(futures::executor::LocalPool);
/// The list of outstanding futures spawned on this executor.
futures_queue: VecDeque<OwnedFuture<(), Error>>,
/// The source of any additional futures.
channel: Receiver<OwnedFuture<(), Error>>,
}
unsafe fn do_nothing(_data: *const ()) {}
unsafe fn clone(_data: *const ()) -> RawWaker {
NullExecutor::raw_waker()
}
const NULL_VTABLE: RawWakerVTable = RawWakerVTable::new(clone, do_nothing, do_nothing, do_nothing);
#[cfg(not(target_family = "wasm"))]
impl NullExecutor { impl NullExecutor {
/// Construct a new executor. pub fn new() -> Self {
/// Self(futures::executor::LocalPool::new())
/// The sender yielded as part of construction should be given to a
/// `NullNavigatorBackend` so that it can spawn futures on this executor.
pub fn new() -> (Self, Sender<OwnedFuture<(), Error>>) {
let (send, recv) = channel();
(
Self {
futures_queue: VecDeque::new(),
channel: recv,
},
send,
)
} }
/// Construct a do-nothing raw waker. pub fn spawner(&self) -> NullSpawner {
/// NullSpawner(self.0.spawner())
/// The RawWaker, because the RawWaker
/// interface normally deals with unchecked pointers. We instead just hand
/// it a null pointer and do nothing with it, which is trivially sound.
fn raw_waker() -> RawWaker {
RawWaker::new(null(), &NULL_VTABLE)
} }
/// Copy all outstanding futures into the local queue. pub fn run(&mut self) {
fn flush_channel(&mut self) { self.0.run();
for future in self.channel.try_iter() {
self.futures_queue.push_back(future);
}
} }
}
/// Poll all in-progress futures. impl Default for NullExecutor {
/// fn default() -> Self {
/// If any task in the executor yields an error, then this function will Self::new()
/// stop polling futures and return that error. Otherwise, it will yield }
/// `Ok`, indicating that no errors occurred. More work may still be }
/// available,
pub fn poll_all(&mut self) -> Result<(), Error> {
self.flush_channel();
let mut unfinished_futures = VecDeque::new(); #[cfg(not(target_family = "wasm"))]
let mut result = Ok(()); pub struct NullSpawner(futures::executor::LocalSpawner);
while let Some(mut future) = self.futures_queue.pop_front() { #[cfg(not(target_family = "wasm"))]
let waker = unsafe { Waker::from_raw(Self::raw_waker()) }; impl NullSpawner {
let mut context = Context::from_waker(&waker); pub fn spawn_local(&self, future: OwnedFuture<(), Error>) {
use futures::task::LocalSpawnExt;
match future.as_mut().poll(&mut context) { let _ = self.0.spawn_local(async move {
Poll::Ready(v) if v.is_err() => { if let Err(e) = future.await {
result = v; log::error!("Asynchronous error occurred: {}", e);
break;
}
Poll::Ready(_) => continue,
Poll::Pending => unfinished_futures.push_back(future),
} }
} });
}
}
for future in unfinished_futures { #[cfg(target_family = "wasm")]
self.futures_queue.push_back(future); pub struct NullExecutor;
}
result #[cfg(target_family = "wasm")]
impl NullExecutor {
pub fn new() -> Self {
Self
} }
/// Check if work remains in the executor. pub fn spawner(&self) -> NullSpawner {
pub fn has_work(&mut self) -> bool { NullSpawner
self.flush_channel();
!self.futures_queue.is_empty()
} }
/// Block until all futures complete or an error occurs. pub fn run(&mut self) {}
pub fn block_all(&mut self) -> Result<(), Error> { }
while self.has_work() {
self.poll_all()?;
}
Ok(()) #[cfg(target_family = "wasm")]
pub struct NullSpawner;
#[cfg(target_family = "wasm")]
impl NullSpawner {
pub fn spawn_local(&self, future: OwnedFuture<(), Error>) {
wasm_bindgen_futures::spawn_local(async move {
if let Err(e) = future.await {
log::error!("Asynchronous error occurred: {}", e);
}
});
} }
} }
@ -309,35 +274,25 @@ impl NullExecutor {
/// The NullNavigatorBackend includes a trivial executor that holds owned /// The NullNavigatorBackend includes a trivial executor that holds owned
/// futures and runs them to completion, blockingly. /// futures and runs them to completion, blockingly.
pub struct NullNavigatorBackend { pub struct NullNavigatorBackend {
/// The channel upon which all spawned futures will be sent. spawner: NullSpawner,
channel: Option<Sender<OwnedFuture<(), Error>>>,
/// The base path for all relative fetches. /// The base path for all relative fetches.
relative_base_path: PathBuf, relative_base_path: PathBuf,
} }
impl NullNavigatorBackend { impl NullNavigatorBackend {
/// Construct a default navigator backend with no async or fetch
/// capability.
pub fn new() -> Self { pub fn new() -> Self {
NullNavigatorBackend { let executor = NullExecutor::new();
channel: None, Self {
spawner: executor.spawner(),
relative_base_path: PathBuf::new(), relative_base_path: PathBuf::new(),
} }
} }
/// Construct a navigator backend with fetch and async capability. pub fn with_base_path(path: &Path, executor: &NullExecutor) -> Self {
pub fn with_base_path<P: AsRef<Path>>( Self {
path: P, spawner: executor.spawner(),
channel: Sender<OwnedFuture<(), Error>>, relative_base_path: path.to_path_buf(),
) -> Self {
let mut relative_base_path = PathBuf::new();
relative_base_path.push(path);
NullNavigatorBackend {
channel: Some(channel),
relative_base_path,
} }
} }
} }
@ -365,9 +320,7 @@ impl NavigatorBackend for NullNavigatorBackend {
} }
fn spawn_future(&mut self, future: OwnedFuture<(), Error>) { fn spawn_future(&mut self, future: OwnedFuture<(), Error>) {
if let Some(channel) = self.channel.as_ref() { self.spawner.spawn_local(future);
channel.send(future).unwrap();
}
} }
fn resolve_relative_url<'a>(&self, url: &'a str) -> Cow<'a, str> { fn resolve_relative_url<'a>(&self, url: &'a str) -> Cow<'a, str> {

View File

@ -13,24 +13,21 @@ use ruffle_core::swf::{decompress_swf, parse_swf};
use ruffle_core::tag_utils::SwfMovie; use ruffle_core::tag_utils::SwfMovie;
use ruffle_core::Player; use ruffle_core::Player;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::path::Path;
use std::panic::catch_unwind;
use std::io::{stdout, Write}; use std::io::{stdout, Write};
use std::panic::catch_unwind;
use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
fn execute_swf(file: &Path) { fn execute_swf(file: &Path) {
let base_path = file.parent().unwrap(); let base_path = file.parent().unwrap();
let (_executor, channel) = NullExecutor::new(); let executor = NullExecutor::new();
let movie = SwfMovie::from_path(file, None).unwrap(); let movie = SwfMovie::from_path(file, None).unwrap();
let frame_time = 1000.0 / movie.frame_rate().to_f64(); let frame_time = 1000.0 / movie.frame_rate().to_f64();
let player = Player::new( let player = Player::new(
Box::new(NullRenderer::new()), Box::new(NullRenderer::new()),
Box::new(NullAudioBackend::new()), Box::new(NullAudioBackend::new()),
Box::new(NullNavigatorBackend::with_base_path(base_path, channel)), Box::new(NullNavigatorBackend::with_base_path(base_path, &executor)),
Box::new(MemoryStorageBackend::default()), Box::new(MemoryStorageBackend::default()),
Box::new(NullVideoBackend::new()), Box::new(NullVideoBackend::new()),
Box::new(ScanLogBackend::new()), Box::new(ScanLogBackend::new()),

View File

@ -1073,7 +1073,7 @@ fn run_swf(
check_img &= RUN_IMG_TESTS; check_img &= RUN_IMG_TESTS;
let base_path = Path::new(swf_path).parent().unwrap(); let base_path = Path::new(swf_path).parent().unwrap();
let (mut executor, channel) = NullExecutor::new(); let mut executor = NullExecutor::new();
let movie = SwfMovie::from_path(swf_path, None)?; let movie = SwfMovie::from_path(swf_path, None)?;
let frame_time = 1000.0 / movie.frame_rate().to_f64(); let frame_time = 1000.0 / movie.frame_rate().to_f64();
let trace_output = Rc::new(RefCell::new(Vec::new())); let trace_output = Rc::new(RefCell::new(Vec::new()));
@ -1115,7 +1115,7 @@ fn run_swf(
let player = Player::new( let player = Player::new(
render_backend, render_backend,
Box::new(NullAudioBackend::new()), Box::new(NullAudioBackend::new()),
Box::new(NullNavigatorBackend::with_base_path(base_path, channel)), Box::new(NullNavigatorBackend::with_base_path(base_path, &executor)),
Box::new(MemoryStorageBackend::default()), Box::new(MemoryStorageBackend::default()),
video_backend, video_backend,
Box::new(TestLogBackend::new(trace_output.clone())), Box::new(TestLogBackend::new(trace_output.clone())),
@ -1132,7 +1132,7 @@ fn run_swf(
for _ in 0..num_frames { for _ in 0..num_frames {
player.lock().unwrap().run_frame(); player.lock().unwrap().run_frame();
player.lock().unwrap().update_timers(frame_time); player.lock().unwrap().update_timers(frame_time);
executor.poll_all().unwrap(); executor.run();
} }
// Render the image to disk // Render the image to disk
@ -1193,7 +1193,7 @@ fn run_swf(
before_end(player)?; before_end(player)?;
executor.block_all().unwrap(); executor.run();
let trace = trace_output.borrow().join("\n"); let trace = trace_output.borrow().join("\n");
Ok(trace) Ok(trace)