use std::cell::RefCell;
use std::collections::{hash_map::Entry, HashMap, VecDeque};
use std::rc::Rc;
pub type Shared<T> = Rc<RefCell<T>>;
pub trait Runnable {
fn run(self: Box<Self>);
}
#[derive(Default)]
#[allow(missing_debug_implementations)] struct Scheduler {
main: Vec<Box<dyn Runnable>>,
destroy: Vec<Box<dyn Runnable>>,
create: Vec<Box<dyn Runnable>>,
update: Vec<Box<dyn Runnable>>,
render_first: VecDeque<Box<dyn Runnable>>,
render: RenderScheduler,
rendered_first: Vec<Box<dyn Runnable>>,
rendered: RenderedScheduler,
}
#[inline]
fn with<R>(f: impl FnOnce(&mut Scheduler) -> R) -> R {
thread_local! {
static SCHEDULER: RefCell<Scheduler> = Default::default();
}
SCHEDULER.with(|s| f(&mut *s.borrow_mut()))
}
pub fn push(runnable: Box<dyn Runnable>) {
with(|s| s.main.push(runnable));
start();
}
pub(crate) fn push_component_create(
create: impl Runnable + 'static,
first_render: impl Runnable + 'static,
first_rendered: impl Runnable + 'static,
) {
with(|s| {
s.create.push(Box::new(create));
s.render_first.push_back(Box::new(first_render));
s.rendered_first.push(Box::new(first_rendered));
});
}
pub(crate) fn push_component_destroy(runnable: impl Runnable + 'static) {
with(|s| s.destroy.push(Box::new(runnable)));
}
pub(crate) fn push_component_render(
component_id: usize,
render: impl Runnable + 'static,
rendered: impl Runnable + 'static,
) {
with(|s| {
s.render.schedule(component_id, Box::new(render));
s.rendered.schedule(component_id, Box::new(rendered));
});
}
pub(crate) fn push_component_update(runnable: impl Runnable + 'static) {
with(|s| s.update.push(Box::new(runnable)));
}
pub(crate) fn start() {
thread_local! {
static LOCK: RefCell<()> = Default::default();
}
LOCK.with(|l| {
if let Ok(_lock) = l.try_borrow_mut() {
let mut queue = vec![];
loop {
with(|s| s.fill_queue(&mut queue));
if queue.is_empty() {
break;
}
for r in queue.drain(..) {
r.run();
}
}
}
});
}
impl Scheduler {
fn fill_queue(&mut self, to_run: &mut Vec<Box<dyn Runnable>>) {
to_run.append(&mut self.destroy);
to_run.append(&mut self.create);
if let Some(r) = self.render_first.pop_front() {
to_run.push(r);
}
if !to_run.is_empty() {
return;
}
to_run.extend(self.rendered_first.drain(..).rev());
to_run.append(&mut self.update);
to_run.append(&mut self.main);
if !to_run.is_empty() {
return;
}
if let Some(r) = self.render.pop() {
to_run.push(r);
}
if !to_run.is_empty() {
return;
}
self.rendered.drain_into(to_run);
}
}
struct QueueTask {
skip: usize,
runnable: Box<dyn Runnable>,
}
#[derive(Default)]
struct RenderScheduler {
tasks: HashMap<usize, QueueTask>,
queue: VecDeque<usize>,
}
impl RenderScheduler {
fn schedule(&mut self, component_id: usize, runnable: Box<dyn Runnable>) {
self.queue.push_back(component_id);
match self.tasks.entry(component_id) {
Entry::Vacant(e) => {
e.insert(QueueTask { skip: 0, runnable });
}
Entry::Occupied(mut e) => {
let v = e.get_mut();
v.skip += 1;
v.runnable = runnable;
}
}
}
fn pop(&mut self) -> Option<Box<dyn Runnable>> {
while let Some(id) = self.queue.pop_front() {
match self.tasks.entry(id) {
Entry::Occupied(mut e) => {
let v = e.get_mut();
if v.skip == 0 {
return Some(e.remove().runnable);
}
v.skip -= 1;
}
Entry::Vacant(_) => (),
}
}
None
}
}
#[derive(Default)]
struct RenderedScheduler {
tasks: HashMap<usize, Box<dyn Runnable>>,
stack: Vec<usize>,
}
impl RenderedScheduler {
fn schedule(&mut self, component_id: usize, runnable: Box<dyn Runnable>) {
if self.tasks.insert(component_id, runnable).is_none() {
self.stack.push(component_id);
}
}
fn drain_into(&mut self, dst: &mut Vec<Box<dyn Runnable>>) {
for id in self.stack.drain(..).rev() {
if let Some(t) = self.tasks.remove(&id) {
dst.push(t);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn push_executes_runnables_immediately() {
use std::cell::Cell;
thread_local! {
static FLAG: Cell<bool> = Default::default();
}
struct Test;
impl Runnable for Test {
fn run(self: Box<Self>) {
FLAG.with(|v| v.set(true));
}
}
push(Box::new(Test));
FLAG.with(|v| assert!(v.get()));
}
}