-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
61 additions
and
423 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,14 @@ | ||
use actuate::{use_state, virtual_dom, Scope, View, ViewBuilder}; | ||
|
||
struct A; | ||
|
||
impl View for A { | ||
fn body(&self, _cx: &Scope) -> impl ViewBuilder { | ||
dbg!("A!"); | ||
} | ||
} | ||
|
||
struct Counter { | ||
initial: i32, | ||
} | ||
|
||
impl View for Counter { | ||
fn body(&self, cx: &Scope) -> impl ViewBuilder { | ||
let (count, set_count) = use_state(cx, || self.initial); | ||
|
||
set_count.set(count + 1); | ||
|
||
dbg!(count); | ||
|
||
(*count == 2).then_some(A) | ||
} | ||
} | ||
use actuate::ViewBuilder; | ||
|
||
struct App; | ||
|
||
impl View for App { | ||
fn body(&self, _cx: &Scope) -> impl ViewBuilder { | ||
(Counter { initial: 1 }, Counter { initial: 2 }) | ||
impl ViewBuilder for App { | ||
fn body(&self) -> impl actuate::IntoView { | ||
dbg!("Hello World!"); | ||
} | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
let mut vdom = virtual_dom(App); | ||
|
||
vdom.run().await; | ||
vdom.run().await; | ||
|
||
dbg!(vdom); | ||
} | ||
actuate::run(App).await; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,148 +1,85 @@ | ||
use slotmap::{DefaultKey, SlotMap, SparseSecondaryMap}; | ||
use std::fmt; | ||
use std::{any::Any, cell::UnsafeCell}; | ||
use tokio::sync::mpsc; | ||
use std::{ | ||
future, | ||
marker::PhantomData, | ||
mem, | ||
task::{Context, Poll}, | ||
}; | ||
|
||
mod use_state; | ||
pub use self::use_state::{use_state, Setter}; | ||
pub trait View: 'static { | ||
type State; | ||
|
||
mod tree; | ||
pub use self::tree::{Tree, ViewTree}; | ||
fn build(&self) -> Self::State; | ||
|
||
mod view; | ||
pub use self::view::View; | ||
|
||
mod view_builder; | ||
pub use self::view_builder::ViewBuilder; | ||
|
||
struct Inner { | ||
hooks: Vec<Box<dyn Any>>, | ||
idx: usize, | ||
fn poll_view(&self, cx: &mut Context, state: &mut Self::State) -> Poll<()>; | ||
} | ||
|
||
pub struct Scope { | ||
key: DefaultKey, | ||
inner: UnsafeCell<Inner>, | ||
tx: mpsc::UnboundedSender<Update>, | ||
} | ||
impl View for () { | ||
type State = (); | ||
|
||
trait AnyView { | ||
fn name(&self) -> &'static str; | ||
} | ||
fn build(&self) -> Self::State {} | ||
|
||
impl<VB: ViewBuilder> AnyView for VB { | ||
fn name(&self) -> &'static str { | ||
std::any::type_name::<VB>() | ||
fn poll_view(&self, cx: &mut Context, state: &mut Self::State) -> Poll<()> { | ||
Poll::Ready(()) | ||
} | ||
} | ||
|
||
struct Node { | ||
view: *const dyn AnyView, | ||
children: Vec<DefaultKey>, | ||
} | ||
|
||
pub struct Context { | ||
nodes: SlotMap<DefaultKey, Node>, | ||
tx: mpsc::UnboundedSender<Update>, | ||
pending_updates: SparseSecondaryMap<DefaultKey, Vec<Update>>, | ||
} | ||
|
||
pub fn virtual_dom(view: impl ViewBuilder) -> VirtualDom<impl Tree> { | ||
let (tx, rx) = mpsc::unbounded_channel(); | ||
|
||
VirtualDom { | ||
tree: view.into_tree(), | ||
state: None, | ||
cx: Context { | ||
nodes: SlotMap::new(), | ||
tx, | ||
pending_updates: SparseSecondaryMap::new(), | ||
}, | ||
roots: Vec::new(), | ||
rx, | ||
} | ||
pub trait ViewBuilder: 'static { | ||
fn body(&self) -> impl IntoView; | ||
} | ||
|
||
struct Update { | ||
key: DefaultKey, | ||
idx: usize, | ||
value: Box<dyn Any>, | ||
pub trait IntoView { | ||
fn into_view(self) -> impl View; | ||
} | ||
|
||
pub struct VirtualDom<T> { | ||
tree: T, | ||
state: Option<Box<dyn Any>>, | ||
cx: Context, | ||
rx: mpsc::UnboundedReceiver<Update>, | ||
roots: Vec<DefaultKey>, | ||
impl IntoView for () { | ||
fn into_view(self) -> impl View {} | ||
} | ||
|
||
impl<T> VirtualDom<T> { | ||
pub async fn run(&mut self) | ||
where | ||
T: Tree, | ||
{ | ||
if let Some(ref mut state) = self.state { | ||
// Wait for at least one update. | ||
let update = self.rx.recv().await.unwrap(); | ||
|
||
if let Some(updates) = self.cx.pending_updates.get_mut(update.key) { | ||
updates.push(update); | ||
} else { | ||
self.cx.pending_updates.insert(update.key, vec![update]); | ||
} | ||
|
||
// Flush any pending updates. | ||
while let Ok(update) = self.rx.try_recv() { | ||
if let Some(updates) = self.cx.pending_updates.get_mut(update.key) { | ||
updates.push(update); | ||
} else { | ||
self.cx.pending_updates.insert(update.key, vec![update]); | ||
} | ||
} | ||
|
||
self.tree | ||
.rebuild(&mut self.cx, state.downcast_mut().unwrap(), &mut self.roots) | ||
} else { | ||
let state = self.tree.build(&mut self.cx, &mut self.roots); | ||
self.state = Some(Box::new(state)); | ||
impl<VB: ViewBuilder> IntoView for VB { | ||
fn into_view(self) -> impl View { | ||
ViewBuilderView { | ||
view_builder: self, | ||
view_fn: |me: &Self| { | ||
let me: &'static Self = unsafe { mem::transmute(me) }; | ||
me.body().into_view() | ||
}, | ||
_marker: PhantomData, | ||
} | ||
} | ||
} | ||
|
||
pub fn slice(&self, key: DefaultKey) -> Slice<T> { | ||
Slice { | ||
vdom: self, | ||
node: self.cx.nodes.get(key).unwrap(), | ||
} | ||
} | ||
pub struct ViewBuilderView<VB, B, F> { | ||
view_builder: VB, | ||
view_fn: F, | ||
_marker: PhantomData<B>, | ||
} | ||
|
||
impl<T> fmt::Debug for VirtualDom<T> { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
let mut t = f.debug_tuple("VirtualDom"); | ||
impl<VB, B, F> View for ViewBuilderView<VB, B, F> | ||
where | ||
VB: ViewBuilder, | ||
B: View, | ||
F: Fn(&VB) -> B + 'static, | ||
{ | ||
type State = B::State; | ||
|
||
for key in &self.roots { | ||
t.field(&self.slice(*key)); | ||
} | ||
fn build(&self) -> Self::State { | ||
let body = (self.view_fn)(&self.view_builder); | ||
let body_state = body.build(); | ||
|
||
t.finish() | ||
body_state | ||
} | ||
} | ||
|
||
pub struct Slice<'a, T> { | ||
vdom: &'a VirtualDom<T>, | ||
node: &'a Node, | ||
fn poll_view(&self, cx: &mut Context, state: &mut Self::State) -> Poll<()> { | ||
let body = (self.view_fn)(&self.view_builder); | ||
body.poll_view(cx, state) | ||
} | ||
} | ||
|
||
impl<T> fmt::Debug for Slice<'_, T> { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
let view = unsafe { &*self.node.view }; | ||
let mut t = f.debug_tuple(view.name()); | ||
|
||
for child_key in &self.node.children { | ||
t.field(&self.vdom.slice(*child_key)); | ||
} | ||
pub async fn run(view: impl IntoView) { | ||
let view = view.into_view(); | ||
let mut state = view.build(); | ||
|
||
t.finish() | ||
loop { | ||
future::poll_fn(|cx| view.poll_view(cx, &mut state)).await | ||
} | ||
} |
Oops, something went wrong.