use std::cell::{Cell, RefCell};
use arch::Raf;
use futures_signals::signal::{Mutable, Signal, SignalExt};
use silkenweb_macros::cfg_browser;
pub(crate) mod local;
pub use arch::scope;
pub use arch::sync_scope;
pub use silkenweb_task::{run_tasks, spawn_local, TaskSignal, TaskSignalVec};
#[cfg_browser(false)]
pub mod server {
pub use silkenweb_task::server::{block_on, run_tasks_sync};
use super::Render;
pub fn render_now_sync() {
Render::with(Render::render_effects);
run_tasks_sync();
}
}
#[cfg_browser(false)]
mod arch {
use futures::Future;
use super::local;
pub struct Raf;
impl Raf {
pub fn new() -> Self {
Self
}
pub fn request_animation_frame(&self) {}
}
pub fn scope<Fut>(f: Fut) -> impl Future<Output = Fut::Output>
where
Fut: Future,
{
local::TASK_LOCAL.scope(local::TaskLocal::default(), silkenweb_task::scope(f))
}
pub fn sync_scope<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
local::TASK_LOCAL.sync_scope(local::TaskLocal::default(), || {
silkenweb_task::sync_scope(f)
})
}
}
#[cfg_browser(true)]
mod arch {
use std::future::Future;
use silkenweb_base::window;
use wasm_bindgen::{prelude::Closure, JsCast, JsValue, UnwrapThrowExt};
use super::Render;
pub struct Raf {
on_raf: Closure<dyn FnMut(JsValue)>,
}
impl Raf {
pub fn new() -> Self {
Self {
on_raf: Closure::wrap(Box::new(|time_stamp: JsValue| {
Render::with(|render| render.on_raf(time_stamp.as_f64().unwrap_throw()));
})),
}
}
pub fn request_animation_frame(&self) {
window::request_animation_frame(self.on_raf.as_ref().unchecked_ref());
}
}
pub fn scope<Fut>(f: Fut) -> impl Future<Output = Fut::Output>
where
Fut: Future,
{
silkenweb_task::scope(f)
}
pub fn sync_scope<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
silkenweb_task::sync_scope(f)
}
}
pub fn on_animation_frame(f: impl FnOnce() + 'static) {
Render::with(|render| render.on_animation_frame(f));
}
pub(super) fn animation_timestamp() -> impl Signal<Item = f64> {
Render::with(Render::animation_timestamp)
}
pub(super) fn request_animation_frame() {
Render::with(Render::request_animation_frame);
}
pub async fn render_now() {
run_tasks().await;
Render::with(Render::render_effects);
}
pub(crate) struct TaskLocal {
render: Render,
}
impl Default for TaskLocal {
fn default() -> Self {
Self {
render: Render::new(),
}
}
}
struct Render {
raf: Raf,
raf_pending: Cell<bool>,
pending_effects: RefCell<Vec<Box<dyn FnOnce()>>>,
animation_timestamp_millis: Mutable<f64>,
}
impl Render {
fn new() -> Self {
Self {
raf: Raf::new(),
raf_pending: Cell::new(false),
pending_effects: RefCell::new(Vec::new()),
animation_timestamp_millis: Mutable::new(0.0),
}
}
fn with<R>(f: impl FnOnce(&Self) -> R) -> R {
local::with(|local| f(&local.task.render))
}
#[cfg_browser(true)]
fn on_raf(&self, time_stamp: f64) {
self.raf_pending.set(false);
self.animation_timestamp_millis.set(time_stamp);
self.render_effects();
}
fn on_animation_frame(&self, x: impl FnOnce() + 'static) {
self.pending_effects.borrow_mut().push(Box::new(x));
self.request_animation_frame();
}
fn animation_timestamp(&self) -> impl Signal<Item = f64> {
let base = self.base_timestamp();
self.animation_timestamp_millis.signal().map(move |ts| {
let relative_ts = ts - base;
if relative_ts > 0.0 {
relative_ts
} else {
0.0
}
})
}
#[cfg_browser(true)]
fn base_timestamp(&self) -> f64 {
silkenweb_base::window::performance().unwrap().now()
}
#[cfg_browser(false)]
fn base_timestamp(&self) -> f64 {
self.animation_timestamp_millis.get()
}
pub fn render_effects(&self) {
for effect in self.pending_effects.take() {
effect();
}
}
fn request_animation_frame(&self) {
if !self.raf_pending.get() {
self.raf_pending.set(true);
self.raf.request_animation_frame();
}
}
}