wip: doesnt share on thread

This commit is contained in:
Jonathan Kelley 2021-05-27 17:57:59 -04:00
parent ba975410f9
commit fe67ff9fa4
32 changed files with 1008 additions and 575 deletions

View file

@ -7,9 +7,9 @@ members = [
"packages/dioxus",
"packages/recoil",
"packages/docsite",
"packages/ssr",
] # "packages/webview",
# "packages/ssr",
# "packages/cli",
# "packages/webview",
# "packages/hooks",

View file

@ -19,11 +19,11 @@ serde = "1.0.120"
serde_json = "1.0.61"
async-std = { version = "1.9.0", features = ["attributes"] }
tide = "0.15.0"
cargo_toml = "0.8.1"
fs_extra = "1.2.0"
notify = "5.0.0-pre.4"
cargo_toml = "0.8.1"
futures = "0.3.12"
notify = "5.0.0-pre.4"
rjdebounce = "0.2.1"
tempfile = "3.2.0"

View file

@ -1,13 +1,14 @@
use std::{
cell::{RefCell, UnsafeCell},
collections::HashMap,
rc::Rc,
sync::Arc,
};
use generational_arena::Arena;
use crate::innerlude::*;
type Rc<T> = Arc<T>;
#[derive(Clone)]
pub struct ScopeArena(Rc<RefCell<ScopeArenaInner>>);

View file

@ -15,7 +15,7 @@ impl DebugRenderer {
///
/// This means that the root component must either consumes its own context, or statics are used to generate the page.
/// The root component can access things like routing in its context.
pub fn new(root: FC<()>) -> Self {
pub fn new(root: impl for<'a> Fn(Context<'a>, &'a ()) -> DomTree + 'static) -> Self {
Self::new_with_props(root, ())
}
@ -23,7 +23,10 @@ impl DebugRenderer {
/// Automatically progresses the creation of the VNode tree to completion.
///
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
pub fn new_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) -> Self {
pub fn new_with_props<T: Properties + 'static>(
root: impl for<'a> Fn(Context<'a>, &'a T) -> DomTree + 'static,
root_props: T,
) -> Self {
Self::from_vdom(VirtualDom::new_with_props(root, root_props))
}

View file

@ -41,9 +41,11 @@ use std::{
cell::{RefCell, RefMut},
cmp::Ordering,
collections::VecDeque,
rc::{Rc, Weak},
sync::atomic::AtomicU32,
// rc::{Rc, Weak},
sync::{Arc, Weak},
};
type Rc<T> = Arc<T>;
/// The DiffState is a cursor internal to the VirtualDOM's diffing algorithm that allows persistence of state while
/// diffing trees of components. This means we can "re-enter" a subtree of a component by queuing a "NeedToDiff" event.

View file

@ -10,7 +10,9 @@ use crate::{
use bumpalo::Bump;
use std::fmt::Debug;
use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc};
use std::sync::Arc;
use std::{any::Any, cell::RefCell, marker::PhantomData};
type Rc<T> = Arc<T>;
/// A domtree represents the result of "Viewing" the context
/// It's a placeholder over vnodes, to make working with lifetimes easier

View file

@ -29,9 +29,12 @@ use std::{
fmt::Debug,
future::Future,
pin::Pin,
rc::{Rc, Weak},
// rc::{Rc, Weak},
sync::{Arc, Weak},
};
type Rc<T> = Arc<T>;
/// An integrated virtual node system that progresses events and diffs UI trees.
/// Differences are converted into patches which a renderer can use to draw the UI.
pub struct VirtualDom {
@ -96,7 +99,7 @@ impl VirtualDom {
///
/// let dom = VirtualDom::new(Example);
/// ```
pub fn new(root: FC<()>) -> Self {
pub fn new(root: impl Fn(Context, &()) -> DomTree + 'static) -> Self {
Self::new_with_props(root, ())
}
@ -128,7 +131,10 @@ impl VirtualDom {
///
/// let dom = VirtualDom::new(Example);
/// ```
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
pub fn new_with_props<P: Properties + 'static>(
root: impl for<'a> Fn(Context<'a>, &'a P) -> DomTree + 'static,
root_props: P,
) -> Self {
let components = ScopeArena::new(Arena::new());
// Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents
@ -887,12 +893,11 @@ impl Scope {
let shared_contexts = inner.shared_contexts.borrow();
if let Some(shared_ctx) = shared_contexts.get(&ty) {
let rc = shared_ctx
.clone()
.downcast()
.downcast_ref()
.expect("Should not fail, already validated the type from the hashmap");
*cached_root.borrow_mut() = Some(Rc::downgrade(&rc));
return Ok(rc);
return Ok(rc.clone());
} else {
match inner.parent {
Some(parent_id) => {
@ -1086,4 +1091,18 @@ mod tests {
});
// let root = dom.components.get(dom.base_scope).unwrap();
}
// ensure the virtualdom is send + sync
// needed for use in async/await contexts
fn is_send_sync() {
fn check_send<T: Send>(a: T) -> T {
todo!()
}
fn check_sync<T: Sync>(a: T) -> T {
todo!()
}
let _ = check_send(VirtualDom::new(|ctx, props| ctx.render(rsx! { div {}})));
let _ = check_sync(VirtualDom::new(|ctx, props| ctx.render(rsx! { div {}})));
}
}

View file

@ -1,3 +1,3 @@
{
"rust-analyzer.inlayHints.enable": false
"rust-analyzer.inlayHints.enable": true
}

View file

@ -7,7 +7,10 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.40"
dioxus-core = { path = "../core" }
generational-arena = "0.2.8"
thiserror = "1.0.24"
[dev-dependencies]
uuid = { version = "0.8.2", features = ["v4"] }

View file

@ -14,7 +14,7 @@ A simple atom of state is defined globally as a const:
const Light: Atom<&'static str> = |_| "Green";
```
This atom of state is initialized with a value of `"Green"`. The atom that is returned does not actually contain any values. Instead, the atom's key - which is automatically generated in this instance - is used in the context of a Recoil App.
This atom of state is initialized with a value of `"Green"`. The atom that is returned does not actually contain any values. Instead, the atom's key - which is automatically generated in this instance - is used in the context of a Recoil App.
This is then later used in components like so:
@ -23,7 +23,7 @@ fn App(ctx: Context, props: &()) -> DomTree {
// The recoil root must be initialized at the top of the application before any use_recoil hooks
recoil::init_recoil_root(&ctx, |_| {});
let color = use_recoil(&ctx, &TITLE);
let color = recoil::use_read(ctx, Light);
ctx.render(rsx!{
h1 {"Color of light: {color}"}

View file

@ -29,9 +29,9 @@ impl TitleController {
fn main() {
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(|ctx, _| {
let title = use_recoil_value(ctx, &TITLE);
let subtitle = use_recoil_value(ctx, &SUBTITLE);
let controller = use_recoil_callback(ctx, TitleController::new);
let title = use_read(ctx, &TITLE);
let subtitle = use_read(ctx, &SUBTITLE);
let controller = use_recoil_api(ctx, TitleController::new);
rsx! { in ctx,
div {

View file

@ -20,8 +20,8 @@ fn update_title(api: &RecoilApi) {
}
static App: FC<()> = |ctx, _| {
let title = use_recoil_value(ctx, &TITLE);
let next_light = use_recoil_callback(ctx, |api| move |_| update_title(&api));
let title = use_read(ctx, &TITLE);
let next_light = use_recoil_api(ctx, |api| move |_| update_title(&api));
rsx! { in ctx,
div {

View file

@ -5,7 +5,7 @@
//! slicing up your state beyond atoms might be desirable.
//!
//! Instead of storing groups of entities in a collection of structs, the ECS Architecture instead stores
//! an array for each entity in the collection. This tends to improve performance for batch operations on
//! an array for each field in of a struct. This tends to improve performance for batch operations on
//! individual fields at the cost of complexity. Fortunately, this ECS model is built right into Recoil,
//! making it easier than ever to enable sharded datastructures in your app.
//!
@ -16,6 +16,7 @@
//!
//! This approach is best suited for applications where individual entries in families are very large
//! and updates to neighbors are costly in terms of Clone or field comparisons for memoization.
use dioxus::prelude::*;
use dioxus_core as dioxus;
use recoil::*;
@ -31,8 +32,9 @@ const TODOS: EcsModel<u32, TodoModel> = |builder| {};
// const SELECT_SUBTITLE: SelectorBorrowed<u32, &str> = |s, k| TODOS.field(1).select(k);
static App: FC<()> = |ctx, _| {
use_init_recoil_root(ctx, |_| {});
// let title = use_recoil_value(ctx, &C_SELECTOR);
dbg!(TODOS);
let title = "";
rsx! { in ctx,

View file

@ -14,9 +14,8 @@ struct Todo {
}
static App: FC<()> = |ctx, _| {
use_init_recoil_root(ctx);
let todos = use_recoil_family(ctx, &TODOS);
use_init_recoil_root(ctx, |_| {});
let todos = use_read_family(ctx, &TODOS);
rsx! { in ctx,
div {
@ -31,7 +30,7 @@ struct ChildProps {
}
static Child: FC<ChildProps> = |ctx, props| {
let (todo, set_todo) = use_recoil_state(ctx, &TODOS.select(&props.id));
let (todo, set_todo) = use_read_write(ctx, &TODOS.select(&props.id));
rsx! { in ctx,
div {

View file

@ -4,7 +4,9 @@ use recoil::*;
const COUNT: Atom<i32> = |_| 0;
static App: FC<()> = |ctx, _| {
let (count, set_count) = use_recoil_state(ctx, &COUNT);
use_init_recoil_root(ctx, |_| {});
let (count, set_count) = use_read_write(ctx, &COUNT);
rsx! { in ctx,
div {

View file

@ -18,10 +18,10 @@ const D_SELECTOR: SelectorFamilyBorrowed<i32, i32> = |api, key| -> &i32 {
};
static App: FC<()> = |ctx, _| {
use_init_recoil_root(ctx);
use_init_recoil_root(ctx, |_| {});
let title = use_read(ctx, &C_SELECTOR);
let title = use_recoil_value(ctx, &C_SELECTOR);
let title = "";
rsx! { in ctx,
div {
"{title}"

View file

@ -6,7 +6,7 @@ const B: Atom<i32> = |_| 0;
const C: Selector<i32> = |api| api.get(&A) + api.get(&B);
static App: FC<()> = |ctx, _| {
use_init_recoil_root(ctx);
use_init_recoil_root(ctx, |_| {});
rsx! { in ctx,
div {
Banner {}
@ -17,12 +17,12 @@ static App: FC<()> = |ctx, _| {
};
static Banner: FC<()> = |ctx, _| {
let count = use_recoil_value(ctx, &C);
let count = use_read(ctx, &C);
ctx.render(rsx! { h1 { "Count: {count}" } })
};
static BtnA: FC<()> = |ctx, _| {
let (a, set) = use_recoil_state(ctx, &A);
let (a, set) = use_read_write(ctx, &A);
rsx! { in ctx,
div { "a"
button { "+", onclick: move |_| set(a + 1) }
@ -32,7 +32,7 @@ static BtnA: FC<()> = |ctx, _| {
};
static BtnB: FC<()> = |ctx, _| {
let (b, set) = use_recoil_state(ctx, &B);
let (b, set) = use_read_write(ctx, &B);
rsx! { in ctx,
div { "b"
button { "+", onclick: move |_| set(b + 1) }

View file

@ -1,16 +1,15 @@
# Architecture
## ECS
It's often ideal to represent list-y state as an SoA (struct of arrays) instead of an AoS (array of structs). In 99% of apps, normal clone-y performance is fine. If you need more performance on top of cloning on update, IM.rs will provide fast immutable data structures.
But, if you need **extreme performance** consider the ECS (SoA) model. With ECS model, we can modify fields of an entry without invalidating selectors on neighboring fields.
But, if you need **extreme performance** consider the ECS (SoA) model. With ECS model, we can modify fields of an entry without invalidating selectors on neighboring fields.
This approach is for that 0.1% of apps that need peak performance. Our philosophy is that these tools should be available when needed, but you shouldn't need to reach for them often. An example use case might be a graphics editor or simulation engine where thousands of entities with many fields are rendered to the screen in realtime.
This approach is for that 0.1% of apps that need peak performance. Our philosophy is that these tools should be available when needed, but you shouldn't need to reach for them often. An example use case might be a graphics editor or simulation engine where thousands of entities with many fields are rendered to the screen in realtime.
Recoil will even help you:
```rust
type TodoModel = (
String, // title
@ -24,6 +23,7 @@ const SELECT_SUBTITLE: SelectorBorrowed<u32, &str> = |s, k| TODOS.field(1).selec
```
Or with a custom derive macro to take care of some boilerplate and maintain readability. This macro simply generates the type tuple from the model fields and then some associated constants for indexing them.
```rust
#[derive(EcsModel)]
struct TodoModel {
@ -44,22 +44,69 @@ const SELECT_TITLE: SelectorBorrowed<u32, &str> = |s, k| TODOS.field(TodoModel::
const SELECT_SUBTITLE: SelectorBorrowed<u32, &str> = |s, k| TODOS.field(TodoModel::subtitle).select(k);
```
## Optimization
Selectors and references.
----
Because new values are inserted without touching the original, we can keep old values around. As such, it makes sense to allow borrowed data since we can continue to reference old data if the data itself hasn't changed.
## Selectors and references.
However, this does lead to a greater memory overhead, so occasionally we'll want to sacrifice an a component render in order to evict old values.
Because new values are inserted without touching the original, we can keep old values around. As such, it makes sense to allow borrowed data since we can continue to reference old data if the data itself hasn't changed.
However, this does lead to a greater memory overhead, so occasionally we'll want to sacrifice an a component render in order to evict old values. This will be done automatically for us due to the nature of Rc.
Also, we want selectors to have internal dependencies.
## Async Atoms
- make it so "waiting" data can be provided
- someone updates via a future IE `atom.set(|| async {})`
- RecoilJS handles this with a "loadable" that has different states (ready, waiting, err just like Rust's poll method on future)
- I think the old value is provided until the new value is ready(?)
- Integrates with suspense to directly await the contents
## Async atoms
```rust
let (atom, set_atom, modify_atom) = (ATOM.use_read(ctx), ATOM.use_write(ctx), ATOM.use_modify(ctx));
const Title: AsyncAtom<String> = |builder| {
builder.on_set(|api, new_val| async {
})
builder.on_get(|api| async {
})
}
```
## Async selectors
```rust
struct ContentCard {
title: String,
content: String,
}
const ROOT_URL: Atom<&str> = |_| "localhost:8080";
// Since we don't plan on changing any content during the lifetime of the app, a selector works fine
const ContentCards: SelectorFamily<Uuid, ContentCard> = |api, key| api.on_get_async(async {
// Whenever the root_url changes, this atom will be re-evaluated
let root_url = api.get(&ROOT_URL);
let data: ContentCard = fetch(format!("{}/{}", root_url, key).await?.json().await?;
data
})
static ContentCard: FC<()> = |ctx, props| {
let body = async match use_recoil_value()(props.id).await {
Ok(content) => rsx!{ p {"{content}"} }
Err(e) => rsx!{ p {"Failed to load"}}
};
rsx!{
div {
h1{ "Non-async data here" }}
Suspense {
content: {body}
fallback: { rsx!{ div {"Loading..."} } }
}
}
}
};
```

View file

@ -1,26 +0,0 @@
use crate::{AtomValue, Readable, RecoilItem};
pub type Atom<T: PartialEq> = fn(&mut AtomBuilder<T>) -> T;
impl<T: AtomValue + 'static> Readable<T> for Atom<T> {
fn load(&'static self) -> RecoilItem {
todo!()
// RecoilItem::Atom(self as *const _ as _)
}
}
pub struct AtomBuilder<T: PartialEq> {
pub key: String,
_never: std::marker::PhantomData<T>,
}
impl<T: PartialEq> AtomBuilder<T> {
pub fn new() -> Self {
Self {
key: "".to_string(),
_never: std::marker::PhantomData {},
}
}
pub fn set_key(&mut self, _key: &'static str) {}
}

View file

@ -1,32 +0,0 @@
use std::rc::Rc;
use crate::Atom;
pub struct RecoilApi {}
impl RecoilApi {
/// Get the value of an atom. Returns a reference to the underlying data.
pub fn get<T: PartialEq>(&self, t: &'static Atom<T>) -> Rc<T> {
todo!()
}
/// Replace an existing value with a new value
///
/// This does not replace the value instantly.
/// All calls to "get" will return the old value until the component is rendered.
pub fn set<T: PartialEq>(&self, t: &'static Atom<T>, new: T) {
self.modify(t, move |old| *old = new);
}
/// Modify lets you modify the value in place. However, because there's no previous value around to compare
/// the new one with, we are unable to memoize the change. As such, all downsteam users of this Atom will
/// be updated, causing all subsrcibed components to re-render.
///
/// This is fine for most values, but might not be performant when dealing with collections. For collections,
/// use the "Family" variants as these will stay memoized for inserts, removals, and modifications.
///
/// Note - like "set" this won't propogate instantly. Once all "gets" are dropped, only then will the update occur
pub fn modify<T: PartialEq, O>(&self, t: &'static Atom<T>, f: impl FnOnce(&mut T) -> O) -> O {
todo!()
}
}

View file

@ -1,26 +0,0 @@
//! Provide a memoized wrapper around collections for efficient updates.
//! --------------------------------------------------------------------
//!
use crate::{AtomValue, FamilyKey, Readable, RecoilItem};
#[allow(non_camel_case_types)]
pub struct atom_family<K: FamilyKey, V: AtomValue>(pub fn(&mut AtomFamilyBuilder<K, V>));
pub type AtomFamily<K, V> = atom_family<K, V>;
// impl<K: FamilyKey, V: AtomValue> Readable for &'static AtomFamily<K, V> {
// fn load(&self) -> RecoilItem {
// RecoilItem::Atom(*self as *const _ as _)
// }
// }
pub struct AtomFamilyBuilder<K, V> {
_never: std::marker::PhantomData<(K, V)>,
}
impl<K: FamilyKey, V: AtomValue> atom_family<K, V> {
fn select(&'static self, key: &K) -> FamilySelected {
todo!()
}
}
struct FamilySelected {}

View file

@ -1,110 +0,0 @@
use std::rc::Rc;
use dioxus_core::{hooks::use_ref, prelude::Context};
use crate::{Atom, AtomValue, Readable, RecoilApi, RecoilRoot};
/// This hook initializes the Recoil Context - should only be placed once per app.
///
///
/// ```ignore
///
///
///
/// ```
pub fn init_root(ctx: Context) {
ctx.use_create_context(move || RecoilRoot::new())
}
/// Use an atom and its setter
///
/// This hook subscribes the component to any updates to the atom.
///
/// ```rust
/// const TITLE: Atom<&str> = atom(|_| "default");
///
/// static App: FC<()> = |ctx, props| {
/// let (title, set_title) = recoil::use_state(ctx, &TITLE);
/// ctx.render(rsx!{
/// div {
/// "{title}"
/// button {"on", onclick: move |_| set_title("on")}
/// button {"off", onclick: move |_| set_title("off")}
/// }
/// })
/// }
///
/// ```
pub fn use_recoil_state<'a, T: PartialEq + 'static>(
ctx: Context<'a>,
readable: &'static impl Readable<T>,
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
struct RecoilStateInner<G: AtomValue> {
root: Rc<RecoilRoot>,
value: Rc<G>,
setter: Rc<dyn Fn(G)>,
}
let root = ctx.use_context::<RecoilRoot>();
let (subscriber_id, value) = root.subscribe_consumer(readable, ctx.schedule_update());
ctx.use_hook(
move || RecoilStateInner {
value,
root: root.clone(),
setter: Rc::new(move |new_val| root.update_atom(readable, new_val)),
},
move |hook| {
hook.value = hook.root.load_value(readable);
(hook.value.as_ref(), &hook.setter)
},
// Make sure we unsubscribe
// It's not *wrong* for a dead component to receive updates, but it is less performant
move |hook| hook.root.drop_consumer(subscriber_id),
)
}
///
///
///
/// ```ignore
/// let (title, set_title) = recoil::use_state()
///
///
///
/// ```
pub fn use_recoil_value<'a, T: PartialEq>(ctx: Context<'a>, t: &'static impl Readable<T>) -> &'a T {
todo!()
}
/// Update an atom's value without
///
/// Enable the ability to set a value without subscribing the componet
///
///
///
/// ```ignore
/// let (title, set_title) = recoil::use_state()
///
///
///
/// ```
pub fn use_set_state<'a, T: PartialEq>(c: Context<'a>, t: &'static Atom<T>) -> &'a Rc<dyn Fn(T)> {
todo!()
}
///
///
///
/// ```ignore
/// let (title, set_title) = recoil::use_state()
///
///
///
/// ```
pub fn use_recoil_callback<'a, F: 'a>(
ctx: Context<'a>,
f: impl Fn(RecoilApi) -> F + 'static,
) -> &F {
todo!()
}

View file

@ -1,15 +0,0 @@
mod atom;
mod callback;
mod family;
mod hooks;
mod root;
mod selector;
mod traits;
pub use atom::*;
pub use callback::*;
pub use family::*;
pub use hooks::*;
pub use root::*;
pub use selector::*;
pub use traits::*;

View file

@ -1,148 +0,0 @@
use std::{
any::Any,
cell::RefCell,
collections::HashMap,
rc::Rc,
sync::atomic::{AtomicU32, AtomicUsize},
};
use crate::{Atom, AtomBuilder, AtomValue, Readable};
pub type OpaqueConsumerCallback = Box<dyn Any>;
struct Consumer {
callback: OpaqueConsumerCallback,
}
static SUBSCRIBER_ID: AtomicU32 = AtomicU32::new(0);
fn next_id() -> u32 {
SUBSCRIBER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}
type AtomId = u32;
type ConsumerId = u32;
pub struct RecoilRoot {
consumers: RefCell<HashMap<AtomId, Box<dyn RecoilSlot>>>,
}
impl RecoilRoot {
pub(crate) fn new() -> Self {
Self {
consumers: Default::default(),
}
}
// This is run by hooks when they register as a listener for a given atom
// Their ID is stored in the `atom_consumers` map which lets us know which consumers to update
// When the hook is dropped, they are unregistered from this map
//
// If the Atom they're registering as a listener for doesn't exist, then the atom
// is initialized. Otherwise, the most recent value of the atom is returned
// pub fn register_readable<T: AtomValue>(&self, atom: &impl Readable<T>) -> Rc<T> {
// todo!()
// }
/// This registers the updater fn to any updates to the readable
/// Whenever the readable changes, the updater Fn will be called.
///
/// This also back-propogates changes, meaning components that update an atom
/// will be updated from their subscription instead of directly within their own hook.
pub fn subscribe_consumer<T: AtomValue>(
&self,
atom: &'static impl Readable<T>,
updater: impl Fn(),
// return the value and the consumer's ID
// the consumer needs to store its ID so it can properly unsubscribe from the value
) -> (u32, Rc<T>) {
let id = next_id();
// Get the raw static reference of the atom
let atom_ptr = get_atom_raw_ref(atom);
let mut consumers = self.consumers.borrow_mut();
if !consumers.contains_key(&atom_ptr) {
// Run the atom's initialization
// let mut b = AtomBuilder::<T>::new();
// let inital_value = atom.0(&mut b);
consumers.insert(atom_ptr, HashMap::new());
}
todo!()
// Rc::new(inital_value)
// todo!()
// // Get the raw static reference of the atom
// let atom_ptr = get_atom_raw_ref(atom);
// // Subcribe this hook_id to this atom
// let mut consumers = self.consumers.borrow_mut();
// let hooks = consumers
// .get_mut(&atom_ptr)
// .expect("Atom must be initialized before being subscribed to");
// // Coerce into any by wrapping the updater in a box
// // (yes it's some weird indirection)
// let any_updater: OpaqueConsumerCallback = Box::new(updater);
// let consumer = Consumer {
// callback: any_updater,
// };
// Insert into the map, booting out the old consumer
// TODO @Jon, make the "Consumer" more efficient, patching its update mechanism in-place
// hooks.insert(hook_id, consumer);
}
pub fn drop_consumer(&self, subscriber_id: u32) {
// let mut consumers = self.consumers.borrow_mut();
// let atom_ptr = get_atom_raw_ref(atom);
// let atoms = consumers.get_mut(&atom_ptr);
// if let Some(consumers) = atoms {
// let entry = consumers.remove_entry(&hook_id);
// if let Some(_) = entry {
// log::debug!("successfully unsubscribed listener");
// } else {
// log::debug!("Failure to unsubscribe");
// }
// } else {
// log::debug!("Strange error, atoms should be registed if the consumer is being dropped");
// }
}
pub fn update_atom<T: AtomValue>(&self, atom: &impl Readable<T>, new_val: T) {
// Get the raw static reference of the atom
// let atom_ptr = get_atom_raw_ref(atom);
// let mut consumers = self.consumers.borrow_mut();
// let hooks = consumers
// .get_mut(&atom_ptr)
// .expect("Atom needs to be registered before trying to update it");
// let new_val = Rc::new(new_val);
// for hook in hooks.values_mut() {
// let callback: &mut Rc<ConsumerCallback<T>> = hook
// .callback
// .downcast_mut::<_>()
// .expect("Wrong type of atom stored, internal error");
// callback(UpdateAction::Regenerate(new_val.clone()));
// }
}
pub fn load_value<T: AtomValue>(&self, atom: &impl Readable<T>) -> Rc<T> {
todo!()
}
}
trait RecoilSlot {}
// struct RecoilSlot {
// consumers: HashMap<u32, Box<dyn Fn()>>,
// }
fn get_atom_raw_ref<T: AtomValue>(atom: &'static impl Readable<T>) -> u32 {
let atom_ptr = atom as *const _;
let atom_ptr = atom_ptr as *const u32;
atom_ptr as u32
}

View file

@ -1,25 +0,0 @@
use crate::Atom;
// =====================================
// Selectors
// =====================================
pub struct SelectorApi {}
impl SelectorApi {
pub fn get<T: PartialEq>(&self, t: &'static Atom<T>) -> &T {
todo!()
}
}
// pub struct SelectorBuilder<Out, const Built: bool> {
// _p: std::marker::PhantomData<Out>,
// }
// impl<O> SelectorBuilder<O, false> {
// pub fn getter(self, f: impl Fn(()) -> O) -> SelectorBuilder<O, true> {
// todo!()
// // std::rc::Rc::pin(value)
// // todo!()
// }
// }
pub struct selector<O>(pub fn(&SelectorApi) -> O);
// pub struct selector<O>(pub fn(SelectorBuilder<O, false>) -> SelectorBuilder<O, true>);
pub type Selector<O> = selector<O>;

View file

@ -1,13 +0,0 @@
use crate::{Atom, AtomFamily};
use std::hash::Hash;
pub trait FamilyKey: PartialEq + Hash {}
impl<T: PartialEq + Hash> FamilyKey for T {}
pub trait AtomValue: PartialEq {}
impl<T: PartialEq> AtomValue for T {}
pub trait Readable<T>: 'static {
fn load(&'static self) -> RecoilItem;
}

View file

@ -6,158 +6,443 @@ use std::{
rc::Rc,
};
pub trait FamilyKey: PartialEq + Hash {}
impl<T: PartialEq + Hash> FamilyKey for T {}
pub use api::*;
pub use atomfamily::*;
pub use atoms::*;
pub use ecs::*;
use error::*;
pub use hooks::*;
pub use hooks::*;
pub use root::*;
pub use selector::*;
pub use selectorfamily::*;
pub use traits::*;
pub use utils::*;
pub trait AtomValue: PartialEq + Clone {}
impl<T: PartialEq + Clone> AtomValue for T {}
mod traits {
use dioxus_core::prelude::Context;
// Atoms, selectors, and their family variants are readable
pub trait Readable<T> {}
use super::*;
pub trait FamilyKey: PartialEq + Hash + 'static {}
impl<T: PartialEq + Hash + 'static> FamilyKey for T {}
// Only atoms and atom families are writable
// Selectors are not
pub trait Writeable<T>: Readable<T> {}
pub trait AtomValue: PartialEq + 'static {}
impl<T: PartialEq + 'static> AtomValue for T {}
// =================
// Atoms
// =================
// Atoms, selectors, and their family variants are readable
pub trait Readable<T: AtomValue>: Sized + Copy {
fn use_read<'a>(self, ctx: Context<'a>) -> &'a T {
hooks::use_read(ctx, self);
todo!()
}
pub struct AtomBuilder {}
pub type Atom<T> = fn(&mut AtomBuilder) -> T;
impl<T> Readable<T> for Atom<T> {}
impl<T> Writeable<T> for Atom<T> {}
// This returns a future of the value
// If the atom is currently pending, that future will resolve to pending
// If the atom is currently ready, the future will immediately resolve
// if the atom switches from ready to pending, the component will re-run, returning a pending future
fn use_read_async<'a>(self, ctx: Context<'a>) -> &'a T {
todo!()
}
pub type AtomFamily<K, V, F = HashMap<K, V>> = fn((K, V)) -> F;
fn initialize(self, api: &RecoilRoot) -> T {
todo!()
}
pub trait SelectionSelector<K, V> {
fn select(&self, k: &K) -> CollectionSelection<V> {
todo!()
// We use the Raw Ptr to the atom
// TODO: Make sure atoms with the same definitions don't get merged together. I don't think they do, but double check
fn static_id(self) -> u32;
}
}
impl<K, V, F> SelectionSelector<K, V> for AtomFamily<K, V, F> {}
pub trait FamilyCollection<K, V> {}
impl<K, V> FamilyCollection<K, V> for HashMap<K, V> {}
pub struct CollectionSelection<T> {
_never: PhantomData<T>,
}
impl<T> Readable<T> for CollectionSelection<T> {}
impl<T> Writeable<T> for CollectionSelection<T> {}
// Only atoms and atom families are writable
// Selectors and selector families are not
pub trait Writable<T: AtomValue>: Readable<T> + Sized {
fn use_read_write<'a>(self, ctx: Context<'a>) -> (&'a T, &'a Rc<dyn Fn(T)>) {
todo!()
}
// =================
// Selectors
// =================
pub struct SelectorBuilder {}
impl SelectorBuilder {
pub fn get<T: PartialEq>(&self, t: &impl Readable<T>) -> &T {
todo!()
}
}
pub type Selector<T> = fn(&mut SelectorBuilder) -> T;
impl<T> Readable<T> for Selector<T> {}
pub struct SelectorFamilyBuilder {}
impl SelectorFamilyBuilder {
pub fn get<T: PartialEq>(&self, t: &impl Readable<T>) -> &T {
todo!()
}
}
/// Create a new value as a result of a combination of previous values
/// If you need to return borrowed data, check out [`SelectorFamilyBorrowed`]
pub type SelectorFamily<Key, Value> = fn(&mut SelectorFamilyBuilder, Key) -> Value;
impl<K, V> Readable<V> for SelectorFamily<K, V> {}
/// Borrowed selector families are surprisingly - discouraged.
/// This is because it's not possible safely memoize these values without keeping old versions around.
///
/// However, it does come in handy to borrow the contents of an item without re-rendering child components.
pub type SelectorFamilyBorrowed<Key, Value> =
for<'a> fn(&'a mut SelectorFamilyBuilder, Key) -> &'a Value;
impl<'a, K, V: 'a> SelectionSelector<K, V> for fn(&'a mut SelectorFamilyBuilder, K) -> V {}
// =================
// API
// =================
pub struct RecoilApi {}
impl RecoilApi {
pub fn get<T: PartialEq>(&self, t: &'static Atom<T>) -> Rc<T> {
todo!()
}
pub fn modify<T: PartialEq, O>(&self, t: &'static Atom<T>, f: impl FnOnce(&mut T) -> O) -> O {
todo!()
}
pub fn set<T: PartialEq>(&self, t: &'static Atom<T>, new: T) {
self.modify(t, move |old| *old = new);
}
}
// ================
// Root
// ================
type AtomId = u32;
type ConsumerId = u32;
pub struct RecoilRoot {
consumers: RefCell<HashMap<AtomId, Box<dyn RecoilSlot>>>,
}
trait RecoilSlot {}
impl RecoilRoot {
pub(crate) fn new() -> Self {
Self {
consumers: Default::default(),
fn use_write<'a>(self, ctx: Context<'a>) -> &'a Rc<dyn Fn(T)> {
todo!()
}
}
}
pub use hooks::*;
mod atoms {
use super::*;
// Currently doesn't do anything, but will eventually add effects, id, serialize/deserialize keys, etc
// Doesn't currently allow async values, but the goal is to eventually enable them
pub struct AtomBuilder {}
pub type Atom<T> = fn(&mut AtomBuilder) -> T;
// impl<T: AtomValue> Readable<T> for Atom<T> {}
impl<T: AtomValue> Readable<T> for &'static Atom<T> {
fn static_id(self) -> u32 {
todo!()
}
}
impl<T: AtomValue> Writable<T> for &'static Atom<T> {}
mod compilests {
use super::*;
use dioxus_core::prelude::Context;
const Example: Atom<i32> = |_| 10;
fn test(ctx: Context) {
// ensure that atoms are both read and write
let _ = use_read(ctx, &Example);
let _ = use_read_write(ctx, &Example);
let _ = use_write(ctx, &Example);
}
}
}
mod atomfamily {
use super::*;
pub trait FamilyCollection<K, V> {}
impl<K, V> FamilyCollection<K, V> for HashMap<K, V> {}
pub type AtomFamily<K, V, F = HashMap<K, V>> = fn((&K, &V)) -> F;
pub trait AtomFamilySelector<K: FamilyKey, V: AtomValue> {
fn select(&'static self, k: &K) -> AtomFamilySelection<K, V> {
todo!()
}
}
impl<K: FamilyKey, V: AtomValue> AtomFamilySelector<K, V> for AtomFamily<K, V> {
fn select(&'static self, k: &K) -> AtomFamilySelection<K, V> {
todo!()
}
}
pub struct AtomFamilySelection<'a, K: FamilyKey, V: AtomValue> {
root: &'static AtomFamily<K, V>,
key: &'a K,
}
impl<'a, K: FamilyKey, V: AtomValue> Readable<V> for &AtomFamilySelection<'a, K, V> {
fn static_id(self) -> u32 {
todo!()
}
}
impl<'a, K: FamilyKey, T: AtomValue> Writable<T> for &AtomFamilySelection<'a, K, T> {}
mod compiletests {
use dioxus_core::prelude::Context;
use super::*;
const Titles: AtomFamily<u32, &str> = |_| HashMap::new();
fn test(ctx: Context) {
let title = Titles.select(&10).use_read(ctx);
let t2 = use_read(ctx, &Titles.select(&10));
}
}
}
mod selector {
use super::*;
pub struct SelectorBuilder {}
impl SelectorBuilder {
pub fn get<T: AtomValue>(&self, t: impl Readable<T>) -> &T {
todo!()
}
}
pub type Selector<T> = fn(&mut SelectorBuilder) -> T;
impl<T: AtomValue> Readable<T> for &'static Selector<T> {
fn static_id(self) -> u32 {
todo!()
}
}
pub struct SelectorFamilyBuilder {}
impl SelectorFamilyBuilder {
pub fn get<T: AtomValue>(&self, t: impl Readable<T>) -> &T {
todo!()
}
}
}
mod selectorfamily {
use super::*;
// pub trait SelectionSelector<K, V> {
// fn select(&self, k: &K) -> CollectionSelection<K, V> {
// todo!()
// }
// }
// impl<K, V, F> SelectionSelector<K, V> for AtomFamily<K, V, F> {}
/// Create a new value as a result of a combination of previous values
/// If you need to return borrowed data, check out [`SelectorFamilyBorrowed`]
pub type SelectorFamily<Key, Value> = fn(&mut SelectorFamilyBuilder, Key) -> Value;
impl<K, V: AtomValue> Readable<V> for &'static SelectorFamily<K, V> {
fn static_id(self) -> u32 {
todo!()
}
}
/// Borrowed selector families are surprisingly - discouraged.
/// This is because it's not possible safely memoize these values without keeping old versions around.
///
/// However, it does come in handy to borrow the contents of an item without re-rendering child components.
pub type SelectorFamilyBorrowed<Key, Value> =
for<'a> fn(&'a mut SelectorFamilyBuilder, Key) -> &'a Value;
// impl<'a, K, V: 'a> SelectionSelector<K, V> for fn(&'a mut SelectorFamilyBuilder, K) -> V {}
}
mod api {
use super::*;
// pub struct RecoilApi {}
// impl RecoilApi {
// pub fn get<T: AtomValue>(&self, t: &'static Atom<T>) -> Rc<T> {
// todo!()
// }
// pub fn modify<T: PartialEq, O>(
// &self,
// t: &'static Atom<T>,
// f: impl FnOnce(&mut T) -> O,
// ) -> O {
// todo!()
// }
// pub fn set<T: AtomValue>(&self, t: &'static Atom<T>, new: T) {
// self.modify(t, move |old| *old = new);
// }
// }
}
mod root {
use std::{
any::{Any, TypeId},
collections::{HashSet, VecDeque},
iter::FromIterator,
};
use super::*;
// use generational_arena::Index as ConsumerId;
type AtomId = u32;
type ConsumerId = u32;
pub struct RecoilContext {
pub(crate) inner: Rc<RefCell<RecoilRoot>>,
}
impl RecoilContext {
pub fn new() -> Self {
Self {
inner: Rc::new(RefCell::new(RecoilRoot::new())),
}
}
}
// Sometimes memoization means we don't need to re-render components that holds "correct values"
// IE we consider re-render more expensive than keeping the old value around.
// We *could* unsafely overwrite this slot, but that's just **asking** for UB (holding a &mut while & is held in components)
//
// Instead, we choose to let the hook itself hold onto the Rc<T> by not forcing a render when T is the same.
// Whenever the component needs to be re-rendered for other reasons, the "get" method will automatically update the Rc<T> to the most recent one.
pub struct RecoilRoot {
nodes: HashMap<AtomId, Slot>,
}
struct Slot {
type_id: TypeId,
source: AtomId,
value: Rc<dyn Any>,
consumers: HashMap<ConsumerId, Rc<dyn Fn()>>,
dependents: HashSet<AtomId>,
}
impl RecoilRoot {
pub(crate) fn new() -> Self {
Self {
nodes: Default::default(),
}
}
pub fn subscribe<T: AtomValue>(
&self,
readable: impl Readable<T>,
receiver_fn: Rc<dyn Fn()>,
) -> ConsumerId {
todo!()
}
pub fn unsubscribe(&self, id: ConsumerId) {
todo!()
}
/// Directly get the *slot*
/// All Atoms are held in slots (an Rc)
///
///
pub fn try_get_raw<T: AtomValue>(&self, readable: impl Readable<T>) -> Result<Rc<T>> {
todo!()
}
// pub fn try_get<T: AtomValue>(&self, readable: impl Readable<T>) -> Result<&T> {
// self.try_get_raw(readable).map(|f| f.as_ref())
// }
pub fn try_set<T: AtomValue>(
&mut self,
writable: impl Writable<T>,
new_val: T,
) -> crate::error::Result<()> {
let atom_id = writable.static_id();
let consumers = match self.nodes.get_mut(&atom_id) {
Some(slot) => {
slot.value = Rc::new(new_val);
&slot.consumers
}
None => {
let value = Slot {
type_id: TypeId::of::<T>(),
source: atom_id,
value: Rc::new(writable.initialize(self)),
consumers: Default::default(),
dependents: Default::default(),
};
self.nodes.insert(atom_id, value);
&self.nodes.get(&atom_id).unwrap().consumers
}
};
for (_, consumer_fn) in consumers {
consumer_fn();
}
// if it's a an atom or selector, update all the dependents
Ok(())
}
pub fn get<T: AtomValue>(&self, readable: impl Readable<T>) -> Rc<T> {
todo!()
// self.try_get(readable).unwrap()
}
pub fn set<T: AtomValue>(&mut self, writable: impl Writable<T>, new_val: T) {
self.try_set(writable, new_val).unwrap();
}
/// A slightly dangerous method to manually overwrite any slot given an AtomId
pub(crate) fn set_by_id<T: AtomValue>(&self, id: AtomId, new_val: T) {}
}
}
mod hooks {
use super::*;
use dioxus_core::prelude::Context;
pub fn use_init_recoil_root(ctx: Context) {
pub fn use_init_recoil_root(ctx: Context, cfg: impl Fn(())) {
ctx.use_create_context(move || RecoilRoot::new())
}
pub fn use_set_state<'a, T: PartialEq>(
c: Context<'a>,
t: &impl Writeable<T>,
) -> &'a Rc<dyn Fn(T)> {
todo!()
}
pub fn use_recoil_state<'a, T: PartialEq + 'static>(
/// Gain access to the recoil API directly - set, get, modify, everything
/// This is the foundational hook in which read/write/modify are built on
///
/// This does not subscribe the component to *any* updates
///
/// You can use this method to create controllers that perform much more complex actions than set/get
/// However, be aware that "getting" values through this hook will not subscribe the component to any updates.
pub fn use_recoil_api<'a, F: 'a>(
ctx: Context<'a>,
writeable: &impl Writeable<T>,
f: impl Fn(Rc<RecoilRoot>) -> F + 'static,
) -> &F {
let g = ctx.use_context::<RecoilContext>();
let api = g.inner.clone();
todo!()
}
pub fn use_write<'a, T: AtomValue>(
ctx: Context<'a>,
writable: impl Writable<T>,
) -> &'a Rc<dyn Fn(T)> {
let api = use_recoil_api(ctx, |f| f);
ctx.use_hook(
move || {
let api = api.clone();
let raw_id = writable.static_id();
Rc::new(move |new_val| api.set_by_id(raw_id, new_val)) as Rc<dyn Fn(T)>
},
move |hook| &*hook,
|hook| {},
)
}
/// Read the atom and get the Rc directly to the Atom's slot
/// This is useful if you need the memoized Atom value. However, Rc<T> is not as easy to
/// work with as
pub fn use_read_raw<'a, T: AtomValue>(ctx: Context<'a>, readable: impl Readable<T>) -> &Rc<T> {
struct ReadHook<T> {
value: Rc<T>,
consumer_id: u32,
}
let api = use_recoil_api(ctx, |api| api);
ctx.use_hook(
move || {
let update = ctx.schedule_update();
let val = api.try_get_raw(readable).unwrap();
let id = api.subscribe(readable, Rc::new(update));
ReadHook {
value: val,
consumer_id: id,
}
},
move |hook| {
let val = api.try_get_raw(readable).unwrap();
hook.value = val;
&hook.value
},
|hook| {
api.unsubscribe(hook.consumer_id);
},
)
}
///
pub fn use_read<'a, T: AtomValue>(ctx: Context<'a>, readable: impl Readable<T>) -> &'a T {
use_read_raw(ctx, readable).as_ref()
}
/// Use an atom in both read and write modes - only available for atoms and family selections (not selectors)
/// This is equivalent to calling both `use_read` and `use_write`, but saves you the hassle and repitition
///
/// ```
/// const Title: Atom<&str> = |_| "hello";
/// //...
/// let (title, set_title) = use_read_write(ctx, &Title);
///
/// // equivalent to:
/// let (title, set_title) = (use_read(ctx, &Title), use_write(ctx, &Title));
/// ```
pub fn use_read_write<'a, T: AtomValue + 'static>(
ctx: Context<'a>,
writable: impl Writable<T>,
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
todo!()
(use_read(ctx, writable), use_write(ctx, writable))
}
pub fn use_recoil_value<'a, T: PartialEq>(ctx: Context<'a>, t: &impl Readable<T>) -> &'a T {
todo!()
}
pub fn use_recoil_family<'a, K, V, C: FamilyCollection<K, V>>(
/// Use a family collection directly
/// !! Any changes to the family will cause this subscriber to update
/// Try not to put this at the very top-level of your app.
pub fn use_read_family<'a, K, V, C: FamilyCollection<K, V>>(
ctx: Context<'a>,
t: &AtomFamily<K, V, C>,
) -> &'a C {
todo!()
}
pub fn use_recoil_callback<'a, F: 'a>(
ctx: Context<'a>,
f: impl Fn(RecoilApi) -> F + 'static,
) -> &F {
todo!()
}
}
pub use ecs::*;
mod ecs {
use super::*;
pub struct Blah<K, V> {
@ -165,3 +450,52 @@ mod ecs {
}
pub type EcsModel<K, Ty> = fn(Blah<K, Ty>);
}
mod utils {
use super::*;
use dioxus_core::prelude::*;
/// This tiny util wraps your main component with the initializer for the recoil root.
/// This is useful for small programs and the examples in this crate
pub fn RecoilApp<T: 'static>(
root: impl for<'a> Fn(Context<'a>, &'a T) -> DomTree,
) -> impl for<'a> Fn(Context<'a>, &'a T) -> DomTree {
move |ctx, props| {
use_init_recoil_root(ctx, |_| {});
root(ctx, props)
}
}
}
mod compiletests {}
pub mod error {
use thiserror::Error as ThisError;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(ThisError, Debug)]
pub enum Error {
#[error("Fatal Internal Error: {0}")]
FatalInternal(&'static str),
// #[error("Context is missing")]
// MissingSharedContext,
// #[error("No event to progress")]
// NoEvent,
// #[error("Wrong Properties Type")]
// WrongProps,
// #[error("Base scope has not been mounted yet")]
// NotMounted,
// #[error("I/O Error: {0}")]
// BorrowMut(#[from] std::),
// #[error("eee Error: {0}")]
// IO(#[from] core::result::),
#[error("I/O Error: {0}")]
IO(#[from] std::io::Error),
#[error(transparent)]
Other(#[from] anyhow::Error), // source and Display delegate to anyhow::Error
}
}

View file

@ -8,3 +8,17 @@ edition = "2018"
[dependencies]
dioxus-core = { path = "../core", version = "0.1.0" }
[dev-dependencies]
tide-websockets = "*"
thiserror = "1.0.23"
log = "0.4.13"
fern = { version = "0.6.0", features = ["colored"] }
anyhow = "1.0.38"
argh = "0.1.4"
serde = "1.0.120"
serde_json = "1.0.61"
async-std = { version = "1.9.0", features = ["attributes"] }
tide = "0.16.0"
fs_extra = "1.2.0"

View file

@ -0,0 +1,290 @@
<!-- a js-only interpreter for the dioxus patch stream :) -->
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<meta charset="UTF-8" />
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet" />
</head>
<body>
<div>hello dioxus</div>
</body>
<script>
class Interpreter {
constructor(root) {
this.stack = [root];
}
top() {
return this.stack[this.stack.length - 1];
}
pop() {
return this.stack.pop();
}
}
class OPTABLE {
// 0
SetText(edit, interp) {
interp.top(interp.stack).textContent = edit.text;
}
// 1
RemoveSelfAndNextSiblings(edit) {
const node = interp.pop();
let sibling = node.nextSibling;
while (sibling) {
const temp = sibling.nextSibling;
sibling.remove();
sibling = temp;
}
node.remove();
}
// 2
ReplaceWith(edit, interp) {
const newNode = interp.pop();
const oldNode = interp.pop();
oldNode.replaceWith(newNode);
interp.stack.push(newNode);
}
// 3
SetAttribute(edit, interp) {
const name = edit.name;
const value = edit.value;
const node = interp.top(interp.stack);
node.setAttribute(name, value);
// Some attributes are "volatile" and don't work through `setAttribute`.
if ((name === "value", interp)) {
node.value = value;
}
if ((name === "checked", interp)) {
node.checked = true;
}
if ((name === "selected", interp)) {
node.selected = true;
}
}
// 4
RemoveAttribute(edit, interp) {
const name = edit.name;
const node = interp.top(interp.stack);
node.removeAttribute(name);
// Some attributes are "volatile" and don't work through `removeAttribute`.
if ((name === "value", interp)) {
node.value = null;
}
if ((name === "checked", interp)) {
node.checked = false;
}
if ((name === "selected", interp)) {
node.selected = false;
}
}
// 5
PushReverseChild(edit, interp) {
const n = edit.n;
const parent = interp.top(interp.stack);
const children = parent.childNodes;
const child = children[children.length - n - 1];
interp.stack.push(child);
}
// 6
PopPushChild(edit, interp) {
const n = edit.n;
interp.pop();
const parent = interp.top(interp.stack);
const children = parent.childNodes;
const child = children[n];
interp.stack.push(child);
}
// 7
Pop(edit, interp) {
interp.pop();
}
// 8
AppendChild(edit, interp) {
console.log(interp.stack);
const child = interp.pop();
console.log(interp.stack);
interp.top().appendChild(child);
}
// 9
CreateTextNode(edit, interp) {
// interp.stack.push(document.createTextNode("asd"));
console.log(interp.stack);
interp.stack.push(document.createTextNode(edit.text));
console.log(interp.stack);
}
// 10
CreateElement(edit, interp) {
const tagName = edit.tag_name;
interp.stack.push(document.createElement(tagName));
}
// 11
NewEventListener(edit, interp) {
// todo!
const eventId = mem32[i++];
const eventType = interp.getCachedString(eventId);
const a = mem32[i++];
const b = mem32[i++];
const el = interp.top(interp.stack);
el.addEventListener(eventType, interp.eventHandler);
el[`dodrio-a-${eventType}`] = a;
el[`dodrio-b-${eventType}`] = b;
}
// 12
UpdateEventListener(edit, interp) {
// todo!
const eventId = mem32[i++];
const eventType = interp.getCachedString(eventId);
const el = interp.top(interp.stack);
el[`dodrio-a-${eventType}`] = mem32[i++];
el[`dodrio-b-${eventType}`] = mem32[i++];
}
// 13
RemoveEventListener(edit, interp) {
// todo!
const eventId = mem32[i++];
const eventType = interp.getCachedString(eventId);
const el = interp.top(interp.stack);
el.removeEventListener(eventType, interp.eventHandler);
}
// 14
AddCachedString(edit, interp) {
// todo!
const pointer = mem32[i++];
const length = mem32[i++];
const id = mem32[i++];
const str = string(mem8, pointer, length);
interp.addCachedString(str, id);
}
// 15
DropCachedString(edit, interp) {
// todo!
const id = mem32[i++];
interp.dropCachedString(id);
}
// 16
CreateElementNS(edit, interp) {
// const tagNameId = mem32[i++];
// const tagName = interp.getCachedString(tagNameId);
// const nsId = mem32[i++];
// const ns = interp.getCachedString(nsId);
interp.stack.push(document.createElementNS(edit.ns, edit.tag_name));
}
// 17
SaveChildrenToTemporaries(edit, interp) {
// let temp = mem32[i++];
// const start = mem32[i++];
// const end = mem32[i++];
let temp = edit.temp;
const start = edit.start;
const end = edit.end;
const parent = interp.top(interp.stack);
const children = parent.childNodes;
for (let i = start; i < end; i++, interp) {
interp.temporaries[temp++] = children[i];
}
}
// 18
PushChild(edit, interp) {
const parent = interp.top(interp.stack);
// const n = mem32[i++];
const n = edit.n;
const child = parent.childNodes[n];
interp.stack.push(child);
}
// 19
PushTemporary(edit, interp) {
// const temp = mem32[i++];
const temp = edit.temp;
interp.stack.push(interp.temporaries[temp]);
}
// 20
InsertBefore(edit, interp) {
const before = interp.pop();
const after = interp.pop();
after.parentNode.insertBefore(before, after);
interp.stack.push(before);
}
// 21
PopPushReverseChild(edit, interp) {
// const n = mem32[i++];
const n = edit.n;
interp.pop();
const parent = interp.top(interp.stack);
const children = parent.childNodes;
const child = children[children.length - n - 1];
interp.stack.push(child);
}
// 22
RemoveChild(edit, interp) {
// const n = mem32[i++];
const n = edit.n;
const parent = interp.top(interp.stack);
const child = parent.childNodes[n];
child.remove();
}
// 23
SetClass(edit, interp) {
// const classId = mem32[i++];
const className = edit.class_name;
interp.top(interp.stack).className = className;
}
// 24
SaveTemplate(edit, interp) {
const id = mem32[i++];
const template = interp.top(interp.stack);
interp.saveTemplate(id, template.cloneNode(true));
}
// 25
PushTemplate(edit, interp) {
const id = mem32[i++];
const template = interp.getTemplate(id);
interp.stack.push(template.cloneNode(true));
}
}
const op_table = new OPTABLE();
const interpreter = new Interpreter(window.document.body);
function EditListReceived(rawEditList) {
let editList = JSON.parse(rawEditList);
editList.forEach(function (edit, index) {
op_table[edit.type](edit, interpreter);
});
}
external.invoke("initiate");
</script>
</html>

View file

@ -0,0 +1,107 @@
//!
//!
//!
use std::{borrow::Borrow, rc::Rc, sync::Arc};
use async_std::{prelude::*, sync::RwLock};
use dioxus::{events::on::MouseEvent, virtual_dom::VirtualDom};
use dioxus_core::prelude::*;
use tide::{Body, Request, Response};
use tide_websockets::{Message, WebSocket};
#[derive(PartialEq, Props)]
struct ExampleProps {
initial_name: String,
}
static Example: FC<ExampleProps> = |ctx, props| {
let dispaly_name = use_state_new(&ctx, move || props.initial_name);
let buttons = ["Jack", "Jill", "Bob"].iter().map(|name| {
rsx!{
button {
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
onmouseover: move |_| dispaly_name.set(name.to_string())
"{name}"
}
}
});
ctx.render(rsx! {
div {
class: "py-12 px-4 text-center w-full max-w-2xl mx-auto"
span {
class: "text-sm font-semibold"
"Dioxus Example: Jack and Jill"
}
h2 {
class: "text-5xl mt-2 mb-6 leading-tight font-semibold font-heading"
"Hello, {dispaly_name}"
}
}
})
};
const TEMPLATE: &str = include_str!("./template.html");
// static VDOM: Arc<RwLock<VirtualDom>> = Arc::new(RwLock::new(VirtualDom::new_with_props(
// Example,
// ExampleProps {
// initial_name: "asd".to_string(),
// },
// )));
#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
let mut app = tide::new();
let (se, re) = async_std::channel::unbounded::<()>();
async_std::task::spawn(async move {
let dom = VirtualDom::new_with_props(
Example,
ExampleProps {
initial_name: "asd".to_string(),
},
);
while let Ok(msg) = re.recv().await {
//
}
});
// app.at("/").get(|_| async { Ok(Body::from_file())});
app.at("/").get(|_| async {
//
let response = Response::builder(200)
.body(TEMPLATE)
.content_type(tide::http::mime::HTML)
.build();
Ok(response)
});
app.at("/session/:name")
.get(WebSocket::new(|req: Request<()>, mut stream| async move {
let initial_name: String = req.param("name")?.parse().unwrap_or("...?".to_string());
// {
// let a = Rc::new(());
// }
// let dom = VirtualDom::new_with_props(Example, ExampleProps { initial_name });
// let g = RwLock::new(Rc::new(10));
// drop(g);
while let Some(Ok(Message::Text(input))) = stream.next().await {
let output: String = input.chars().rev().collect();
stream
.send_string(format!("{} | {}", &input, &output))
.await?;
}
Ok(())
}));
app.listen("127.0.0.1:8080").await?;
Ok(())
}

View file

@ -91,8 +91,8 @@ pub fn TodoList(ctx: Context, _props: &()) -> DomTree {
})
});
rsx! { in ctx,
div {
rsx! { in ctuse_read
div {use_read
header {
class: "header"
h1 {"todos"}
@ -135,7 +135,7 @@ pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
))}
}
))
}
}use_read
pub fn FilterToggles(ctx: Context, _props: &()) -> DomTree {
let reducer = use_recoil_callback(ctx, |api| TodoManager(api));

View file

@ -2,7 +2,7 @@
//! --------------
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
use dioxus::prelude::Properties;
use dioxus::prelude::{Context, DomTree, Properties};
use fxhash::FxHashMap;
use web_sys::{window, Document, Element, Event, Node};
// use futures::{channel::mpsc, SinkExt, StreamExt};
@ -33,7 +33,7 @@ impl WebsysRenderer {
///
/// Run the app to completion, panicing if any error occurs while rendering.
/// Pairs well with the wasm_bindgen async handler
pub async fn start(root: FC<()>) {
pub async fn start(root: impl for<'a> Fn(Context<'a>, &'a ()) -> DomTree + 'static) {
Self::new(root).run().await.expect("Virtual DOM failed :(");
}
@ -41,7 +41,7 @@ impl WebsysRenderer {
///
/// This means that the root component must either consumes its own context, or statics are used to generate the page.
/// The root component can access things like routing in its context.
pub fn new(root: FC<()>) -> Self {
pub fn new(root: impl for<'a> Fn(Context<'a>, &'a ()) -> DomTree + 'static) -> Self {
Self::new_with_props(root, ())
}
@ -49,7 +49,10 @@ impl WebsysRenderer {
/// Automatically progresses the creation of the VNode tree to completion.
///
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
pub fn new_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) -> Self {
pub fn new_with_props<T: Properties + 'static>(
root: impl for<'a> Fn(Context<'a>, &'a T) -> DomTree + 'static,
root_props: T,
) -> Self {
Self::from_vdom(VirtualDom::new_with_props(root, root_props))
}