react-rs-core 0.3.0

Core reactive primitives for react.rs - Signal, Effect, Memo, Context
Documentation
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;

thread_local! {
    pub static RUNTIME: RefCell<Runtime> = RefCell::new(Runtime::new());
}

pub type EffectId = usize;
pub type ScopeId = usize;
type EffectFn = Rc<dyn Fn()>;
type CleanupFn = Box<dyn FnOnce()>;

struct Scope {
    effects: Vec<EffectId>,
    children: Vec<ScopeId>,
    #[allow(dead_code)]
    parent: Option<ScopeId>,
    disposed: bool,
}

pub struct Runtime {
    effects: Vec<Option<EffectFn>>,
    effect_cleanups: Vec<Vec<CleanupFn>>,
    effect_disposed: Vec<bool>,
    current_effect: Option<EffectId>,
    pending_effects: VecDeque<EffectId>,
    is_batching: bool,
    scopes: Vec<Scope>,
    current_scope: Option<ScopeId>,
}

impl Runtime {
    pub fn new() -> Self {
        let root_scope = Scope {
            effects: Vec::new(),
            children: Vec::new(),
            parent: None,
            disposed: false,
        };
        Self {
            effects: Vec::new(),
            effect_cleanups: Vec::new(),
            effect_disposed: Vec::new(),
            current_effect: None,
            pending_effects: VecDeque::new(),
            is_batching: false,
            scopes: vec![root_scope],
            current_scope: Some(0),
        }
    }

    pub fn current_effect(&self) -> Option<EffectId> {
        self.current_effect
    }

    pub fn register_effect(&mut self, f: impl Fn() + 'static) -> EffectId {
        let id = self.effects.len();
        self.effects.push(Some(Rc::new(f)));
        self.effect_cleanups.push(Vec::new());
        self.effect_disposed.push(false);

        if let Some(scope_id) = self.current_scope {
            if scope_id < self.scopes.len() {
                self.scopes[scope_id].effects.push(id);
            }
        }

        id
    }

    pub fn set_current_effect(&mut self, id: Option<EffectId>) -> Option<EffectId> {
        let prev = self.current_effect;
        self.current_effect = id;
        prev
    }

    pub fn get_effect(&self, id: EffectId) -> Option<&EffectFn> {
        self.effects.get(id).and_then(|e| e.as_ref())
    }

    pub fn clone_effect(&self, id: EffectId) -> Option<EffectFn> {
        if self.is_effect_disposed(id) {
            return None;
        }
        self.effects.get(id).and_then(|e| e.as_ref().map(Rc::clone))
    }

    pub fn schedule_effect(&mut self, id: EffectId) {
        if self.is_effect_disposed(id) {
            return;
        }
        if !self.pending_effects.contains(&id) {
            self.pending_effects.push_back(id);
        }
    }

    pub fn is_batching(&self) -> bool {
        self.is_batching
    }

    pub fn pop_pending_effect(&mut self) -> Option<EffectId> {
        loop {
            match self.pending_effects.pop_front() {
                Some(id) if self.is_effect_disposed(id) => continue,
                other => return other,
            }
        }
    }

    pub fn start_batch(&mut self) -> bool {
        let was_batching = self.is_batching;
        self.is_batching = true;
        was_batching
    }

    pub fn end_batch(&mut self, was_batching: bool) {
        self.is_batching = was_batching;
    }

    pub fn is_effect_disposed(&self, id: EffectId) -> bool {
        self.effect_disposed.get(id).copied().unwrap_or(true)
    }

    pub fn create_scope(&mut self) -> ScopeId {
        let id = self.scopes.len();
        let parent = self.current_scope;
        self.scopes.push(Scope {
            effects: Vec::new(),
            children: Vec::new(),
            parent,
            disposed: false,
        });
        if let Some(parent_id) = parent {
            if parent_id < self.scopes.len() {
                self.scopes[parent_id].children.push(id);
            }
        }
        self.current_scope = Some(id);
        id
    }

    pub fn set_current_scope(&mut self, scope: Option<ScopeId>) -> Option<ScopeId> {
        let prev = self.current_scope;
        self.current_scope = scope;
        prev
    }

    pub fn dispose_scope(&mut self, scope_id: ScopeId) {
        if scope_id >= self.scopes.len() || self.scopes[scope_id].disposed {
            return;
        }

        let children: Vec<ScopeId> = self.scopes[scope_id].children.clone();
        for child_id in children {
            self.dispose_scope(child_id);
        }

        let effects: Vec<EffectId> = self.scopes[scope_id].effects.clone();
        for effect_id in effects {
            self.dispose_effect(effect_id);
        }

        self.scopes[scope_id].disposed = true;
    }

    fn dispose_effect(&mut self, effect_id: EffectId) {
        if effect_id >= self.effect_disposed.len() {
            return;
        }
        self.run_cleanups(effect_id);
        self.effect_disposed[effect_id] = true;
        if let Some(slot) = self.effects.get_mut(effect_id) {
            *slot = None;
        }
    }

    pub fn add_cleanup(&mut self, f: impl FnOnce() + 'static) {
        if let Some(effect_id) = self.current_effect {
            if effect_id < self.effect_cleanups.len() {
                self.effect_cleanups[effect_id].push(Box::new(f));
            }
        }
    }

    pub fn run_cleanups(&mut self, effect_id: EffectId) {
        if effect_id < self.effect_cleanups.len() {
            let cleanups = std::mem::take(&mut self.effect_cleanups[effect_id]);
            for cleanup in cleanups {
                cleanup();
            }
        }
    }
}

impl Default for Runtime {
    fn default() -> Self {
        Self::new()
    }
}