use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
use std::sync::mpsc::{self, Receiver, Sender};
use std::time::{Duration, Instant};
use boa_engine::{Context, JsError};
use crate::{
events::{HitMap, HtmlEvent, SharedHitMap},
resources::AsyncResult,
source::SharedSourceText,
tdom::node::{NodeArena, NodeId},
tui::graph_api::{DirtyInfo, RenderCmd},
};
use super::host::HostFn;
pub(crate) struct ScriptState {
pub arena: NodeArena,
pub root_id: NodeId,
pub document_css: SharedSourceText,
pub document_reload: bool,
pub session_js: SharedSourceText,
pub render_cmds: Vec<RenderCmd>,
pub dirty: DirtyInfo,
pub current_event: Option<HtmlEvent>,
pub host_fns: HashMap<String, HostFn>,
pub timers: Vec<TimerEntry>,
pub next_timer_id: u32,
pub active_fetches: std::collections::HashSet<u32>,
pub next_fetch_id: u32,
pub fetch_rx: Receiver<AsyncResult>,
pub fetch_tx: Sender<AsyncResult>,
pub frame_epoch: Instant,
}
impl ScriptState {
pub(super) fn fetch_channel() -> (Sender<AsyncResult>, Receiver<AsyncResult>) {
mpsc::channel()
}
}
pub(crate) struct TimerEntry {
pub id: u32,
pub deadline: Instant,
pub period: Duration,
pub repeat: bool,
pub frame_time: Option<f64>,
}
thread_local! {
pub(crate) static ENGINE_STATE: RefCell<Option<Rc<RefCell<ScriptState>>>> = const {RefCell::new(None)} ;
static UI_STATE: RefCell<Option<std::sync::Arc<SharedUiState>>> = const { RefCell::new(None) };
static HIT_MAP: RefCell<Option<SharedHitMap>> = const { RefCell::new(None) };
static SELECTION_TEXT: RefCell<Option<std::sync::Arc<Mutex<String>>>> =
const { RefCell::new(None) };
}
#[derive(Debug, Default)]
pub struct SharedUiState {
pub cols: AtomicU32,
pub rows: AtomicU32,
pub scroll_y: AtomicU32,
pub focused: AtomicU64,
pub has_wheel_listeners: AtomicBool,
}
impl SharedUiState {
pub fn load(&self) -> (u32, u32, u32) {
(
self.cols.load(Ordering::Relaxed),
self.rows.load(Ordering::Relaxed),
self.scroll_y.load(Ordering::Relaxed),
)
}
pub fn set_focused(&self, node_id: Option<NodeId>) {
let bits = node_id.map_or(0, |id| slotmap::Key::data(&id).as_ffi());
self.focused.store(bits, Ordering::Relaxed);
}
pub fn focused(&self) -> Option<NodeId> {
let bits = self.focused.load(Ordering::Relaxed);
(bits != 0).then(|| slotmap::KeyData::from_ffi(bits).into())
}
}
pub(crate) fn set_ui_state(ui: std::sync::Arc<SharedUiState>) {
UI_STATE.with(|cell| *cell.borrow_mut() = Some(ui));
}
pub(crate) fn with_ui() -> Option<(u32, u32, u32)> {
UI_STATE.with(|cell| cell.borrow().as_ref().map(|ui| ui.load()))
}
pub(crate) fn with_focused() -> u64 {
UI_STATE.with(|cell| {
cell.borrow()
.as_ref()
.map_or(0, |ui| ui.focused.load(Ordering::Relaxed))
})
}
pub(crate) fn note_wheel_listener() {
UI_STATE.with(|cell| {
if let Some(ui) = cell.borrow().as_ref() {
ui.has_wheel_listeners.store(true, Ordering::Relaxed);
}
});
}
pub(crate) fn set_hit_map(hit_map: SharedHitMap) {
HIT_MAP.with(|cell| *cell.borrow_mut() = Some(hit_map));
}
pub(crate) fn with_hit_map() -> Option<std::sync::Arc<HitMap>> {
HIT_MAP.with(|cell| {
cell.borrow().as_ref().and_then(|shared| {
shared
.lock()
.ok()
.map(|guard| std::sync::Arc::clone(&guard))
})
})
}
pub(crate) fn set_selection_text(text: std::sync::Arc<Mutex<String>>) {
SELECTION_TEXT.with(|cell| *cell.borrow_mut() = Some(text));
}
pub(crate) fn selection_text() -> String {
SELECTION_TEXT.with(|cell| {
cell.borrow()
.as_ref()
.and_then(|text| text.lock().ok().map(|guard| guard.clone()))
.unwrap_or_default()
})
}
pub(super) type EngineParts = (Context, Rc<RefCell<ScriptState>>);
pub(super) type EngineBuildError = (JsError, HashMap<String, HostFn>);