Skip to content

Commit

Permalink
Create VirtualDom struct and reorganize
Browse files Browse the repository at this point in the history
  • Loading branch information
matthunz committed Jan 20, 2024
1 parent 1173334 commit ea35f56
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 159 deletions.
2 changes: 1 addition & 1 deletion crates/concoct-web/src/html.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::WebContext;
use concoct::{
view::Child,
hook::{use_context, use_on_drop, use_provider, use_ref},
view::Child,
View, ViewBuilder,
};
use std::{borrow::Cow, cell::RefCell, rc::Rc};
Expand Down
2 changes: 1 addition & 1 deletion crates/concoct-web/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use concoct::{
hook::{use_context, use_on_drop, use_provider, use_ref},
View, TextViewContext, ViewBuilder,
TextViewContext, View, ViewBuilder,
};
use std::{cell::RefCell, rc::Rc};
use web_sys::{Document, Node, Text, Window};
Expand Down
6 changes: 3 additions & 3 deletions crates/concoct/src/hook/use_context.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::Context;
use crate::Runtime;
use std::{any::TypeId, rc::Rc};

/// Hook to provide a context.
pub fn use_provider<T: 'static>(value: T) {
let cx = Context::current();
let cx = Runtime::current();
let cx_ref = cx.inner.borrow();
let mut scope = cx_ref.scope.as_ref().unwrap().inner.borrow_mut();

Expand All @@ -12,7 +12,7 @@ pub fn use_provider<T: 'static>(value: T) {

/// Hook to get a context from its type.
pub fn use_context<T: 'static>() -> Option<Rc<T>> {
Context::current()
Runtime::current()
.inner
.borrow()
.scope
Expand Down
4 changes: 2 additions & 2 deletions crates/concoct/src/hook/use_on_drop.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use super::use_ref;
use crate::Context;
use crate::Runtime;

/// Hook to store a function that's triggered on removal of the current `View`.
pub fn use_on_drop(on_drop: impl FnMut() + 'static) {
use_ref(|| {
Context::current()
Runtime::current()
.inner
.borrow()
.scope
Expand Down
6 changes: 3 additions & 3 deletions crates/concoct/src/hook/use_ref.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::Context;
use crate::Runtime;
use std::rc::Rc;

/// Hook to store a stateless value.
///
/// This function will only call `make_value` once, on the first render,
/// to create the initial value.
pub fn use_ref<T: 'static>(make_value: impl FnOnce() -> T) -> Rc<T> {
let cx = Context::current();
let cx = Runtime::current();
let cx_ref = cx.inner.borrow();
let mut scope = cx_ref.scope.as_ref().unwrap().inner.borrow_mut();

Expand All @@ -20,7 +20,7 @@ pub fn use_ref<T: 'static>(make_value: impl FnOnce() -> T) -> Rc<T> {
drop(cx_ref);
let value = Rc::new(make_value());

let cx = Context::current();
let cx = Runtime::current();
let cx_ref = cx.inner.borrow();
let scope = &mut *cx_ref.scope.as_ref().unwrap().inner.borrow_mut();
scope.hooks.push(value.clone());
Expand Down
4 changes: 2 additions & 2 deletions crates/concoct/src/hook/use_state.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::use_ref;
use crate::Context;
use crate::Runtime;
use std::cell::RefCell;

/// Hook to create render state.
Expand All @@ -20,7 +20,7 @@ pub fn use_state<T: Clone + 'static>(
let cell = use_ref(|| RefCell::new(make_value()));
let getter = cell.borrow().clone();

let cx = Context::current();
let cx = Runtime::current();
let key = cx.inner.borrow().node.unwrap();
let setter = move |value| {
*cell.borrow_mut() = value;
Expand Down
136 changes: 11 additions & 125 deletions crates/concoct/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,106 +1,31 @@
use rustc_hash::FxHasher;
use slotmap::{DefaultKey, SlotMap};
use std::any::{Any, TypeId};
use std::borrow::Cow;
use std::collections::{HashMap, VecDeque};
use std::hash::{Hash, Hasher};
use std::task::{Poll, Waker};
use std::{cell::RefCell, rc::Rc};

use std::cell::RefCell;

pub mod hook;

mod rt;
pub(crate) use self::rt::{Runtime, Scope};

mod tree;
pub(crate) use tree::Node;
pub use tree::Tree;

mod vdom;
pub use self::vdom::{virtual_dom, VirtualDom};

mod view_builder;
pub use self::view_builder::ViewBuilder;

pub mod view;
pub use self::view::View;
use view::Empty;

#[derive(Default)]
struct ScopeInner {
contexts: HashMap<TypeId, Rc<dyn Any>>,
hooks: Vec<Rc<dyn Any>>,
hook_idx: usize,
droppers: Vec<Box<dyn FnMut()>>,
}

#[derive(Clone, Default)]
pub struct Scope {
inner: Rc<RefCell<ScopeInner>>,
}

#[derive(Default)]
struct ContextInner {
node: Option<DefaultKey>,
pending: VecDeque<DefaultKey>,
scope: Option<Scope>,
nodes: SlotMap<DefaultKey, *mut dyn Tree>,
waker: Option<Waker>,
contexts: HashMap<TypeId, Rc<dyn Any>>,
}

#[derive(Clone, Default)]
pub struct Context {
inner: Rc<RefCell<ContextInner>>,
}

impl Context {
pub fn enter(&self) {
CONTEXT
.try_with(|cell| *cell.borrow_mut() = Some(self.clone()))
.unwrap();
}

pub fn current() -> Self {
CONTEXT
.try_with(|cell| cell.borrow().as_ref().unwrap().clone())
.unwrap()
}

pub async fn rebuild(&self) {
futures::future::poll_fn(|cx| {
let mut inner = self.inner.borrow_mut();
inner.waker = Some(cx.waker().clone());

if let Some(key) = inner.pending.pop_front() {
let raw = inner.nodes[key];
drop(inner);

let pending = unsafe { &mut *raw };
pending.build();
}

Poll::Pending
})
.await
}
}

thread_local! {
static CONTEXT: RefCell<Option<Context>> = RefCell::new(None);
}

impl Tree for Empty {
fn build(&mut self) {}

fn rebuild(&mut self, _last: &mut dyn Any) {}

fn remove(&mut self) {}
}

pub async fn run(view: impl ViewBuilder) {
let cx = Context::default();
cx.enter();

let mut tree = view.into_tree();
tree.build();
let mut vdom = virtual_dom(view);
vdom.build();

loop {
cx.rebuild().await
vdom.rebuild().await
}
}

Expand All @@ -115,42 +40,3 @@ impl TextViewContext {
}
}
}

pub fn memo<B>(input: impl Hash, body: B) -> Memo<B> {
let mut hasher = FxHasher::default();
input.hash(&mut hasher);
let hash = hasher.finish();

Memo { hash, body }
}

pub struct Memo<B> {
hash: u64,
body: B,
}

impl<B: View> View for Memo<B> {
fn into_tree(self) -> impl Tree {
Memo {
hash: self.hash,
body: self.body.into_tree(),
}
}
}

impl<T: Tree> Tree for Memo<T> {
fn build(&mut self) {
self.body.build()
}

fn rebuild(&mut self, last: &mut dyn Any) {
let last = last.downcast_mut::<Self>().unwrap();
if self.hash != last.hash {
self.body.rebuild(&mut last.body)
}
}

fn remove(&mut self) {
self.body.remove()
}
}
57 changes: 57 additions & 0 deletions crates/concoct/src/rt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use crate::Tree;
use slotmap::{DefaultKey, SlotMap};
use std::{
any::{Any, TypeId},
cell::RefCell,
collections::{HashMap, VecDeque},
rc::Rc,
task::Waker,
time::Instant,
};

#[derive(Default)]
pub(crate) struct ScopeInner {
pub(crate) contexts: HashMap<TypeId, Rc<dyn Any>>,
pub(crate) hooks: Vec<Rc<dyn Any>>,
pub(crate) hook_idx: usize,
pub(crate) droppers: Vec<Box<dyn FnMut()>>,
}

#[derive(Clone, Default)]
pub(crate) struct Scope {
pub(crate) inner: Rc<RefCell<ScopeInner>>,
}

#[derive(Default)]
pub(crate) struct RuntimeInner {
pub(crate) node: Option<DefaultKey>,
pub(crate) pending: VecDeque<DefaultKey>,
pub(crate) scope: Option<Scope>,
pub(crate) nodes: SlotMap<DefaultKey, *mut dyn Tree>,
pub(crate) waker: Option<Waker>,
pub(crate) contexts: HashMap<TypeId, Rc<dyn Any>>,
pub(crate) limit: Option<Instant>,
}

#[derive(Clone, Default)]
pub struct Runtime {
pub(crate) inner: Rc<RefCell<RuntimeInner>>,
}

impl Runtime {
pub fn enter(&self) {
CONTEXT
.try_with(|cell| *cell.borrow_mut() = Some(self.clone()))
.unwrap();
}

pub fn current() -> Self {
CONTEXT
.try_with(|cell| cell.borrow().as_ref().unwrap().clone())
.unwrap()
}
}

thread_local! {
static CONTEXT: RefCell<Option<Runtime>> = RefCell::new(None);
}
24 changes: 12 additions & 12 deletions crates/concoct/src/tree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ mod node;
pub use node::Node;

pub trait Tree: 'static {
fn build(&mut self);
unsafe fn build(&mut self);

fn rebuild(&mut self, last: &mut dyn Any);
unsafe fn rebuild(&mut self, last: &mut dyn Any);

fn remove(&mut self);
unsafe fn remove(&mut self);
}

impl<T: Tree> Tree for Option<T> {
fn build(&mut self) {
unsafe fn build(&mut self) {
if let Some(tree) = self {
tree.build()
}
}

fn rebuild(&mut self, last: &mut dyn Any) {
unsafe fn rebuild(&mut self, last: &mut dyn Any) {
if let Some(tree) = self {
if let Some(last_tree) = last.downcast_mut::<Self>().unwrap() {
tree.rebuild(last_tree)
Expand All @@ -30,7 +30,7 @@ impl<T: Tree> Tree for Option<T> {
}
}

fn remove(&mut self) {
unsafe fn remove(&mut self) {
if let Some(tree) = self {
tree.remove()
}
Expand All @@ -42,13 +42,13 @@ where
K: Hash + Eq + 'static,
T: Tree,
{
fn build(&mut self) {
unsafe fn build(&mut self) {
for (_, body) in self.iter_mut() {
body.build()
}
}

fn rebuild(&mut self, last: &mut dyn Any) {
unsafe fn rebuild(&mut self, last: &mut dyn Any) {
let mut visited = HashSet::new();
let last = last.downcast_mut::<Self>().unwrap();

Expand All @@ -68,7 +68,7 @@ where
}
}

fn remove(&mut self) {
unsafe fn remove(&mut self) {
for (_, body) in self.iter_mut() {
body.remove()
}
Expand All @@ -78,21 +78,21 @@ where
macro_rules! impl_tree_for_tuple {
($($t:tt : $idx:tt),*) => {
impl<$($t: Tree),*> Tree for ($($t),*) {
fn build(&mut self) {
unsafe fn build(&mut self) {
$(
self.$idx.build();
)*
}

fn rebuild(&mut self, last: &mut dyn Any) {
unsafe fn rebuild(&mut self, last: &mut dyn Any) {
if let Some(last) = last.downcast_mut::<Self>() {
$(
self.$idx.rebuild(&mut last.$idx);
)*
}
}

fn remove(&mut self) {
unsafe fn remove(&mut self) {
$(
self.$idx.remove();
)*
Expand Down
Loading

0 comments on commit ea35f56

Please sign in to comment.