use crate::{innerlude::*, unsafe_utils::extend_vnode};
use bumpalo::Bump;
use futures_channel::mpsc::UnboundedSender;
use fxhash::FxHashMap;
use slab::Slab;
use std::{
any::{Any, TypeId},
borrow::Borrow,
cell::{Cell, RefCell},
collections::{HashMap, HashSet},
future::Future,
pin::Pin,
rc::Rc,
sync::Arc,
};
pub(crate) type ComponentPtr = *mut std::os::raw::c_void;
pub(crate) struct Heuristic {
hook_arena_size: usize,
node_arena_size: usize,
}
pub(crate) struct ScopeArena {
pub scope_gen: Cell<usize>,
pub bump: Bump,
pub scopes: RefCell<FxHashMap<ScopeId, *mut ScopeState>>,
pub heuristics: RefCell<FxHashMap<ComponentPtr, Heuristic>>,
pub free_scopes: RefCell<Vec<*mut ScopeState>>,
pub nodes: RefCell<Slab<*const VNode<'static>>>,
pub tasks: Rc<TaskQueue>,
}
impl ScopeArena {
pub(crate) fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
let bump = Bump::new();
let el = bump.alloc(VElement {
tag: "root",
namespace: None,
key: None,
id: Cell::new(Some(ElementId(0))),
parent: Default::default(),
listeners: &[],
attributes: &[],
children: &[],
});
let node = bump.alloc(VNode::Element(el));
let mut nodes = Slab::new();
let root_id = nodes.insert(unsafe { std::mem::transmute(node as *const _) });
debug_assert_eq!(root_id, 0);
Self {
scope_gen: Cell::new(0),
bump,
scopes: RefCell::new(FxHashMap::default()),
heuristics: RefCell::new(FxHashMap::default()),
free_scopes: RefCell::new(Vec::new()),
nodes: RefCell::new(nodes),
tasks: Rc::new(TaskQueue {
tasks: RefCell::new(FxHashMap::default()),
task_map: RefCell::new(FxHashMap::default()),
gen: Cell::new(0),
sender,
}),
}
}
pub(crate) fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
unsafe { self.scopes.borrow().get(&id).map(|f| &**f) }
}
pub(crate) fn get_scope_raw(&self, id: ScopeId) -> Option<*mut ScopeState> {
self.scopes.borrow().get(&id).copied()
}
pub(crate) fn new_with_key(
&self,
fc_ptr: ComponentPtr,
vcomp: Box<dyn AnyProps>,
parent_scope: Option<ScopeId>,
container: ElementId,
subtree: u32,
) -> ScopeId {
let new_scope_id = ScopeId(self.scope_gen.get());
self.scope_gen.set(self.scope_gen.get() + 1);
let height = parent_scope
.and_then(|id| self.get_scope(id).map(|scope| scope.height + 1))
.unwrap_or_default();
let parent_scope = parent_scope.and_then(|f| self.get_scope_raw(f));
if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
let scope = unsafe { &mut *old_scope };
scope.container = container;
scope.our_arena_idx = new_scope_id;
scope.parent_scope = parent_scope;
scope.height = height;
scope.fnptr = fc_ptr;
scope.props.get_mut().replace(vcomp);
scope.subtree.set(subtree);
scope.frames[0].reset();
scope.frames[1].reset();
scope.shared_contexts.get_mut().clear();
scope.items.get_mut().listeners.clear();
scope.items.get_mut().borrowed_props.clear();
scope.hook_idx.set(0);
scope.hook_vals.get_mut().clear();
let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
debug_assert!(any_item.is_none());
} else {
let (node_capacity, hook_capacity) = self
.heuristics
.borrow()
.get(&fc_ptr)
.map(|h| (h.node_arena_size, h.hook_arena_size))
.unwrap_or_default();
self.scopes.borrow_mut().insert(
new_scope_id,
self.bump.alloc(ScopeState {
container,
our_arena_idx: new_scope_id,
parent_scope,
height,
fnptr: fc_ptr,
props: RefCell::new(Some(vcomp)),
frames: [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)],
subtree: Cell::new(0),
is_subtree_root: Cell::new(false),
generation: 0.into(),
tasks: self.tasks.clone(),
shared_contexts: Default::default(),
items: RefCell::new(SelfReferentialItems {
listeners: Default::default(),
borrowed_props: Default::default(),
}),
hook_arena: Bump::new(),
hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)),
hook_idx: Default::default(),
}),
);
}
new_scope_id
}
pub fn try_remove(&self, id: ScopeId) -> Option<()> {
log::trace!("removing scope {:?}", id);
self.ensure_drop_safety(id);
let mut tasks = self.tasks.tasks.borrow_mut();
let mut task_map = self.tasks.task_map.borrow_mut();
if let Some(cur_tasks) = task_map.remove(&id) {
for task in cur_tasks {
tasks.remove(&task);
}
}
let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() };
scope.reset();
self.free_scopes.borrow_mut().push(scope);
Some(())
}
pub fn reserve_node<'a>(&self, node: &'a VNode<'a>) -> ElementId {
let mut els = self.nodes.borrow_mut();
let entry = els.vacant_entry();
let key = entry.key();
let id = ElementId(key);
let node = unsafe { extend_vnode(node) };
entry.insert(node as *const _);
id
}
pub fn update_node<'a>(&self, node: &'a VNode<'a>, id: ElementId) {
let node = unsafe { extend_vnode(node) };
*self.nodes.borrow_mut().get_mut(id.0).unwrap() = node;
}
pub fn collect_garbage(&self, id: ElementId) {
self.nodes.borrow_mut().remove(id.0);
}
pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
if let Some(scope) = self.get_scope(scope_id) {
let mut items = scope.items.borrow_mut();
items.borrowed_props.drain(..).for_each(|comp| {
if let Some(scope_id) = comp.scope.get() {
self.ensure_drop_safety(scope_id);
}
drop(comp.props.take());
});
items
.listeners
.drain(..)
.for_each(|listener| drop(listener.callback.borrow_mut().take()));
}
}
pub(crate) fn run_scope(&self, id: ScopeId) {
self.ensure_drop_safety(id);
let scope = unsafe { &mut *self.get_scope_raw(id).expect("could not find scope") };
log::trace!("running scope {:?} symbol: {:?}", id, scope.fnptr);
scope.hook_idx.set(0);
{
unsafe { scope.reset_wip_frame() };
let items = scope.items.borrow();
debug_assert!(items.listeners.is_empty());
debug_assert!(items.borrowed_props.is_empty());
}
let props = scope.props.borrow();
let render = props.as_ref().unwrap();
if let Some(node) = render.render(scope) {
let frame = scope.wip_frame();
let node = frame.bump.alloc(node);
frame.node.set(unsafe { extend_vnode(node) });
} else {
let frame = scope.wip_frame();
let node = frame
.bump
.alloc(VNode::Placeholder(frame.bump.alloc(VPlaceholder {
id: Default::default(),
})));
frame.node.set(unsafe { extend_vnode(node) });
}
scope.cycle_frame();
}
pub fn call_listener_with_bubbling(&self, event: UserEvent, element: ElementId) {
let nodes = self.nodes.borrow();
let mut cur_el = Some(element);
log::trace!("calling listener {:?}, {:?}", event, element);
let state = Rc::new(BubbleState::new());
while let Some(id) = cur_el.take() {
if let Some(el) = nodes.get(id.0) {
let real_el = unsafe { &**el };
log::trace!("looking for listener on {:?}", real_el);
if let VNode::Element(real_el) = real_el {
for listener in real_el.listeners.borrow().iter() {
if listener.event == event.name {
log::trace!("calling listener {:?}", listener.event);
if state.canceled.get() {
break;
}
let mut cb = listener.callback.borrow_mut();
if let Some(cb) = cb.as_mut() {
(cb)(AnyEvent {
bubble_state: state.clone(),
data: event.data.clone(),
});
}
}
}
cur_el = real_el.parent.get();
}
}
}
}
pub fn wip_head(&self, id: ScopeId) -> &VNode {
let scope = self.get_scope(id).unwrap();
let frame = scope.wip_frame();
let node = unsafe { &*frame.node.get() };
unsafe { extend_vnode(node) }
}
pub fn fin_head(&self, id: ScopeId) -> &VNode {
let scope = self.get_scope(id).unwrap();
let frame = scope.fin_frame();
let node = unsafe { &*frame.node.get() };
unsafe { extend_vnode(node) }
}
pub fn root_node(&self, id: ScopeId) -> &VNode {
self.fin_head(id)
}
pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
let ptr = self.nodes.borrow().get(id.0).cloned();
match ptr {
Some(ptr) => {
let node = unsafe { &*ptr };
Some(unsafe { extend_vnode(node) })
}
None => None,
}
}
}
pub struct Scope<'a, P = ()> {
pub scope: &'a ScopeState,
pub props: &'a P,
}
impl<P> Copy for Scope<'_, P> {}
impl<P> Clone for Scope<'_, P> {
fn clone(&self) -> Self {
Self {
scope: self.scope,
props: self.props,
}
}
}
impl<'a, P> std::ops::Deref for Scope<'a, P> {
type Target = &'a ScopeState;
fn deref(&self) -> &Self::Target {
&self.scope
}
}
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
pub struct ScopeId(pub usize);
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct TaskId {
pub id: usize,
pub scope: ScopeId,
}
pub struct ScopeState {
pub(crate) parent_scope: Option<*mut ScopeState>,
pub(crate) container: ElementId,
pub(crate) our_arena_idx: ScopeId,
pub(crate) height: u32,
pub(crate) fnptr: ComponentPtr,
pub(crate) is_subtree_root: Cell<bool>,
pub(crate) subtree: Cell<u32>,
pub(crate) props: RefCell<Option<Box<dyn AnyProps>>>,
pub(crate) frames: [BumpFrame; 2],
pub(crate) generation: Cell<u32>,
pub(crate) items: RefCell<SelfReferentialItems<'static>>,
pub(crate) hook_arena: Bump,
pub(crate) hook_vals: RefCell<Vec<*mut dyn Any>>,
pub(crate) hook_idx: Cell<usize>,
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
pub(crate) tasks: Rc<TaskQueue>,
}
pub struct SelfReferentialItems<'a> {
pub(crate) listeners: Vec<&'a Listener<'a>>,
pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
}
impl ScopeState {
pub(crate) fn _subtree(&self) -> u32 {
self.subtree.get()
}
pub(crate) fn _create_subtree(&self) -> Option<u32> {
if self.is_subtree_root.get() {
None
} else {
todo!()
}
}
pub fn height(&self) -> u32 {
self.height
}
pub fn parent(&self) -> Option<ScopeId> {
self.parent_scope.map(|p| unsafe { &*p }.our_arena_idx)
}
pub fn scope_id(&self) -> ScopeId {
self.our_arena_idx
}
pub fn scheduler_channel(&self) -> UnboundedSender<SchedulerMsg> {
self.tasks.sender.clone()
}
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
Arc::new(move || {
let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
})
}
pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
let chan = self.tasks.sender.clone();
Arc::new(move |id| {
let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
})
}
pub fn needs_update(&self) {
self.needs_update_any(self.scope_id())
}
pub fn needs_update_any(&self, id: ScopeId) {
let _ = self
.tasks
.sender
.unbounded_send(SchedulerMsg::Immediate(id));
}
pub fn root_node(&self) -> &VNode {
let node = unsafe { &*self.fin_frame().node.get() };
unsafe { std::mem::transmute(node) }
}
pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
self.shared_contexts
.borrow_mut()
.insert(TypeId::of::<T>(), Box::new(value.clone()))
.and_then(|f| f.downcast::<T>().ok());
value
}
pub fn provide_root_context<T: 'static + Clone>(&self, value: T) -> T {
if self.scope_id() == ScopeId(0) {
self.shared_contexts
.borrow_mut()
.insert(TypeId::of::<T>(), Box::new(value.clone()))
.and_then(|f| f.downcast::<T>().ok());
return value;
}
let mut search_parent = self.parent_scope;
while let Some(parent) = search_parent.take() {
let parent = unsafe { &*parent };
if parent.scope_id() == ScopeId(0) {
let exists = parent
.shared_contexts
.borrow_mut()
.insert(TypeId::of::<T>(), Box::new(value.clone()));
if exists.is_some() {
log::warn!("Context already provided to parent scope - replacing it");
}
return value;
}
search_parent = parent.parent_scope;
}
unreachable!("all apps have a root scope")
}
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
if let Some(shared) = self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
Some((*shared.downcast_ref::<T>().unwrap()).clone())
} else {
let mut search_parent = self.parent_scope;
while let Some(parent_ptr) = search_parent {
let parent = unsafe { &*parent_ptr };
if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
return Some(shared.downcast_ref::<T>().unwrap().clone());
}
search_parent = parent.parent_scope;
}
None
}
}
pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
self.tasks
.sender
.unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
.unwrap();
self.tasks.spawn(self.our_arena_idx, fut)
}
pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) {
self.push_future(fut);
}
pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
self.tasks
.sender
.unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
.unwrap();
self.tasks.spawn(ScopeId(0), fut)
}
pub fn remove_future(&self, id: TaskId) {
self.tasks.remove(id);
}
pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option<VNode<'src>> {
Some(rsx.call(NodeFactory {
scope: self,
bump: &self.wip_frame().bump,
}))
}
#[allow(clippy::mut_from_ref)]
pub fn use_hook<'src, State: 'static>(
&'src self,
initializer: impl FnOnce(usize) -> State,
) -> &'src mut State {
let mut vals = self.hook_vals.borrow_mut();
let hook_len = vals.len();
let cur_idx = self.hook_idx.get();
if cur_idx >= hook_len {
vals.push(self.hook_arena.alloc(initializer(hook_len)));
}
vals
.get(cur_idx)
.and_then(|inn| {
self.hook_idx.set(cur_idx + 1);
let raw_box = unsafe { &mut **inn };
raw_box.downcast_mut::<State>()
})
.expect(
r###"
Unable to retrieve the hook that was initialized at this index.
Consult the `rules of hooks` to understand how to use hooks properly.
You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
Functions prefixed with "use" should never be called conditionally.
"###,
)
}
pub(crate) fn wip_frame(&self) -> &BumpFrame {
match self.generation.get() & 1 == 0 {
true => &self.frames[0],
false => &self.frames[1],
}
}
pub(crate) fn wip_frame_mut(&mut self) -> &mut BumpFrame {
match self.generation.get() & 1 == 0 {
true => &mut self.frames[0],
false => &mut self.frames[1],
}
}
pub(crate) fn fin_frame(&self) -> &BumpFrame {
match self.generation.get() & 1 == 1 {
true => &self.frames[0],
false => &self.frames[1],
}
}
pub(crate) unsafe fn reset_wip_frame(&mut self) {
self.wip_frame_mut().bump.reset();
}
pub(crate) fn cycle_frame(&self) {
self.generation.set(self.generation.get() + 1);
}
pub(crate) fn reset(&mut self) {
self.hook_idx.set(0);
self.parent_scope = None;
self.generation.set(0);
self.is_subtree_root.set(false);
self.subtree.set(0);
self.shared_contexts.get_mut().clear();
let SelfReferentialItems {
borrowed_props,
listeners,
} = self.items.get_mut();
borrowed_props.clear();
listeners.clear();
self.frames[0].reset();
self.frames[1].reset();
self.hook_vals.get_mut().drain(..).for_each(|state| {
let as_mut = unsafe { &mut *state };
let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) };
drop(boxed);
});
self.hook_arena.reset();
}
}
pub(crate) struct BumpFrame {
pub bump: Bump,
pub node: Cell<*const VNode<'static>>,
}
impl BumpFrame {
pub(crate) fn new(capacity: usize) -> Self {
let bump = Bump::with_capacity(capacity);
let node = &*bump.alloc(VText {
text: "placeholdertext",
id: Default::default(),
is_static: false,
});
let node = bump.alloc(VNode::Text(unsafe { std::mem::transmute(node) }));
let nodes = Cell::new(node as *const _);
Self { bump, node: nodes }
}
pub(crate) fn reset(&mut self) {
self.bump.reset();
let node = self.bump.alloc(VText {
text: "placeholdertext",
id: Default::default(),
is_static: false,
});
let node = self
.bump
.alloc(VNode::Text(unsafe { std::mem::transmute(node) }));
self.node.set(node as *const _);
}
}
pub(crate) struct TaskQueue {
pub(crate) tasks: RefCell<FxHashMap<TaskId, InnerTask>>,
pub(crate) task_map: RefCell<FxHashMap<ScopeId, HashSet<TaskId>>>,
gen: Cell<usize>,
sender: UnboundedSender<SchedulerMsg>,
}
pub(crate) type InnerTask = Pin<Box<dyn Future<Output = ()>>>;
impl TaskQueue {
fn spawn(&self, scope: ScopeId, task: impl Future<Output = ()> + 'static) -> TaskId {
let pinned = Box::pin(task);
let id = self.gen.get();
self.gen.set(id + 1);
let tid = TaskId { id, scope };
self.tasks.borrow_mut().insert(tid, pinned);
self.task_map
.borrow_mut()
.entry(scope)
.or_default()
.insert(tid);
tid
}
fn remove(&self, id: TaskId) {
if let Ok(mut tasks) = self.tasks.try_borrow_mut() {
let _ = tasks.remove(&id);
}
if let Some(task_map) = self.task_map.borrow_mut().get_mut(&id.scope) {
task_map.remove(&id);
}
}
pub(crate) fn has_tasks(&self) -> bool {
!self.tasks.borrow().is_empty()
}
}
#[test]
fn sizeof() {
dbg!(std::mem::size_of::<ScopeState>());
}