use std::{cell::RefCell, rc::Rc};
use wasm_bindgen::{JsCast, closure::Closure};
use web_sys::WebGlContextEvent;
use crate::Error;
#[derive(Debug, Clone)]
pub(crate) struct ContextState {
inner: Rc<RefCell<ContextStateInner>>,
}
#[derive(Debug, Default)]
struct ContextStateInner {
is_lost: bool,
pending_rebuild: bool,
}
impl ContextState {
fn new() -> Self {
Self {
inner: Rc::new(RefCell::new(ContextStateInner::default())),
}
}
pub(crate) fn is_lost(&self) -> bool {
self.inner.borrow().is_lost
}
pub(crate) fn pending_rebuild(&self) -> bool {
self.inner.borrow().pending_rebuild
}
fn set_lost(&self) {
let mut inner = self.inner.borrow_mut();
inner.is_lost = true;
}
fn set_restored(&self) {
let mut inner = self.inner.borrow_mut();
inner.is_lost = false;
inner.pending_rebuild = true;
}
pub fn clear_restoration_needed(&self) {
self.inner.borrow_mut().pending_rebuild = false;
}
}
pub(crate) struct ContextLossHandler {
canvas: web_sys::HtmlCanvasElement,
on_context_lost: Closure<dyn FnMut(WebGlContextEvent)>,
on_context_restored: Closure<dyn FnMut(WebGlContextEvent)>,
state: ContextState,
}
impl ContextLossHandler {
pub(crate) fn new(canvas: &web_sys::HtmlCanvasElement) -> Result<Self, Error> {
let state = ContextState::new();
let state_clone = state.clone();
let on_context_lost = Self::register_callback(canvas, "webglcontextlost", move |event| {
event.prevent_default();
state_clone.set_lost();
})?;
let state_clone = state.clone();
let on_context_restored =
Self::register_callback(canvas, "webglcontextrestored", move |_event| {
state_clone.set_restored();
})?;
Ok(Self {
canvas: canvas.clone(),
on_context_lost,
on_context_restored,
state,
})
}
pub(crate) fn is_context_lost(&self) -> bool {
self.state.is_lost()
}
pub(crate) fn context_pending_rebuild(&self) -> bool {
self.state.pending_rebuild()
}
pub(crate) fn clear_context_rebuild_needed(&self) {
self.state.clear_restoration_needed();
}
fn cleanup(&self) {
let _ = self.canvas.remove_event_listener_with_callback(
"webglcontextlost",
self.on_context_lost.as_ref().unchecked_ref(),
);
let _ = self.canvas.remove_event_listener_with_callback(
"webglcontextrestored",
self.on_context_restored.as_ref().unchecked_ref(),
);
}
fn register_callback(
canvas: &web_sys::HtmlCanvasElement,
event_type: &str,
f: impl 'static + FnMut(WebGlContextEvent),
) -> Result<Closure<dyn FnMut(WebGlContextEvent)>, Error> {
let callback = Closure::wrap(Box::new(f) as Box<dyn FnMut(_)>);
canvas
.add_event_listener_with_callback(event_type, callback.as_ref().unchecked_ref())
.map_err(|_| Error::Callback(format!("Failed to add {event_type} listener")))?;
Ok(callback)
}
}
impl Drop for ContextLossHandler {
fn drop(&mut self) {
self.cleanup();
}
}
impl std::fmt::Debug for ContextLossHandler {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ContextLossHandler")
.field("is_lost", &self.state.is_lost())
.field("pending_rebuild", &self.state.pending_rebuild())
.finish()
}
}