use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::ops::Deref;
use std::rc::{Rc, Weak};
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
use web_sys::{window, WebGl2RenderingContext as Gl, WebGlSync};
use crate::runtime::executor_job::{ExecutorJob, JobState};
use crate::runtime::Connection;
pub(crate) struct FencedTaskQueue {
queue: VecDeque<(WebGlSync, Box<dyn ExecutorJob>)>,
connection: Rc<RefCell<Connection>>,
}
impl FencedTaskQueue {
pub(crate) fn new(connection: Rc<RefCell<Connection>>) -> Self {
FencedTaskQueue {
queue: VecDeque::new(),
connection,
}
}
pub(crate) fn push(&mut self, job: Box<dyn ExecutorJob>) {
let connection = self.connection.borrow_mut();
let (gl, _) = unsafe { connection.unpack() };
let fence = gl.fence_sync(Gl::SYNC_GPU_COMMANDS_COMPLETE, 0).unwrap();
self.queue.push_back((fence, job));
}
pub(crate) fn run(&mut self) -> bool {
let gl = {
let connection = self.connection.borrow_mut();
let (gl, _) = unsafe { connection.unpack() };
gl.clone()
};
while let Some((fence, _)) = self.queue.front() {
let sync_status = gl
.get_sync_parameter(fence, Gl::SYNC_STATUS)
.as_f64()
.unwrap() as u32;
if sync_status == Gl::SIGNALED {
let (_, mut job) = self.queue.pop_front().unwrap();
if JobState::ContinueFenced == job.progress(&mut self.connection.borrow_mut()) {
let new_fence = gl.fence_sync(Gl::SYNC_GPU_COMMANDS_COMPLETE, 0).unwrap();
self.queue.push_back((new_fence, job));
}
} else {
break;
}
}
self.queue.is_empty()
}
}
pub(crate) struct JsTimeoutFencedTaskRunner {
queue: Rc<RefCell<FencedTaskQueue>>,
loop_handle: Option<JsTimeoutFencedTaskLoopHandle>,
}
impl JsTimeoutFencedTaskRunner {
pub(crate) fn new(connection: Rc<RefCell<Connection>>) -> Self {
JsTimeoutFencedTaskRunner {
queue: Rc::new(RefCell::new(FencedTaskQueue::new(connection))),
loop_handle: None,
}
}
pub(crate) fn schedule(&mut self, job: Box<dyn ExecutorJob>) {
self.queue.borrow_mut().push(job);
let loop_running = if let Some(handle) = &self.loop_handle {
!handle.cancelled()
} else {
false
};
if !loop_running {
self.loop_handle = Some(JsTimeoutFencedTaskLoop::init(self.queue.clone()));
}
}
}
#[derive(Clone)]
struct JsTimeoutFencedTaskLoop {
queue: Rc<RefCell<FencedTaskQueue>>,
closure: Weak<Option<Closure<dyn FnMut()>>>,
handle: Rc<Cell<i32>>,
cancelled: Rc<Cell<bool>>,
}
impl JsTimeoutFencedTaskLoop {
fn init(queue: Rc<RefCell<FencedTaskQueue>>) -> JsTimeoutFencedTaskLoopHandle {
let handle = Rc::new(Cell::new(0));
let cancelled = Rc::new(Cell::new(false));
let mut closure_container = Rc::new(None);
let closure = Closure::wrap(Box::new(JsTimeoutFencedTaskLoop {
queue,
closure: Rc::downgrade(&closure_container),
handle: handle.clone(),
cancelled: cancelled.clone(),
}) as Box<dyn FnMut()>);
let handle_id = window()
.unwrap()
.set_timeout_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
1,
)
.unwrap();
unsafe {
*Rc::get_mut_unchecked(&mut closure_container) = Some(closure);
}
handle.set(handle_id);
JsTimeoutFencedTaskLoopHandle {
inner: handle,
closure_container,
cancelled,
}
}
}
impl FnOnce<()> for JsTimeoutFencedTaskLoop {
type Output = ();
extern "rust-call" fn call_once(mut self, _: ()) -> () {
self.call_mut(())
}
}
impl FnMut<()> for JsTimeoutFencedTaskLoop {
extern "rust-call" fn call_mut(&mut self, _: ()) -> () {
let is_empty = self.queue.borrow_mut().run();
if !is_empty {
if let Some(container) = self.closure.upgrade() {
let closure = container
.deref()
.as_ref()
.expect("Uninitialized closure container.");
let handle_id = window()
.unwrap()
.set_timeout_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
1,
)
.unwrap();
self.handle.set(handle_id);
}
} else {
self.cancelled.set(true);
}
}
}
struct JsTimeoutFencedTaskLoopHandle {
inner: Rc<Cell<i32>>,
#[allow(dead_code)] closure_container: Rc<Option<Closure<dyn FnMut()>>>,
cancelled: Rc<Cell<bool>>,
}
impl JsTimeoutFencedTaskLoopHandle {
fn cancelled(&self) -> bool {
self.cancelled.get()
}
fn cancel(&mut self) -> bool {
if !self.cancelled.get() {
window()
.unwrap()
.clear_timeout_with_handle(self.inner.get());
self.cancelled.set(true);
true
} else {
false
}
}
}
impl Drop for JsTimeoutFencedTaskLoopHandle {
fn drop(&mut self) {
self.cancel();
}
}