use crate::{
any_props::AnyProps,
any_props::VProps,
arena::ElementId,
bump_frame::BumpFrame,
innerlude::{DynamicNode, EventHandler, VComponent, VText},
innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
lazynodes::LazyNodes,
nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn},
AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId,
};
use bumpalo::{boxed::Box as BumpBox, Bump};
use bumpslab::{BumpSlab, Slot};
use rustc_hash::{FxHashMap, FxHashSet};
use slab::{Slab, VacantEntry};
use std::{
any::{Any, TypeId},
cell::{Cell, RefCell},
fmt::{Arguments, Debug},
future::Future,
ops::{Index, IndexMut},
rc::Rc,
sync::Arc,
};
pub type Scope<'a, T = ()> = &'a Scoped<'a, T>;
pub struct Scoped<'a, T = ()> {
pub scope: &'a ScopeState,
pub props: &'a T,
}
impl<'a, T> std::ops::Deref for Scoped<'a, T> {
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);
pub(crate) struct ScopeSlab {
slab: BumpSlab<ScopeState>,
entries: Slab<Slot<'static, ScopeState>>,
}
impl Drop for ScopeSlab {
fn drop(&mut self) {
for slot in self.entries.drain() {
self.slab.remove(slot);
}
}
}
impl Default for ScopeSlab {
fn default() -> Self {
Self {
slab: BumpSlab::new(),
entries: Slab::new(),
}
}
}
impl ScopeSlab {
pub(crate) fn get(&self, id: ScopeId) -> Option<&ScopeState> {
self.entries.get(id.0).map(|slot| unsafe { &*slot.ptr() })
}
pub(crate) fn get_mut(&mut self, id: ScopeId) -> Option<&mut ScopeState> {
self.entries
.get(id.0)
.map(|slot| unsafe { &mut *slot.ptr_mut() })
}
pub(crate) fn vacant_entry(&mut self) -> ScopeSlabEntry {
let entry = self.entries.vacant_entry();
ScopeSlabEntry {
slab: &mut self.slab,
entry,
}
}
pub(crate) fn remove(&mut self, id: ScopeId) {
self.slab.remove(self.entries.remove(id.0));
}
pub(crate) fn contains(&self, id: ScopeId) -> bool {
self.entries.contains(id.0)
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &ScopeState> {
self.entries.iter().map(|(_, slot)| unsafe { &*slot.ptr() })
}
}
pub(crate) struct ScopeSlabEntry<'a> {
slab: &'a mut BumpSlab<ScopeState>,
entry: VacantEntry<'a, Slot<'static, ScopeState>>,
}
impl<'a> ScopeSlabEntry<'a> {
pub(crate) fn key(&self) -> ScopeId {
ScopeId(self.entry.key())
}
pub(crate) fn insert(self, scope: ScopeState) -> &'a ScopeState {
let slot = self.slab.push(scope);
let slot = unsafe { std::mem::transmute(slot) };
let entry = self.entry.insert(slot);
unsafe { &*entry.ptr() }
}
}
impl Index<ScopeId> for ScopeSlab {
type Output = ScopeState;
fn index(&self, id: ScopeId) -> &Self::Output {
self.get(id).unwrap()
}
}
impl IndexMut<ScopeId> for ScopeSlab {
fn index_mut(&mut self, id: ScopeId) -> &mut Self::Output {
self.get_mut(id).unwrap()
}
}
pub struct ScopeState {
pub(crate) render_cnt: Cell<usize>,
pub(crate) name: &'static str,
pub(crate) node_arena_1: BumpFrame,
pub(crate) node_arena_2: BumpFrame,
pub(crate) parent: Option<*const ScopeState>,
pub(crate) id: ScopeId,
pub(crate) height: u32,
pub(crate) hook_arena: Bump,
pub(crate) hook_list: RefCell<Vec<*mut dyn Any>>,
pub(crate) hook_idx: Cell<usize>,
pub(crate) shared_contexts: RefCell<FxHashMap<TypeId, Box<dyn Any>>>,
pub(crate) tasks: Rc<Scheduler>,
pub(crate) spawned_tasks: RefCell<FxHashSet<TaskId>>,
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
pub(crate) placeholder: Cell<Option<ElementId>>,
}
impl<'src> ScopeState {
pub(crate) fn current_frame(&self) -> &BumpFrame {
match self.render_cnt.get() % 2 {
0 => &self.node_arena_1,
1 => &self.node_arena_2,
_ => unreachable!(),
}
}
pub(crate) fn previous_frame(&self) -> &BumpFrame {
match self.render_cnt.get() % 2 {
1 => &self.node_arena_1,
0 => &self.node_arena_2,
_ => unreachable!(),
}
}
pub fn name(&self) -> &str {
self.name
}
pub fn generation(&self) -> usize {
self.render_cnt.get()
}
pub fn bump(&self) -> &Bump {
self.previous_frame().bump()
}
pub fn root_node(&self) -> &RenderReturn {
self.try_root_node()
.expect("The tree has not been built yet. Make sure to call rebuild on the tree before accessing its nodes.")
}
pub fn try_root_node(&self) -> Option<&RenderReturn> {
let ptr = self.current_frame().node.get();
if ptr.is_null() {
return None;
}
let r: &RenderReturn = unsafe { &*ptr };
unsafe { std::mem::transmute(r) }
}
pub fn height(&self) -> u32 {
self.height
}
pub fn parent(&self) -> Option<ScopeId> {
self.parent.map(|p| unsafe { &*p }.id)
}
pub fn scope_id(&self) -> ScopeId {
self.id
}
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
Arc::new(move || drop(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| {
chan.unbounded_send(SchedulerMsg::Immediate(id)).unwrap();
})
}
pub fn needs_update(&self) {
self.needs_update_any(self.scope_id());
}
pub fn needs_update_any(&self, id: ScopeId) {
self.tasks
.sender
.unbounded_send(SchedulerMsg::Immediate(id))
.expect("Scheduler to exist if scope exists");
}
pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
self.shared_contexts
.borrow()
.get(&TypeId::of::<T>())?
.downcast_ref::<T>()
.cloned()
}
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
if let Some(this_ctx) = self.has_context() {
return Some(this_ctx);
}
let mut search_parent = self.parent;
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 shared.downcast_ref::<T>().cloned();
}
search_parent = parent.parent;
}
None
}
pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
let value2 = value.clone();
self.shared_contexts
.borrow_mut()
.insert(TypeId::of::<T>(), Box::new(value));
value2
}
pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
let id = self.tasks.spawn(self.id, fut);
self.spawned_tasks.borrow_mut().insert(id);
id
}
pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) {
self.push_future(fut);
}
pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
let id = self.tasks.spawn(ScopeId(0), fut);
self.tasks
.sender
.unbounded_send(SchedulerMsg::TaskNotified(id))
.expect("Scheduler should exist");
self.spawned_tasks.borrow_mut().insert(id);
id
}
pub fn remove_future(&self, id: TaskId) {
self.tasks.remove(id);
}
pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> {
let element = rsx.call(self);
let mut listeners = self.attributes_to_drop.borrow_mut();
for attr in element.dynamic_attrs {
match attr.value {
AttributeValue::Any(_) | AttributeValue::Listener(_) => {
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
listeners.push(unbounded);
}
_ => (),
}
}
let mut props = self.borrowed_props.borrow_mut();
for node in element.dynamic_nodes {
if let DynamicNode::Component(comp) = node {
if !comp.static_props {
let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
props.push(unbounded);
}
}
}
Some(element)
}
pub fn text_node(&'src self, args: Arguments) -> DynamicNode<'src> {
DynamicNode::Text(VText {
value: self.raw_text(args),
id: Default::default(),
})
}
pub fn raw_text(&'src self, args: Arguments) -> &'src str {
args.as_str().unwrap_or_else(|| {
use bumpalo::core_alloc::fmt::Write;
let mut str_buf = bumpalo::collections::String::new_in(self.bump());
str_buf.write_fmt(args).unwrap();
str_buf.into_bump_str()
})
}
pub fn make_node<'c, I>(&'src self, into: impl IntoDynNode<'src, I> + 'c) -> DynamicNode {
into.into_vnode(self)
}
pub fn attr(
&'src self,
name: &'static str,
value: impl IntoAttributeValue<'src>,
namespace: Option<&'static str>,
volatile: bool,
) -> Attribute<'src> {
Attribute {
name,
namespace,
volatile,
mounted_element: Default::default(),
value: value.into_value(self.bump()),
}
}
pub fn component<P, A, F: ComponentReturn<'src, A>>(
&'src self,
component: fn(Scope<'src, P>) -> F,
props: P,
fn_name: &'static str,
) -> DynamicNode<'src>
where
P: Properties + 'src,
{
let vcomp = VProps::new(component, P::memoize, props);
let as_dyn: Box<dyn AnyProps<'src> + '_> = Box::new(vcomp);
let extended: Box<dyn AnyProps<'src> + 'src> = unsafe { std::mem::transmute(as_dyn) };
DynamicNode::Component(VComponent {
name: fn_name,
render_fn: component as *const (),
static_props: P::IS_STATIC,
props: RefCell::new(Some(extended)),
scope: Cell::new(None),
})
}
pub fn event_handler<T>(&'src self, f: impl FnMut(T) + 'src) -> EventHandler<'src, T> {
let handler: &mut dyn FnMut(T) = self.bump().alloc(f);
let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) };
let callback = RefCell::new(Some(caller));
EventHandler { callback }
}
pub fn listener<T: 'static>(
&'src self,
mut callback: impl FnMut(Event<T>) + 'src,
) -> AttributeValue<'src> {
let boxed: BumpBox<'src, dyn FnMut(_) + 'src> = unsafe {
BumpBox::from_raw(self.bump().alloc(move |event: Event<dyn Any>| {
if let Ok(data) = event.data.downcast::<T>() {
callback(Event {
propagates: event.propagates,
data,
});
}
}))
};
AttributeValue::Listener(RefCell::new(Some(boxed)))
}
pub fn any_value<T: AnyValue>(&'src self, value: T) -> AttributeValue<'src> {
let boxed: BumpBox<'src, dyn AnyValue> =
unsafe { BumpBox::from_raw(self.bump().alloc(value)) };
AttributeValue::Any(RefCell::new(Some(boxed)))
}
pub fn throw(&self, error: impl Debug + 'static) -> Option<()> {
if let Some(cx) = self.consume_context::<Rc<ErrorBoundary>>() {
cx.insert_error(self.scope_id(), Box::new(error));
}
None
}
#[allow(clippy::mut_from_ref)]
pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce() -> State) -> &mut State {
let cur_hook = self.hook_idx.get();
let mut hook_list = self.hook_list.try_borrow_mut().expect("The hook list is already borrowed: This error is likely caused by trying to use a hook inside a hook which violates the rules of hooks.");
if cur_hook >= hook_list.len() {
hook_list.push(self.hook_arena.alloc(initializer()));
}
hook_list
.get(cur_hook)
.and_then(|inn| {
self.hook_idx.set(cur_hook + 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.
"###,
)
}
}