-
-
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
110 additions
and
422 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,15 @@ | ||
use actuate::{use_state, virtual_dom, Scope, View, ViewBuilder}; | ||
use actuate::{memo, View}; | ||
|
||
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) | ||
} | ||
} | ||
|
||
struct App; | ||
|
||
impl View for App { | ||
fn body(&self, _cx: &Scope) -> impl ViewBuilder { | ||
(Counter { initial: 1 }, Counter { initial: 2 }) | ||
} | ||
fn app() -> impl View { | ||
memo( | ||
0, | ||
actuate::from_fn(0, |_| { | ||
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,134 @@ | ||
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 { | ||
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 fn from_fn<T, F, V>(state: T, view_fn: F) -> ViewBuilderView<T, F, V> | ||
where | ||
T: 'static, | ||
F: Fn(&T) -> V + 'static, | ||
V: View, | ||
{ | ||
ViewBuilderView { | ||
state, | ||
view_fn, | ||
_marker: PhantomData, | ||
} | ||
} | ||
|
||
pub struct Context { | ||
nodes: SlotMap<DefaultKey, Node>, | ||
tx: mpsc::UnboundedSender<Update>, | ||
pending_updates: SparseSecondaryMap<DefaultKey, Vec<Update>>, | ||
pub struct ViewBuilderView<T, F, V> { | ||
state: T, | ||
view_fn: F, | ||
_marker: PhantomData<V>, | ||
} | ||
|
||
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, | ||
impl<T, F, V> View for ViewBuilderView<T, F, V> | ||
where | ||
T: 'static, | ||
F: Fn(&T) -> V + 'static, | ||
V: View, | ||
{ | ||
type State = Option<V::State>; | ||
|
||
fn build(&self) -> Self::State { | ||
None | ||
} | ||
} | ||
|
||
struct Update { | ||
key: DefaultKey, | ||
idx: usize, | ||
value: Box<dyn Any>, | ||
fn poll_view(&self, cx: &mut Context, state: &mut Self::State) -> Poll<()> { | ||
if let Some(ref mut state) = state { | ||
let body = (self.view_fn)(&self.state); | ||
body.poll_view(cx, state) | ||
} else { | ||
let body = (self.view_fn)(&self.state); | ||
let mut body_state = body.build(); | ||
|
||
let ret = body.poll_view(cx, &mut body_state); | ||
*state = Some(body_state); | ||
ret | ||
} | ||
} | ||
} | ||
|
||
pub struct VirtualDom<T> { | ||
tree: T, | ||
state: Option<Box<dyn Any>>, | ||
cx: Context, | ||
rx: mpsc::UnboundedReceiver<Update>, | ||
roots: Vec<DefaultKey>, | ||
pub fn memo<I, V>(input: I, view: V) -> Memo<I, V> | ||
where | ||
I: PartialEq + Clone, | ||
V: View, | ||
{ | ||
Memo { input, 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]); | ||
} | ||
pub struct Memo<I, V> { | ||
input: I, | ||
view: V, | ||
} | ||
|
||
// 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]); | ||
} | ||
} | ||
impl<I, V> View for Memo<I, V> | ||
where | ||
I: PartialEq + Clone, | ||
V: View, | ||
{ | ||
type State = (I, V::State, bool, bool); | ||
|
||
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)); | ||
} | ||
fn build(&self) -> Self::State { | ||
(self.input.clone(), self.view.build(), false, false) | ||
} | ||
|
||
pub fn slice(&self, key: DefaultKey) -> Slice<T> { | ||
Slice { | ||
vdom: self, | ||
node: self.cx.nodes.get(key).unwrap(), | ||
fn poll_view(&self, cx: &mut Context, state: &mut Self::State) -> Poll<()> { | ||
// Init | ||
if !state.3 { | ||
if self.view.poll_view(cx, &mut state.1).is_ready() { | ||
state.3 = true; | ||
return Poll::Ready(()); | ||
} else { | ||
return Poll::Pending; | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl<T> fmt::Debug for VirtualDom<T> { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
let mut t = f.debug_tuple("VirtualDom"); | ||
|
||
for key in &self.roots { | ||
t.field(&self.slice(*key)); | ||
if state.2 { | ||
if self.view.poll_view(cx, &mut state.1).is_ready() { | ||
state.2 = false; | ||
Poll::Ready(()) | ||
} else { | ||
Poll::Pending | ||
} | ||
} else if self.input != state.0 { | ||
state.0 = self.input.clone(); | ||
if self.view.poll_view(cx, &mut state.1).is_ready() { | ||
Poll::Ready(()) | ||
} else { | ||
state.2 = true; | ||
Poll::Pending | ||
} | ||
} else { | ||
Poll::Ready(()) | ||
} | ||
|
||
t.finish() | ||
} | ||
} | ||
|
||
pub struct Slice<'a, T> { | ||
vdom: &'a VirtualDom<T>, | ||
node: &'a Node, | ||
} | ||
|
||
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 View) { | ||
let mut state = view.build(); | ||
|
||
t.finish() | ||
loop { | ||
future::poll_fn(|cx| view.poll_view(cx, &mut state)).await | ||
} | ||
} |
Oops, something went wrong.