wip: threadsafe

This commit is contained in:
Jonathan Kelley committed 2021-10-01 02:07:12 -04:00
1 parent 71f0df6374
commit 82953f2ac3
22 files changed
+318 -449

No files matched your search

-1
View File
@@ -1,4 +1,3 @@
{
"rust-analyzer.inlayHints.enable": false,
"rust-analyzer.cargo.allFeatures": true
}
+4 -2
View File
@@ -41,15 +41,17 @@ futures-util = "0.3.16"
log = "0.4.14"
num-format = "0.4.0"
separator = "0.4.1"
serde = { version = "1.0.126", features = ["derive"] }
serde = { version = "1.0.130", features = ["derive"] }
im-rc = "15.0.0"
fxhash = "0.2.1"
anyhow = "1.0.42"
reqwest = "0.11.4"
serde_json = "1.0.68"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
argh = "0.1.5"
env_logger = "0.9.0"
async-std = { version = "1.9.0", features = ["attributes"] }
tokio = { version = "1.12.0", features = ["full"] }
rand = { version = "0.8.4", features = ["small_rng"] }
gloo-timers = "0.2.1"
# surf = { version = "2.3.1", default-features = false, features = [
+3 -2
View File
@@ -6,8 +6,9 @@ the `TaskHandle` object.
use dioxus::prelude::*;
use gloo_timers::future::TimeoutFuture;
fn main() {
dioxus::desktop::launch(App, |c| c).unwrap();
#[tokio::main]
async fn main() {
dioxus::desktop::launch(App, |c| c).await;
}
pub static App: FC<()> = |cx, _| {
-208
View File
@@ -1,208 +0,0 @@
#![allow(
unused,
dead_code,
non_upper_case_globals,
non_camel_case_types,
non_snake_case
)]
fn main() {}
use dioxus::prelude::*;
use std::rc::Rc;
static App: FC<()> = |cx, props| {
let (selection, set_selection) = use_state(cx, || None as Option<usize>).classic();
let body = match selection {
Some(id) => rsx!(cx, ReferenceItem { selected: *id }),
None => rsx!(cx, div { "Select an concept to explore" }),
};
cx.render(rsx! {
div {
ScrollSelector { onselect: move |id| set_selection(id) }
{body}
}
})
};
// this is its own component to stay memoized
#[derive(Props)]
struct ScrollSelectorProps<'a> {
onselect: &'a dyn Fn(Option<usize>),
}
fn ScrollSelector<'a>(cx: Context<'a>, props: &'a ScrollSelectorProps) -> DomTree<'a> {
let selection_list = (&REFERENCES).iter().enumerate().map(|(id, _)| {
rsx! {
li {
h3 {}
}
}
});
cx.render(rsx! {
div {
h1 {""}
ul {
{selection_list}
button {
onclick: move |_| (props.onselect)(Some(10))
}
}
}
})
}
#[derive(PartialEq, Props)]
struct ReferenceItemProps {
selected: usize,
}
static ReferenceItem: FC<ReferenceItemProps> = |cx, props| {
let (caller, name, code) = REFERENCES[props.selected];
// Create the component using the factory API directly
let caller_node = LazyNodes::new(move |f| f.component(caller, (), None, &[]));
cx.render(rsx! {
div {
// Source of the left, rendered on the right
div {
code { "{code}" }
}
div {
{caller_node}
}
}
})
};
use reference::REFERENCES;
mod reference {
mod antipatterns;
mod basics;
mod children;
mod conditional_rendering;
mod controlled_inputs;
mod custom_elements;
mod empty;
mod fragments;
mod global_css;
mod inline_styles;
mod iterators;
mod listener;
mod memo;
mod noderefs;
mod signals;
mod spreadpattern;
mod statemanagement;
mod suspense;
mod task;
mod testing;
mod tostring;
use super::*;
pub static REFERENCES: &[(FC<()>, &str, &str)] = &[
(
basics::Example,
"Basics",
include_str!("./reference/basics.rs"),
),
(
children::Example,
"Children",
include_str!("./reference/children.rs"),
),
(
conditional_rendering::Example,
"Conditional Rendering",
include_str!("./reference/conditional_rendering.rs"),
),
// TODO
(
controlled_inputs::Example,
"Controlled Inputs",
include_str!("./reference/controlled_inputs.rs"),
),
(
empty::Example,
"empty",
include_str!("./reference/empty.rs"),
),
(
custom_elements::Example,
"Custom Elements & Web Components",
include_str!("./reference/custom_elements.rs"),
),
(
fragments::Example,
"Fragments",
include_str!("./reference/fragments.rs"),
),
(
iterators::Example,
"Iterators",
include_str!("./reference/iterators.rs"),
),
(
global_css::Example,
"Global CSS",
include_str!("./reference/global_css.rs"),
),
(
inline_styles::Example,
"Inline Styles",
include_str!("./reference/inline_styles.rs"),
),
(
listener::Example,
"Listener",
include_str!("./reference/listener.rs"),
),
(memo::Example, "Memo", include_str!("./reference/memo.rs")),
(
spreadpattern::Example,
"Spread Pattern",
include_str!("./reference/spreadpattern.rs"),
),
(
suspense::Example,
"Suspense",
include_str!("./reference/suspense.rs"),
),
(task::Example, "Task", include_str!("./reference/task.rs")),
(
tostring::Example,
"Tostring",
include_str!("./reference/tostring.rs"),
),
(
antipatterns::Example,
"Anti-patterns",
include_str!("./reference/antipatterns.rs"),
),
/*
TODO! these showcase features that aren't quite ready
*/
(
signals::Example,
"Signals",
include_str!("./reference/signals.rs"),
),
(
noderefs::Example,
"NodeRefs",
include_str!("./reference/noderefs.rs"),
),
(
statemanagement::Example,
"State Management",
include_str!("./reference/statemanagement.rs"),
),
(
testing::Example,
"Testing",
include_str!("./reference/testing.rs"),
),
];
}
+9 -2
View File
@@ -7,15 +7,22 @@
use dioxus::prelude::*;
fn main() {
// dioxus::desktop::launch(App, |c| c);
dioxus::desktop::launch(App, |c| c);
}
const ENDPOINT: &str = "https://api.openweathermap.org/data/2.5/weather";
static App: FC<()> = |cx, props| {
//
let body = use_suspense(
cx,
|| async {
//
let content = reqwest::get(ENDPOINT)
.await
.unwrap()
.json::<serde_json::Value>()
.await
.unwrap();
},
|cx, props| {
//
+4
View File
@@ -1,4 +1,8 @@
//! Public APIs for managing component state, tasks, and lifecycles.
//!
//! This module is separate from `Scope` to narrow what exactly is exposed to user code.
//!
//! We unsafely implement `send` for the VirtualDom, but those guarantees can only be
use crate::innerlude::*;
use std::{any::TypeId, ops::Deref, rc::Rc};
+4 -3
View File
@@ -14,6 +14,7 @@ use std::{
fmt::Debug,
ops::Deref,
rc::Rc,
sync::Arc,
};
#[derive(Debug)]
@@ -216,13 +217,13 @@ pub mod on {
)* ) => {
$(
$(#[$attr])*
pub struct $wrapper(pub Rc<dyn $eventdata>);
pub struct $wrapper(pub Arc<dyn $eventdata>);
// todo: derefing to the event is fine (and easy) but breaks some IDE stuff like (go to source)
// going to source in fact takes you to the source of Rc which is... less than useful
// Either we ask for this to be changed in Rust-analyzer or manually impkement the trait
impl Deref for $wrapper {
type Target = Rc<dyn $eventdata>;
type Target = Arc<dyn $eventdata>;
fn deref(&self) -> &Self::Target {
&self.0
}
@@ -598,7 +599,7 @@ pub mod on {
];
}
pub struct GenericEvent(pub Rc<dyn GenericEventInner>);
pub struct GenericEvent(pub Arc<dyn GenericEventInner>);
pub trait GenericEventInner {
/// Return a reference to the raw event. User will need to downcast the event to the right platform-specific type.
+3 -1
View File
@@ -29,6 +29,7 @@ pub mod scheduler;
pub mod scope;
pub mod tasks;
pub mod test_dom;
pub mod threadsafe;
pub mod util;
pub mod virtual_dom;
@@ -50,6 +51,7 @@ pub(crate) mod innerlude {
pub use crate::scope::*;
pub use crate::tasks::*;
pub use crate::test_dom::*;
pub use crate::threadsafe::*;
pub use crate::util::*;
pub use crate::virtual_dom::*;
@@ -60,7 +62,7 @@ pub(crate) mod innerlude {
pub use crate::innerlude::{
Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority, LazyNodes, MountType,
Mutations, NodeFactory, Properties, ScopeId, SuspendedContext, SyntheticEvent, TaskHandle,
TestDom, UserEvent, VNode, VirtualDom, FC,
TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
};
pub mod prelude {
+1
View File
@@ -7,6 +7,7 @@ use std::{
future::Future,
pin::Pin,
rc::Rc,
sync::Arc,
};
/// Every component in Dioxus is represented by a `Scope`.
+58
View File
@@ -0,0 +1,58 @@
//! A threadsafe wrapper for the VirtualDom
use std::sync::{Arc, Mutex, MutexGuard};
use crate::VirtualDom;
/// A threadsafe wrapper for the Dioxus VirtualDom.
///
/// The Dioxus VirtualDom is not normally `Send` because user code can contain non-`Send` types. However, it is important
/// to have a VirtualDom that is `Send` when used in server-side code since very few web frameworks support non-send
/// handlers.
///
/// To address this, we have the `ThreadsafeVirtualDom` type which is a threadsafe wrapper for the VirtualDom. To access
/// the VirtualDom, it must be first unlocked using the `lock` method. This locks the VirtualDom through a mutex and
/// prevents any user code from leaking out. It is not possible to acquire any non-`Send` types from inside the VirtualDom.
///
/// The only way data may be accessed through the VirtualDom is from the "root props" method or by accessing a `Scope`
/// directly. Even then, it's not possible to access any hook data. This means that non-Send types are only "in play"
/// while the VirtualDom is locked with a non-Send marker.
///
/// Note that calling "wait for work" on the regular VirtualDom is inherently non-Send. If there are async tasks that
/// need to be awaited, they must be done thread-local since we don't place any requirements on user tasks. This can be
/// done with the function "spawn_local" in either tokio or async_std.
pub struct ThreadsafeVirtualDom {
inner: Arc<Mutex<VirtualDom>>,
}
impl ThreadsafeVirtualDom {
pub fn new(inner: VirtualDom) -> Self {
let inner = Arc::new(Mutex::new(inner));
Self { inner }
}
pub fn lock(&self) -> Option<VirtualDomGuard> {
let locked = self.inner.lock().unwrap();
Some(VirtualDomGuard { guard: locked })
}
}
unsafe impl Send for ThreadsafeVirtualDom {}
pub struct VirtualDomGuard<'a> {
guard: MutexGuard<'a, VirtualDom>,
}
impl<'a> std::ops::Deref for VirtualDomGuard<'a> {
type Target = MutexGuard<'a, VirtualDom>;
fn deref(&self) -> &Self::Target {
&self.guard
}
}
impl<'a> std::ops::DerefMut for VirtualDomGuard<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.guard
}
}
+60 -49
View File
@@ -20,8 +20,7 @@
//! Additional functionality is defined in the respective files.
use crate::innerlude::*;
use futures_util::Future;
use std::any::Any;
use std::{any::Any, rc::Rc, sync::Arc};
/// An integrated virtual node system that progresses events and diffs UI trees.
///
@@ -60,10 +59,10 @@ pub struct VirtualDom {
root_fc: Box<dyn Any>,
root_props: Box<dyn Any>,
root_props: Rc<dyn Any + Send>,
// we need to keep the allocation around, but we don't necessarily use it
_root_caller: Box<dyn for<'b> Fn(&'b Scope) -> DomTree<'b> + 'static>,
_root_caller: RootCaller,
}
impl VirtualDom {
@@ -121,16 +120,15 @@ impl VirtualDom {
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
/// let mutations = dom.rebuild();
/// ```
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
pub fn new_with_props<P: 'static + Send>(root: FC<P>, root_props: P) -> Self {
let root_fc = Box::new(root);
let root_props: Box<dyn Any> = Box::new(root_props);
let props_ptr = root_props.downcast_ref::<P>().unwrap() as *const P;
let root_props: Rc<dyn Any + Send> = Rc::new(root_props);
let _p = root_props.clone();
// Safety: this callback is only valid for the lifetime of the root props
let root_caller: Box<dyn Fn(&Scope) -> DomTree> = Box::new(move |scope: &Scope| unsafe {
let props: &'_ P = &*(props_ptr as *const P);
let root_caller: Rc<dyn Fn(&Scope) -> DomTree> = Rc::new(move |scope: &Scope| unsafe {
let props = _p.downcast_ref::<P>().unwrap();
std::mem::transmute(root(Context { scope }, props))
});
@@ -148,7 +146,7 @@ impl VirtualDom {
});
Self {
_root_caller: root_caller,
_root_caller: RootCaller(root_caller),
root_fc,
base_scope,
scheduler,
@@ -189,14 +187,14 @@ impl VirtualDom {
/// ```
pub fn update_root_props<P>(&mut self, root_props: P) -> Option<Mutations>
where
P: 'static,
P: 'static + Send,
{
let root_scope = self.scheduler.pool.get_scope_mut(self.base_scope).unwrap();
// Pre-emptively drop any downstream references of the old props
root_scope.ensure_drop_safety(&self.scheduler.pool);
let mut root_props: Box<dyn Any> = Box::new(root_props);
let mut root_props: Rc<dyn Any + Send> = Rc::new(root_props);
if let Some(props_ptr) = root_props.downcast_ref::<P>().map(|p| p as *const P) {
// Swap the old props and new props
@@ -345,46 +343,47 @@ impl VirtualDom {
/// Waits for the scheduler to have work
/// This lets us poll async tasks during idle periods without blocking the main thread.
pub async fn wait_for_work(&mut self) {
if self.scheduler.has_any_work() {
log::debug!("No need to wait for work, we already have some");
return;
}
todo!("making vdom send right now");
// if self.scheduler.has_any_work() {
// log::debug!("No need to wait for work, we already have some");
// return;
// }
log::debug!("No active work.... waiting for some...");
use futures_util::StreamExt;
// log::debug!("No active work.... waiting for some...");
// use futures_util::StreamExt;
// right now this won't poll events if there is ongoing work
// in the future we want to prioritize some events over ongoing work
// this is coming in the "priorities" PR
// // right now this won't poll events if there is ongoing work
// // in the future we want to prioritize some events over ongoing work
// // this is coming in the "priorities" PR
// Wait for any new events if we have nothing to do
// todo: poll the events once even if there is work to do to prevent starvation
futures_util::select! {
_ = self.scheduler.async_tasks.next() => {}
msg = self.scheduler.receiver.next() => {
match msg.unwrap() {
SchedulerMsg::Task(t) => todo!(),
SchedulerMsg::Immediate(im) => {
self.scheduler.dirty_scopes.insert(im);
}
SchedulerMsg::UiEvent(evt) => {
self.scheduler.ui_events.push_back(evt);
}
}
},
}
// // Wait for any new events if we have nothing to do
// // todo: poll the events once even if there is work to do to prevent starvation
// futures_util::select! {
// _ = self.scheduler.async_tasks.next() => {}
// msg = self.scheduler.receiver.next() => {
// match msg.unwrap() {
// SchedulerMsg::Task(t) => todo!(),
// SchedulerMsg::Immediate(im) => {
// self.scheduler.dirty_scopes.insert(im);
// }
// SchedulerMsg::UiEvent(evt) => {
// self.scheduler.ui_events.push_back(evt);
// }
// }
// },
// }
while let Ok(Some(msg)) = self.scheduler.receiver.try_next() {
match msg {
SchedulerMsg::Task(t) => todo!(),
SchedulerMsg::Immediate(im) => {
self.scheduler.dirty_scopes.insert(im);
}
SchedulerMsg::UiEvent(evt) => {
self.scheduler.ui_events.push_back(evt);
}
}
}
// while let Ok(Some(msg)) = self.scheduler.receiver.try_next() {
// match msg {
// SchedulerMsg::Task(t) => todo!(),
// SchedulerMsg::Immediate(im) => {
// self.scheduler.dirty_scopes.insert(im);
// }
// SchedulerMsg::UiEvent(evt) => {
// self.scheduler.ui_events.push_back(evt);
// }
// }
// }
}
}
@@ -407,3 +406,15 @@ impl std::fmt::Display for VirtualDom {
renderer.render(self, root, f, 0)
}
}
/*
Send safety...
The VirtualDom can only be "send" if the internals exposed to user code are also "send".
IE it's okay to move an Rc from one thread to another thread as long as it's only used internally
*/
// we never actually use the contents of this root caller
struct RootCaller(Rc<dyn for<'b> Fn(&'b Scope) -> DomTree<'b> + 'static>);
+1 -1
View File
@@ -11,7 +11,7 @@ use dioxus_html as dioxus_elements;
mod test_logging;
use DomEdit::*;
fn new_dom<P: Properties + 'static>(app: FC<P>, props: P) -> VirtualDom {
fn new_dom<P: 'static + Send>(app: FC<P>, props: P) -> VirtualDom {
const IS_LOGGING_ENABLED: bool = false;
test_logging::set_up_logging(IS_LOGGING_ENABLED);
VirtualDom::new_with_props(app, props)
+5 -7
View File
@@ -1,33 +1,31 @@
//! Tests for the lifecycle of components.
use std::{cell::RefCell, rc::Rc};
use anyhow::{Context, Result};
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
use std::sync::{Arc, Mutex};
mod test_logging;
const IS_LOGGING_ENABLED: bool = true;
type Shared<T> = Rc<RefCell<T>>;
type Shared<T> = Arc<Mutex<T>>;
#[test]
fn manual_diffing() {
test_logging::set_up_logging(IS_LOGGING_ENABLED);
#[derive(PartialEq, Props)]
struct AppProps {
value: Shared<&'static str>,
}
static App: FC<AppProps> = |cx, props| {
let val = props.value.borrow();
let val = props.value.lock().unwrap();
cx.render(rsx! { div { "{val}" } })
};
let value = Rc::new(RefCell::new("Hello"));
let value = Arc::new(Mutex::new("Hello"));
let mut dom = VirtualDom::new_with_props(
App,
AppProps {
@@ -37,7 +35,7 @@ fn manual_diffing() {
let _ = dom.rebuild();
*value.borrow_mut() = "goodbye";
*value.lock().unwrap() = "goodbye";
let edits = dom.diff();
+1 -1
View File
@@ -1,3 +1,3 @@
{
"rust-analyzer.inlayHints.enable": false
"rust-analyzer.inlayHints.enable": true
}
+2 -2
View File
@@ -20,14 +20,14 @@ log = "0.4.13"
fern = { version = "0.6.0", features = ["colored"] }
html-escape = "0.2.9"
wry = "0.11.0"
async-std = { version = "1.9.0", features = ["attributes"] }
tokio = { version = "1.12.0", features = ["full"] }
[dev-dependencies]
dioxus-html = { path = "../html" }
tide = "0.15.0"
tide-websockets = "0.3.0"
dioxus-core-macro = { path = "../core-macro" }
# thiserror = "1.0.23"
# log = "0.4.13"
# fern = { version = "0.6.0", features = ["colored"] }
+24
View File
@@ -0,0 +1,24 @@
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
fn main() {
std::thread::spawn(|| {
let mut vdom = VirtualDom::new(App);
let f = async_std::task::block_on(vdom.wait_for_work());
});
let a = 10;
// async_std::task::spawn_blocking(|| async move {
// });
}
static App: FC<()> = |cx, props| {
//
cx.render(rsx!(
div {
"hello world!"
}
))
};
-16
View File
@@ -1,16 +0,0 @@
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_html as dioxus_elements;
fn main() {
dioxus_desktop::launch(App, |f| f.with_window(|w| w.with_maximized(true))).expect("Failed");
}
static App: FC<()> = |cx, props|{
//
cx.render(rsx!(
div {
"hello world!"
}
))
};
+2 -1
View File
@@ -2,6 +2,7 @@
//!
use std::rc::Rc;
use std::sync::Arc;
use dioxus_core::{
events::{
@@ -21,7 +22,7 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
let mut data: Vec<ImEvent> = serde_json::from_value(val).unwrap();
let data = data.drain(..).next().unwrap();
let event = SyntheticEvent::MouseEvent(MouseEvent(Rc::new(WebviewMouseEvent)));
let event = SyntheticEvent::MouseEvent(MouseEvent(Arc::new(WebviewMouseEvent)));
let scope = ScopeId(data.scope as usize);
let mounted_dom_id = Some(ElementId(data.mounted_dom_id as usize));
UserEvent {
+75 -94
View File
@@ -1,5 +1,11 @@
//! Dioxus Desktop Renderer
//!
//! Render the Dioxus VirtualDom using the platform's native WebView implementation.
//!
use std::borrow::BorrowMut;
use std::ops::{Deref, DerefMut};
use std::sync::atomic::AtomicBool;
use std::sync::mpsc::channel;
use std::sync::{Arc, RwLock};
@@ -21,7 +27,6 @@ mod cfg;
mod dom;
mod escape;
mod events;
use events::*;
static HTML_CONTENT: &'static str = include_str!("./index.html");
@@ -31,20 +36,15 @@ pub fn launch(
) -> anyhow::Result<()> {
launch_with_props(root, (), config_builder)
}
pub fn launch_with_props<P: Properties + 'static>(
pub fn launch_with_props<P: Properties + 'static + Send + Sync>(
root: FC<P>,
props: P,
builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
) -> anyhow::Result<()> {
WebviewRenderer::run(root, props, builder)
run(root, props, builder)
}
/// The `WebviewRenderer` provides a way of rendering a Dioxus Virtual DOM through a bridge to a Webview instance.
/// Components used in WebviewRenderer instances can directly use system libraries, access the filesystem, and multithread with ease.
pub struct WebviewRenderer<T> {
/// The root component used to render the Webview
root: FC<T>,
}
enum RpcEvent<'a> {
Initialize { edits: Vec<DomEdit<'a>> },
}
@@ -55,74 +55,102 @@ struct Response<'a> {
edits: Vec<DomEdit<'a>>,
}
impl<T: Properties + 'static> WebviewRenderer<T> {
pub fn run(
pub fn run<T: Properties + 'static + Send + Sync>(
root: FC<T>,
props: T,
user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
) -> anyhow::Result<()> {
Self::run_with_edits(root, props, user_builder, None)
run_with_edits(root, props, user_builder, None)
}
pub fn run_with_edits<
F: for<'a, 'b> FnOnce(&'a mut DesktopConfig<'b>) -> &'a mut DesktopConfig<'b>,
T: Properties + 'static + Send + Sync,
>(
root: FC<T>,
props: T,
user_builder: F,
redits: Option<Vec<DomEdit<'static>>>,
) -> anyhow::Result<()> {
log::info!("hello edits");
let event_loop = EventLoop::new();
/*
*/
let mut cfg = DesktopConfig::new();
user_builder(&mut cfg);
let DesktopConfig {
window,
manual_edits,
pre_rendered,
} = cfg;
let event_loop = EventLoop::new();
let window = window.build(&event_loop)?;
let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel::<String>();
// Spawn the virtualdom onto its own thread
// if it wants to spawn multithreaded tasks, it can use the executor directly
std::thread::spawn(move || {
//
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(async move {
let mut vir = VirtualDom::new_with_props(root, props);
let channel = vir.get_event_sender();
struct WebviewBridge {}
// impl RealDom for WebviewBridge {
// fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
// todo!()
// }
loop {
vir.wait_for_work().await;
let edits = vir.run_with_deadline(|| false);
let edit_string = serde_json::to_string(&edits[0].edits).unwrap();
event_tx.send(edit_string).unwrap();
}
})
});
// fn must_commit(&self) -> bool {
// false
// }
// fn commit_edits<'a>(&mut self, edits: &mut Vec<DomEdit<'a>>) {}
// fn wait_until_ready<'s>(
// &'s mut self,
// ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + 's>> {
// //
// Box::pin(async {
// //
// })
// }
// }
let mut real_dom = WebviewBridge {};
// async_std::task::spawn_local(vir.run(&mut real_dom));
// todo: combine these or something
let vdom = Arc::new(RwLock::new(vir));
// let registry = Arc::new(RwLock::new(Some(WebviewRegistry::new())));
let dioxus_requsted = Arc::new(AtomicBool::new(false));
let webview = WebViewBuilder::new(window)?
.with_url(&format!("data:text/html,{}", HTML_CONTENT))?
// .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
// match req.method.as_str() {
// "initiate" => {
.with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
match req.method.as_str() {
"initiate" => {}
"user_event" => {}
_ => todo!("this message failed"),
}
todo!()
})
.build()?;
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event, window_id, ..
} => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(_) | WindowEvent::Moved(_) => {
let _ = webview.resize();
}
_ => {}
},
Event::MainEventsCleared => {
webview.resize();
// window.request_redraw();
}
_ => {}
}
});
Ok(())
}
// let edits = if let Some(edits) = &redits {
// serde_json::to_value(edits).unwrap()
// } else {
@@ -157,8 +185,7 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
// };
// // Return the edits into the webview runtime
// Some(RpcResponse::new_result(req.id.take(), Some(edits)))
// }
// "user_event" => {
// log::debug!("User event received");
// // let registry = registry.clone();
// let vdom = vdom.clone();
@@ -190,49 +217,3 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
// });
// response
// // spawn a task to clean up the garbage
// }
// _ => todo!("this message failed"),
// }
// })
.build()?;
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event, window_id, ..
} => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(_) | WindowEvent::Moved(_) => {
let _ = webview.resize();
}
_ => {}
},
Event::MainEventsCleared => {
webview.resize();
// window.request_redraw();
}
_ => {}
}
});
}
/// Create a new text renderer from an existing Virtual DOM.
/// This will progress the existing VDom's events to completion.
pub fn from_vdom() -> Self {
todo!()
}
/// Pass new args to the root function
pub fn update(&mut self, _new_val: T) {
todo!()
}
/// Modify the root function in place, forcing a re-render regardless if the props changed
pub fn update_mut(&mut self, _modifier: impl Fn(&mut T)) {
todo!()
}
}
+41 -40
View File
@@ -8,53 +8,54 @@ use dioxus_core_macro::*;
use dioxus_hooks::use_state;
use dioxus_html as dioxus_elements;
use tide::{Request, Response};
fn main() {}
// use tide::{Request, Response};
#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
let mut app = tide::new();
// #[async_std::main]
// async fn main() -> Result<(), std::io::Error> {
// let mut app = tide::new();
app.at("/:name").get(|req: Request<()>| async move {
let initial_name: String = req
.param("name")
.map(|f| f.parse().unwrap_or("...?".to_string()))
.unwrap_or("...?".to_string());
// app.at("/:name").get(|req: Request<()>| async move {
// let initial_name: String = req
// .param("name")
// .map(|f| f.parse().unwrap_or("...?".to_string()))
// .unwrap_or("...?".to_string());
let mut dom = VirtualDom::new_with_props(Example, ExampleProps { initial_name });
// let mut dom = VirtualDom::new_with_props(Example, ExampleProps { initial_name });
dom.rebuild();
// dom.rebuild();
Ok(Response::builder(200)
.body(format!("{}", dioxus_ssr::render_vdom(&dom, |c| c)))
.content_type(tide::http::mime::HTML)
.build())
});
// Ok(Response::builder(200)
// .body(format!("{}", dioxus_ssr::render_vdom(&dom, |c| c)))
// .content_type(tide::http::mime::HTML)
// .build())
// });
println!("Server available at [http://127.0.0.1:8080/bill]");
app.listen("127.0.0.1:8080").await?;
// println!("Server available at [http://127.0.0.1:8080/bill]");
// app.listen("127.0.0.1:8080").await?;
Ok(())
}
// Ok(())
// }
#[derive(PartialEq, Props)]
struct ExampleProps {
initial_name: String,
}
// #[derive(PartialEq, Props)]
// struct ExampleProps {
// initial_name: String,
// }
static Example: FC<ExampleProps> = |cx, props| {
let dispaly_name = use_state(cx, move || props.initial_name.clone());
// static Example: FC<ExampleProps> = |cx, props| {
// let dispaly_name = use_state(cx, move || props.initial_name.clone());
cx.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}"
}
ul {
{(0..10).map(|f| rsx!( li {"Element {f}"} ))}
}
}
})
};
// cx.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}"
// }
// ul {
// {(0..10).map(|f| rsx!( li {"Element {f}"} ))}
// }
// }
// })
// };
+20 -18
View File
@@ -14,7 +14,7 @@ use dioxus_core::{
DomEdit, ElementId, ScopeId,
};
use fxhash::FxHashMap;
use std::{fmt::Debug, rc::Rc};
use std::{fmt::Debug, rc::Rc, sync::Arc};
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{
Attr, CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement,
@@ -471,77 +471,79 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> SyntheticEvent {
use dioxus_core::events::on::*;
match event.type_().as_str() {
"copy" | "cut" | "paste" => {
SyntheticEvent::ClipboardEvent(ClipboardEvent(Rc::new(WebsysClipboardEvent(event))))
SyntheticEvent::ClipboardEvent(ClipboardEvent(Arc::new(WebsysClipboardEvent(event))))
}
"compositionend" | "compositionstart" | "compositionupdate" => {
let evt: web_sys::CompositionEvent = event.dyn_into().unwrap();
SyntheticEvent::CompositionEvent(CompositionEvent(Rc::new(WebsysCompositionEvent(evt))))
SyntheticEvent::CompositionEvent(CompositionEvent(Arc::new(WebsysCompositionEvent(
evt,
))))
}
"keydown" | "keypress" | "keyup" => {
let evt: web_sys::KeyboardEvent = event.dyn_into().unwrap();
SyntheticEvent::KeyboardEvent(KeyboardEvent(Rc::new(WebsysKeyboardEvent(evt))))
SyntheticEvent::KeyboardEvent(KeyboardEvent(Arc::new(WebsysKeyboardEvent(evt))))
}
"focus" | "blur" => {
let evt: web_sys::FocusEvent = event.dyn_into().unwrap();
SyntheticEvent::FocusEvent(FocusEvent(Rc::new(WebsysFocusEvent(evt))))
SyntheticEvent::FocusEvent(FocusEvent(Arc::new(WebsysFocusEvent(evt))))
}
"change" => {
let evt = event.dyn_into().unwrap();
SyntheticEvent::GenericEvent(GenericEvent(Rc::new(WebsysGenericUiEvent(evt))))
SyntheticEvent::GenericEvent(GenericEvent(Arc::new(WebsysGenericUiEvent(evt))))
}
"input" | "invalid" | "reset" | "submit" => {
let evt: web_sys::Event = event.dyn_into().unwrap();
SyntheticEvent::FormEvent(FormEvent(Rc::new(WebsysFormEvent(evt))))
SyntheticEvent::FormEvent(FormEvent(Arc::new(WebsysFormEvent(evt))))
}
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
| "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
| "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
let evt: web_sys::MouseEvent = event.dyn_into().unwrap();
SyntheticEvent::MouseEvent(MouseEvent(Rc::new(WebsysMouseEvent(evt))))
SyntheticEvent::MouseEvent(MouseEvent(Arc::new(WebsysMouseEvent(evt))))
}
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
let evt: web_sys::PointerEvent = event.dyn_into().unwrap();
SyntheticEvent::PointerEvent(PointerEvent(Rc::new(WebsysPointerEvent(evt))))
SyntheticEvent::PointerEvent(PointerEvent(Arc::new(WebsysPointerEvent(evt))))
}
"select" => {
let evt: web_sys::UiEvent = event.dyn_into().unwrap();
SyntheticEvent::SelectionEvent(SelectionEvent(Rc::new(WebsysGenericUiEvent(evt))))
SyntheticEvent::SelectionEvent(SelectionEvent(Arc::new(WebsysGenericUiEvent(evt))))
}
"touchcancel" | "touchend" | "touchmove" | "touchstart" => {
let evt: web_sys::TouchEvent = event.dyn_into().unwrap();
SyntheticEvent::TouchEvent(TouchEvent(Rc::new(WebsysTouchEvent(evt))))
SyntheticEvent::TouchEvent(TouchEvent(Arc::new(WebsysTouchEvent(evt))))
}
"scroll" => {
let evt: web_sys::UiEvent = event.dyn_into().unwrap();
SyntheticEvent::GenericEvent(GenericEvent(Rc::new(WebsysGenericUiEvent(evt))))
SyntheticEvent::GenericEvent(GenericEvent(Arc::new(WebsysGenericUiEvent(evt))))
}
"wheel" => {
let evt: web_sys::WheelEvent = event.dyn_into().unwrap();
SyntheticEvent::WheelEvent(WheelEvent(Rc::new(WebsysWheelEvent(evt))))
SyntheticEvent::WheelEvent(WheelEvent(Arc::new(WebsysWheelEvent(evt))))
}
"animationstart" | "animationend" | "animationiteration" => {
let evt: web_sys::AnimationEvent = event.dyn_into().unwrap();
SyntheticEvent::AnimationEvent(AnimationEvent(Rc::new(WebsysAnimationEvent(evt))))
SyntheticEvent::AnimationEvent(AnimationEvent(Arc::new(WebsysAnimationEvent(evt))))
}
"transitionend" => {
let evt: web_sys::TransitionEvent = event.dyn_into().unwrap();
SyntheticEvent::TransitionEvent(TransitionEvent(Rc::new(WebsysTransitionEvent(evt))))
SyntheticEvent::TransitionEvent(TransitionEvent(Arc::new(WebsysTransitionEvent(evt))))
}
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
| "timeupdate" | "volumechange" | "waiting" => {
let evt: web_sys::UiEvent = event.dyn_into().unwrap();
SyntheticEvent::MediaEvent(MediaEvent(Rc::new(WebsysMediaEvent(evt))))
SyntheticEvent::MediaEvent(MediaEvent(Arc::new(WebsysMediaEvent(evt))))
}
"toggle" => {
let evt: web_sys::UiEvent = event.dyn_into().unwrap();
SyntheticEvent::ToggleEvent(ToggleEvent(Rc::new(WebsysToggleEvent(evt))))
SyntheticEvent::ToggleEvent(ToggleEvent(Arc::new(WebsysToggleEvent(evt))))
}
_ => {
let evt: web_sys::UiEvent = event.dyn_into().unwrap();
SyntheticEvent::GenericEvent(GenericEvent(Rc::new(WebsysGenericUiEvent(evt))))
SyntheticEvent::GenericEvent(GenericEvent(Arc::new(WebsysGenericUiEvent(evt))))
}
}
}
+1 -1
View File
@@ -193,7 +193,7 @@ pub mod debug {}
pub mod prelude {
//! A glob import that includes helper types like FC, rsx!, html!, and required traits
pub use dioxus_core::prelude::*;
use dioxus_core_macro::{format_args_f, html, rsx, Props};
pub use dioxus_core_macro::{format_args_f, html, rsx, Props};
pub use dioxus_elements::{GlobalAttributes, SvgAttributes};
pub use dioxus_hooks::*;
pub use dioxus_html as dioxus_elements;