wip: more raw ptrs

This commit is contained in:
Jonathan Kelley committed 2021-11-05 18:02:44 -04:00
1 parent b8d98698c6
commit 95bd17e38f
17 files changed
+3335 -3535

No files matched your search

+16
View File
@@ -40,3 +40,19 @@ We have big goals for Dioxus. The final implementation must:
- Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
- Be modular. Components and hooks should be work anywhere without worrying about target platform.
## Safety
Dioxus deals with arenas, lifetimes, asynchronous tasks, custom allocators, pinning, and a lot more foundational low-level work that is very difficult to implement with 0 unsafe.
If you don't want to use a crate that uses unsafe, then this crate is not for you.
however, we are always interested in decreasing the scope of the core VirtualDom to make it easier to review.
We'd also be happy to welcome PRs that can eliminate unsafe code while still upholding the numerous variants required to execute certain features.
There's a few invariants that are very important:
- References to `ScopeInner` and `Props` passed into components are *always* valid for as long as the component exists. Even if the scope backing is resized to fit more scopes, the scope has to stay the same place in memory.
+2 -5
View File
@@ -105,7 +105,6 @@ use DomEdit::*;
/// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's
/// stack machines all the way down!
pub(crate) struct DiffMachine<'bump> {
pub vdom: &'bump ResourcePool,
pub mutations: Mutations<'bump>,
pub stack: DiffStack<'bump>,
pub seen_scopes: FxHashSet<ScopeId>,
@@ -137,10 +136,9 @@ impl<'a> SavedDiffWork<'a> {
std::mem::transmute(self)
}
pub unsafe fn promote<'b>(self, vdom: &'b ResourcePool) -> DiffMachine<'b> {
pub unsafe fn promote<'b>(self) -> DiffMachine<'b> {
let extended: SavedDiffWork<'b> = std::mem::transmute(self);
DiffMachine {
vdom,
cfg: DiffCfg::default(),
mutations: extended.mutations,
stack: extended.stack,
@@ -150,12 +148,11 @@ impl<'a> SavedDiffWork<'a> {
}
impl<'bump> DiffMachine<'bump> {
pub(crate) fn new(mutations: Mutations<'bump>, shared: &'bump ResourcePool) -> Self {
pub(crate) fn new(mutations: Mutations<'bump>) -> Self {
Self {
mutations,
cfg: DiffCfg::default(),
stack: DiffStack::new(),
vdom: shared,
seen_scopes: FxHashSet::default(),
}
}
File diff suppressed because it is too large. Load diff
+2 -4
View File
@@ -25,7 +25,6 @@ pub(crate) mod nodes;
pub(crate) mod scope;
pub(crate) mod scopearena;
pub(crate) mod test_dom;
pub(crate) mod threadsafe;
pub(crate) mod util;
pub(crate) mod virtual_dom;
@@ -46,7 +45,6 @@ pub(crate) mod innerlude {
pub use crate::scope::*;
pub use crate::scopearena::*;
pub use crate::test_dom::*;
pub use crate::threadsafe::*;
pub use crate::util::*;
pub use crate::virtual_dom::*;
@@ -56,8 +54,8 @@ pub(crate) mod innerlude {
pub use crate::innerlude::{
Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, LazyNodes, MountType,
Mutations, NodeFactory, Properties, ScopeChildren, ScopeId, TestDom, ThreadsafeVirtualDom,
UserEvent, VNode, VirtualDom, FC,
Mutations, NodeFactory, Properties, ScopeChildren, ScopeId, TestDom, UserEvent, VNode,
VirtualDom, FC,
};
pub mod prelude {
File renamed without changes.
File renamed without changes.
File renamed without changes.
-52
View File
@@ -180,58 +180,6 @@ impl ScopeInner {
}
}
/// Render this component.
///
/// Returns true if the scope completed successfully and false if running failed (IE a None error was propagated).
pub(crate) fn run_scope<'sel>(&'sel mut self) -> bool {
// Cycle to the next frame and then reset it
// This breaks any latent references, invalidating every pointer referencing into it.
// Remove all the outdated listeners
self.ensure_drop_safety();
// Safety:
// - We dropped the listeners, so no more &mut T can be used while these are held
// - All children nodes that rely on &mut T are replaced with a new reference
unsafe { self.hooks.reset() };
// Safety:
// - We've dropped all references to the wip bump frame
unsafe { self.frames.reset_wip_frame() };
let items = self.items.get_mut();
// just forget about our suspended nodes while we're at it
items.suspended_nodes.clear();
// guarantee that we haven't screwed up - there should be no latent references anywhere
debug_assert!(items.listeners.is_empty());
debug_assert!(items.suspended_nodes.is_empty());
debug_assert!(items.borrowed_props.is_empty());
log::debug!("Borrowed stuff is successfully cleared");
// temporarily cast the vcomponent to the right lifetime
let vcomp = self.load_vcomp();
let render: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> = todo!();
// Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
if let Some(builder) = render(self) {
let new_head = builder.into_vnode(NodeFactory {
bump: &self.frames.wip_frame().bump,
});
log::debug!("Render is successful");
// the user's component succeeded. We can safely cycle to the next frame
self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
self.frames.cycle_frame();
true
} else {
false
}
}
pub(crate) fn new_subtree(&self) -> Option<u32> {
todo!()
// if self.is_subtree_root.get() {
+12
View File
@@ -5,6 +5,14 @@ use futures_channel::mpsc::UnboundedSender;
use crate::innerlude::*;
pub type FcSlot = *const ();
// pub heuristics: FxHashMap<FcSlot, Heuristic>,
pub struct Heuristic {
hook_arena_size: usize,
node_arena_size: usize,
}
// a slab-like arena with stable references even when new scopes are allocated
// uses a bump arena as a backing
//
@@ -24,6 +32,10 @@ impl ScopeArena {
}
}
pub fn get_mut(&mut self, id: &ScopeId) -> Option<&mut ScopeInner> {
unsafe { Some(&mut *self.scopes[id.0]) }
}
pub fn new_with_key(
&mut self,
fc_ptr: *const (),
+4 -4
View File
@@ -33,7 +33,7 @@ impl TestDom {
pub fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
let mutations = Mutations::new();
let mut machine = DiffMachine::new(mutations, todo!());
let mut machine = DiffMachine::new(mutations);
machine.stack.push(DiffInstruction::Diff { new, old });
machine.mutations
}
@@ -41,7 +41,7 @@ impl TestDom {
pub fn create<'a>(&'a self, left: Option<LazyNodes<'a, '_>>) -> Mutations<'a> {
let old = self.bump.alloc(self.render_direct(left));
let mut machine = DiffMachine::new(Mutations::new(), todo!());
let mut machine = DiffMachine::new(Mutations::new());
machine.stack.create_node(old, MountType::Append);
@@ -57,14 +57,14 @@ impl TestDom {
) -> (Mutations<'a>, Mutations<'a>) {
let (old, new) = (self.render(left), self.render(right));
let mut machine = DiffMachine::new(Mutations::new(), todo!());
let mut machine = DiffMachine::new(Mutations::new());
machine.stack.create_node(old, MountType::Append);
machine.work(|| false);
let create_edits = machine.mutations;
let mut machine = DiffMachine::new(Mutations::new(), todo!());
let mut machine = DiffMachine::new(Mutations::new());
machine.stack.push(DiffInstruction::Diff { old, new });
+124 -125
View File
@@ -80,12 +80,8 @@ pub struct VirtualDom {
pub scopes: ScopeArena,
pub heuristics: FxHashMap<FcSlot, Heuristic>,
pub receiver: UnboundedReceiver<SchedulerMsg>,
// Garbage stored
pub pending_garbage: FxHashSet<ScopeId>,
pub sender: UnboundedSender<SchedulerMsg>,
// Every component that has futures that need to be polled
pub pending_futures: FxHashSet<ScopeId>,
@@ -94,10 +90,6 @@ pub struct VirtualDom {
pub pending_immediates: VecDeque<ScopeId>,
pub batched_events: VecDeque<UserEvent>,
pub garbage_scopes: HashSet<ScopeId>,
pub dirty_scopes: IndexSet<ScopeId>,
pub saved_state: Option<SavedDiffWork<'static>>,
@@ -173,7 +165,7 @@ impl VirtualDom {
Self::new_with_props_and_scheduler(root, root_props, sender, receiver)
}
/// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the
/// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler
///
/// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the
/// VirtualDom to be created just to retrieve its channel receiver.
@@ -188,74 +180,36 @@ impl VirtualDom {
let base_scope = scopes.new_with_key(
//
root as _,
boxed_comp.as_ref(),
todo!(),
// boxed_comp.as_ref(),
None,
0,
0,
sender.clone(),
);
// let root_fc = Box::new(root);
// let root_props: Rc<dyn Any> = Rc::new(root_props);
// let props = root_props.clone();
// let mut root_caller: Box<dyn Fn(&ScopeInner) -> Element> =
// Box::new(move |scope: &ScopeInner| {
// let props = props.downcast_ref::<P>().unwrap();
// let node = root((scope, props));
// // cast into the right lifetime
// unsafe { std::mem::transmute(node) }
// });
// let caller = unsafe { bumpalo::boxed::Box::from_raw(root_caller.as_mut() as *mut _) };
// // todo make the memory footprint congifurable
// let scheduler = Scheduler::new(sender, receiver, 100, 2000);
// let vcomp = VComponent {
// key: todo!(),
// associated_scope: todo!(),
// user_fc: root as *const _,
// can_memoize: todo!(),
// raw_props: todo!(),
// // drop_props: todo!(),
// // caller,
// comparator: todo!(),
// caller: todo!(),
// };
// let boxed_comp = Box::new(vcomp);
// let base_scope = pool.insert_scope_with_key(|myidx| {
// ScopeInner::new(
// boxed_comp.as_ref(),
// myidx,
// None,
// 0,
// 0,
// pool.channel.clone(),
// )
// });
Self {
scopes,
base_scope: todo!(),
base_scope,
receiver,
sender,
root_fc: todo!(),
root_props: todo!(),
_root_caller: todo!(),
heuristics: todo!(),
receiver,
pending_garbage: todo!(),
pending_futures: todo!(),
ui_events: todo!(),
pending_immediates: todo!(),
batched_events: todo!(),
garbage_scopes: todo!(),
dirty_scopes: todo!(),
saved_state: todo!(),
in_progress: todo!(),
pending_futures: Default::default(),
dirty_scopes: Default::default(),
saved_state: Some(SavedDiffWork {
mutations: Mutations::new(),
stack: DiffStack::new(),
seen_scopes: Default::default(),
}),
in_progress: false,
}
}
@@ -264,12 +218,14 @@ impl VirtualDom {
/// This is useful for traversing the tree from the root for heuristics or alternsative renderers that use Dioxus
/// directly.
pub fn base_scope(&self) -> &ScopeInner {
self.pool.get_scope(&self.base_scope).unwrap()
todo!()
// self.get_scope(&self.base_scope).unwrap()
}
/// Get the [`Scope`] for a component given its [`ScopeId`]
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeInner> {
self.pool.get_scope(&id)
pub fn get_scope(&self, id: &ScopeId) -> Option<&ScopeInner> {
todo!()
// self.get_scope(&id)
}
/// Update the root props of this VirtualDOM.
@@ -297,7 +253,7 @@ impl VirtualDom {
let root_scope = self.pool.get_scope_mut(&self.base_scope).unwrap();
// Pre-emptively drop any downstream references of the old props
root_scope.ensure_drop_safety(&self.pool);
root_scope.ensure_drop_safety();
let mut root_props: Rc<dyn Any> = Rc::new(root_props);
@@ -339,6 +295,7 @@ impl VirtualDom {
/// apply_edits(edits);
/// ```
pub fn rebuild(&mut self) -> Mutations {
todo!()
// self.rebuild(self.base_scope)
}
@@ -440,7 +397,7 @@ impl VirtualDom {
}
pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
self.pool.channel.sender.clone()
self.sender.clone()
}
/// Waits for the scheduler to have work
@@ -481,13 +438,6 @@ impl VirtualDom {
}
}
pub type FcSlot = *const ();
pub struct Heuristic {
hook_arena_size: usize,
node_arena_size: usize,
}
/*
Welcome to Dioxus's cooperative, priority-based scheduler.
@@ -670,19 +620,15 @@ impl VirtualDom {
let saved_state = unsafe { self.load_work() };
// We have to split away some parts of ourself - current lane is borrowed mutably
let shared = self.clone();
let mut machine = unsafe { saved_state.promote(&shared) };
let mut machine = unsafe { saved_state.promote() };
let mut ran_scopes = FxHashSet::default();
if machine.stack.is_empty() {
let shared = self.clone();
self.dirty_scopes
.retain(|id| shared.get_scope(id).is_some());
self.dirty_scopes.retain(|id| self.get_scope(id).is_some());
self.dirty_scopes.sort_by(|a, b| {
let h1 = shared.get_scope(a).unwrap().height;
let h2 = shared.get_scope(b).unwrap().height;
let h1 = self.get_scope(a).unwrap().height;
let h2 = self.get_scope(b).unwrap().height;
h1.cmp(&h2).reverse()
});
@@ -692,15 +638,14 @@ impl VirtualDom {
ran_scopes.insert(scopeid);
log::debug!("about to run scope {:?}", scopeid);
if let Some(component) = self.get_scope_mut(&scopeid) {
if component.run_scope(&self) {
let (old, new) =
(component.frames.wip_head(), component.frames.fin_head());
// let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
machine.stack.scope_stack.push(scopeid);
machine.stack.push(DiffInstruction::Diff { new, old });
}
// if let Some(component) = self.get_scope_mut(&scopeid) {
if self.run_scope(&scopeid) {
let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
// let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
machine.stack.scope_stack.push(scopeid);
machine.stack.push(DiffInstruction::Diff { new, old });
}
// }
}
}
}
@@ -856,19 +801,17 @@ impl VirtualDom {
///
/// Typically used to kickstart the VirtualDOM after initialization.
pub fn rebuild_inner(&mut self, base_scope: ScopeId) -> Mutations {
let mut shared = self.clone();
let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared);
let mut diff_machine = DiffMachine::new(Mutations::new());
// TODO: drain any in-flight work
let cur_component = self
.pool
.get_scope_mut(&base_scope)
.expect("The base scope should never be moved");
log::debug!("rebuild {:?}", base_scope);
// We run the component. If it succeeds, then we can diff it and add the changes to the dom.
if cur_component.run_scope(&self) {
if self.run_scope(&base_scope) {
diff_machine
.stack
.create_node(cur_component.frames.fin_head(), MountType::Append);
@@ -889,14 +832,13 @@ impl VirtualDom {
pub fn hard_diff(&mut self, base_scope: ScopeId) -> Mutations {
let cur_component = self
.pool
.get_scope_mut(&base_scope)
.expect("The base scope should never be moved");
log::debug!("hard diff {:?}", base_scope);
if cur_component.run_scope(&self) {
let mut diff_machine = DiffMachine::new(Mutations::new(), &mut self);
if self.run_scope(&base_scope) {
let mut diff_machine = DiffMachine::new(Mutations::new());
diff_machine.cfg.force_diff = true;
diff_machine.diff_scope(base_scope);
diff_machine.mutations
@@ -904,36 +846,93 @@ impl VirtualDom {
Mutations::new()
}
}
}
impl Future for VirtualDom {
type Output = ();
pub fn get_scope_mut(&mut self, id: &ScopeId) -> Option<&mut ScopeInner> {
self.scopes.get_mut(id)
}
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
let mut all_pending = true;
pub fn run_scope(&mut self, id: &ScopeId) -> bool {
let scope = self
.get_scope_mut(id)
.expect("The base scope should never be moved");
for fut in self.pending_futures.iter() {
let scope = self
.pool
.get_scope_mut(&fut)
.expect("Scope should never be moved");
// Cycle to the next frame and then reset it
// This breaks any latent references, invalidating every pointer referencing into it.
// Remove all the outdated listeners
scope.ensure_drop_safety();
let items = scope.items.get_mut();
for task in items.tasks.iter_mut() {
let t = task.as_mut();
let g = unsafe { Pin::new_unchecked(t) };
match g.poll(cx) {
Poll::Ready(r) => {
all_pending = false;
}
Poll::Pending => {}
}
}
}
// Safety:
// - We dropped the listeners, so no more &mut T can be used while these are held
// - All children nodes that rely on &mut T are replaced with a new reference
unsafe { scope.hooks.reset() };
match all_pending {
true => Poll::Pending,
false => Poll::Ready(()),
// Safety:
// - We've dropped all references to the wip bump frame
unsafe { scope.frames.reset_wip_frame() };
let items = scope.items.get_mut();
// just forget about our suspended nodes while we're at it
items.suspended_nodes.clear();
// guarantee that we haven't screwed up - there should be no latent references anywhere
debug_assert!(items.listeners.is_empty());
debug_assert!(items.suspended_nodes.is_empty());
debug_assert!(items.borrowed_props.is_empty());
log::debug!("Borrowed stuff is successfully cleared");
// temporarily cast the vcomponent to the right lifetime
let vcomp = scope.load_vcomp();
let render: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> = todo!();
// Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
if let Some(builder) = render(scope) {
let new_head = builder.into_vnode(NodeFactory {
bump: &scope.frames.wip_frame().bump,
});
log::debug!("Render is successful");
// the user's component succeeded. We can safely cycle to the next frame
scope.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
scope.frames.cycle_frame();
true
} else {
false
}
}
}
// impl<'a> Future for PollAllTasks<'a> {
// type Output = ();
// fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
// let mut all_pending = true;
// for fut in self.pending_futures.iter() {
// let scope = self
// .pool
// .get_scope_mut(&fut)
// .expect("Scope should never be moved");
// let items = scope.items.get_mut();
// for task in items.tasks.iter_mut() {
// let t = task.as_mut();
// let g = unsafe { Pin::new_unchecked(t) };
// match g.poll(cx) {
// Poll::Ready(r) => {
// all_pending = false;
// }
// Poll::Pending => {}
// }
// }
// }
// match all_pending {
// true => Poll::Pending,
// false => Poll::Ready(()),
// }
// }
// }