Skip to content

Commit

Permalink
Add initial web support
Browse files Browse the repository at this point in the history
  • Loading branch information
matthunz committed Nov 25, 2023
1 parent 66fabba commit 6464d28
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 63 deletions.
31 changes: 29 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,37 @@ license = "MIT OR Apache-2.0"
description = "Cross-platform UI framework"
repository = "https://github.com/concoct-rs/concoct"

[workspace]
members = [
".",
"web_examples"
]

[features]
full = []
html = ["dep:js-sys", "dep:wasm-bindgen", "dep:web-sys"]
full = ["html"]

[dependencies]
futures = "0.3.29"
slotmap = "1.0.6"
tokio = { version = "1.34.0", features = ["full"] }

js-sys = { version = "0.3.65", optional = true }
wasm-bindgen = { version = "0.2.88", optional = true }

[dependencies.web-sys]
version = "0.3.65"
optional = true
features = [
"Event",
"EventTarget",
"KeyboardEvent",
"InputEvent",
"MouseEvent",
"Node",
"Document",
"HtmlCollection",
"HtmlElement",
"HtmlInputElement",
"Window",
"Text",
]
17 changes: 9 additions & 8 deletions examples/counter.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use concoct::{use_future, use_state, Composable, Composition, IntoComposable};
use std::time::Duration;
use tokio::time;
use futures::executor::block_on;

#[derive(PartialEq)]
struct Counter {
Expand All @@ -13,7 +12,7 @@ impl Composable for Counter {

use_future(|| async move {
loop {
time::sleep(Duration::from_millis(500)).await;
//time::sleep(Duration::from_millis(500)).await;
count += 1;
}
});
Expand All @@ -26,11 +25,13 @@ fn app() -> impl IntoComposable {
(Counter { initial_value: 0 }, Counter { initial_value: 100 })
}

#[tokio::main]
async fn main() {
fn main() {
let mut composition = Composition::new(app);
composition.build();
loop {
composition.rebuild().await;
}

block_on(async move {
loop {
composition.rebuild().await;
}
});
}
35 changes: 13 additions & 22 deletions src/composition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ use crate::{
composable::IntoComposable, AnyComposable, BuildContext, LocalContext, Node, Scope,
TaskContext, BUILD_CONTEXT, GLOBAL_CONTEXT, TASK_CONTEXT,
};
use futures::pending;
use futures::channel::mpsc;
use futures::executor::LocalPool;
use futures::StreamExt;
use slotmap::DefaultKey;
use std::{any::Any, cell::RefCell, rc::Rc};
use tokio::{sync::mpsc, task::LocalSet};

/// A composition of composables.
pub struct Composition {
build_cx: Rc<RefCell<BuildContext>>,
root: DefaultKey,
local_set: LocalSet,

task_cx: TaskContext,
rx: mpsc::UnboundedReceiver<Box<dyn Any>>,
}
Expand All @@ -22,8 +23,12 @@ impl Composition {
where
C: IntoComposable + 'static,
{
let local_set = LocalSet::new();
local_set.enter();
let local_set = LocalPool::new();
let (tx, rx) = mpsc::unbounded();
let task_cx = TaskContext {
tx,
local_pool: Rc::new(RefCell::new(local_set)),
};

{
let build_cx = Rc::new(RefCell::new(BuildContext::default()));
Expand All @@ -47,13 +52,11 @@ impl Composition {
.borrow_mut()
.nodes
.insert(Rc::new(RefCell::new(node)));
let (tx, rx) = mpsc::unbounded_channel();

Self {
build_cx,
root,
local_set: LocalSet::new(),
task_cx: TaskContext { tx },
task_cx,
rx,
}
}
Expand All @@ -74,8 +77,6 @@ impl Composition {
.try_with(|cx| *cx.borrow_mut() = Some(self.build_cx.clone()))
.unwrap();

let g = self.local_set.enter();

{
let cx = self.build_cx.borrow_mut();
let node = cx.nodes[key].borrow_mut();
Expand Down Expand Up @@ -127,8 +128,6 @@ impl Composition {
};
composable.borrow_mut().any_build();

drop(g);

self.build_cx.borrow().children.get(key).cloned()
};

Expand Down Expand Up @@ -157,22 +156,14 @@ impl Composition {

loop {
let fut = async {
self.rx.recv().await;
self.rx.next().await;
};

if futures::poll!(Box::pin(fut)).is_ready() {
break;
}

let fut = async {
let fut = &mut self.local_set;
fut.await;
};
if futures::poll!(Box::pin(fut)).is_pending() {
pending!()
} else {
break;
}
self.task_cx.local_pool.borrow_mut().run_until_stalled();
}

self.compose(self.root);
Expand Down
64 changes: 64 additions & 0 deletions src/html/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use std::{cell::RefCell, rc::Rc};
use web_sys::{Document, HtmlElement};

use crate::{Composable, Composition, IntoComposable};

thread_local! {
static HTML_CONTEXT: RefCell<Option<HtmlContext>> = RefCell::default();
}

struct Inner {
document: Document,
body: HtmlElement,
}

#[derive(Clone)]
pub struct HtmlContext {
inner: Rc<RefCell<Inner>>,
}

impl HtmlContext {
pub fn new() -> Self {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
Self {
inner: Rc::new(RefCell::new(Inner { document, body })),
}
}

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

pub fn enter(self) {
HTML_CONTEXT
.try_with(|cx| *cx.borrow_mut() = Some(self))
.unwrap()
}
}

#[derive(PartialEq, Eq)]
pub struct Html {}

impl Composable for Html {
fn compose(&mut self) -> impl IntoComposable {
let cx = HtmlContext::current();
let inner = cx.inner.borrow_mut();
let element = inner.document.create_element("div").unwrap();
inner.body.append_child(&element).unwrap();
}
}

pub fn run<C>(content: fn() -> C)
where
C: IntoComposable + 'static,
{
let cx = HtmlContext::new();
cx.enter();

let mut composition = Composition::new(content);
composition.build()
}
9 changes: 7 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use futures::channel::mpsc;
use futures::executor::LocalPool;
use slotmap::{DefaultKey, SlotMap, SparseSecondaryMap};
use std::{any::Any, cell::RefCell, collections::HashSet, rc::Rc};
use tokio::sync::mpsc;

mod any_composable;
pub use any_composable::AnyComposable;
Expand All @@ -18,11 +19,14 @@ mod use_ref;
pub use use_ref::{use_ref, Ref, RefMut, UseRef};

mod use_future;
pub use use_future::{use_future, UseFuture};
pub use use_future::use_future;

mod use_state;
pub use use_state::{use_state, UseState};

#[cfg(feature = "html")]
pub mod html;

#[derive(Default)]
struct GlobalContext {
values: SlotMap<DefaultKey, Rc<RefCell<dyn Any>>>,
Expand All @@ -35,6 +39,7 @@ thread_local! {

#[derive(Clone)]
struct TaskContext {
local_pool: Rc<RefCell<LocalPool>>,
tx: mpsc::UnboundedSender<Box<dyn Any>>,
}

Expand Down
36 changes: 8 additions & 28 deletions src/use_future.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,24 @@
use crate::{use_ref, UseRef, TASK_CONTEXT};
use crate::{use_ref, TASK_CONTEXT};
use futures::task::LocalSpawnExt;
use futures::Future;
use tokio::task::{self, JoinHandle};

/// A hook that spawns a local future on current thread.
pub fn use_future<F>(f: impl FnOnce() -> F) -> UseFuture<F::Output>
pub fn use_future<F>(f: impl FnOnce() -> F)
where
F: Future + 'static,
{
let handle = use_ref(|| {
let _handle = use_ref(|| {
let future = f();
TASK_CONTEXT
.try_with(|cx| {
let guard = cx.borrow_mut();
let cx = guard.as_ref().unwrap();
let tx = cx.tx.clone();
task::spawn_local(async move {
let output = future.await;
tx.send(Box::new(())).unwrap();
output
})
cx.local_pool.borrow().spawner().spawn_local(async move {
let _output = future.await;
tx.unbounded_send(Box::new(())).unwrap();
});
})
.unwrap()
});
UseFuture { handle }
}

pub struct UseFuture<T> {
handle: UseRef<JoinHandle<T>>,
}

impl<T: 'static> UseFuture<T> {
pub fn abort(&self) {
self.handle.get().abort()
}
}

impl<T> Clone for UseFuture<T> {
fn clone(&self) -> Self {
*self
}
}

impl<T> Copy for UseFuture<T> {}
2 changes: 1 addition & 1 deletion src/use_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ impl<T: 'static> UseState<T> {
let guard = cx.borrow_mut();
let cx = guard.as_ref().unwrap();
let tx = cx.tx.clone();
tx.send(Box::new(())).unwrap();
tx.unbounded_send(Box::new(())).unwrap();
})
.unwrap();
}
Expand Down
7 changes: 7 additions & 0 deletions web_examples/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "concoct-web-examples"
version = "0.1.0"
edition = "2021"

[dependencies]
concoct = { path = "../", features = ["html"] }
3 changes: 3 additions & 0 deletions web_examples/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<html>
<body></body>
</html>
9 changes: 9 additions & 0 deletions web_examples/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use concoct::{html::Html, IntoComposable};

fn app() -> impl IntoComposable {
Html {}
}

fn main() {
concoct::html::run(app)
}

0 comments on commit 6464d28

Please sign in to comment.