implement for web and desktop

This commit is contained in:
= 2022-12-15 21:16:37 -06:00
parent ab743e01b7
commit 0d9c350d5e
8 changed files with 69 additions and 30 deletions

View file

@ -102,7 +102,7 @@ impl<'a> VNode<'a> {
/// ///
/// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of /// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
/// ways, with the suggested approach being the unique code location (file, line, col, etc). /// ways, with the suggested approach being the unique code location (file, line, col, etc).
#[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
pub struct Template<'a> { pub struct Template<'a> {
/// The name of the template. This must be unique across your entire program for template diffing to work properly /// The name of the template. This must be unique across your entire program for template diffing to work properly
@ -113,26 +113,47 @@ pub struct Template<'a> {
/// The list of template nodes that make up the template /// The list of template nodes that make up the template
/// ///
/// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm. /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
pub roots: &'a [TemplateNode<'a>], pub roots: &'a [TemplateNode<'a>],
/// The paths of each node relative to the root of the template. /// The paths of each node relative to the root of the template.
/// ///
/// These will be one segment shorter than the path sent to the renderer since those paths are relative to the /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
/// topmost element, not the `roots` field. /// topmost element, not the `roots` field.
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
pub node_paths: &'a [&'a [u8]], pub node_paths: &'a [&'a [u8]],
/// The paths of each dynamic attribute relative to the root of the template /// The paths of each dynamic attribute relative to the root of the template
/// ///
/// These will be one segment shorter than the path sent to the renderer since those paths are relative to the /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
/// topmost element, not the `roots` field. /// topmost element, not the `roots` field.
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
pub attr_paths: &'a [&'a [u8]], pub attr_paths: &'a [&'a [u8]],
} }
#[cfg(feature = "serialize")]
fn deserialize_leaky<'a, 'de, T: serde::Deserialize<'de>, D>(
deserializer: D,
) -> Result<&'a [T], D::Error>
where
T: serde::Deserialize<'de>,
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
let deserialized = Box::<[T]>::deserialize(deserializer)?;
Ok(&*Box::leak(deserialized))
}
/// A statically known node in a layout. /// A statically known node in a layout.
/// ///
/// This can be created at compile time, saving the VirtualDom time when diffing the tree /// This can be created at compile time, saving the VirtualDom time when diffing the tree
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize), serde(tag = "type"))] #[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type")
)]
pub enum TemplateNode<'a> { pub enum TemplateNode<'a> {
/// An statically known element in the dom. /// An statically known element in the dom.
/// ///
@ -152,9 +173,11 @@ pub enum TemplateNode<'a> {
/// A list of possibly dynamic attribues for this element /// A list of possibly dynamic attribues for this element
/// ///
/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`. /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
attrs: &'a [TemplateAttribute<'a>], attrs: &'a [TemplateAttribute<'a>],
/// A list of template nodes that define another set of template nodes /// A list of template nodes that define another set of template nodes
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
children: &'a [TemplateNode<'a>], children: &'a [TemplateNode<'a>],
}, },

View file

@ -1,7 +1,7 @@
use crate::desktop_context::{DesktopContext, UserWindowEvent}; use crate::desktop_context::{DesktopContext, UserWindowEvent};
use crate::events::{decode_event, EventMessage}; use crate::events::{decode_event, EventMessage};
use dioxus_core::*; use dioxus_core::*;
use futures_channel::mpsc::{unbounded, UnboundedSender}; use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures_util::StreamExt; use futures_util::StreamExt;
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
use objc::runtime::Object; use objc::runtime::Object;
@ -24,6 +24,8 @@ pub(super) struct DesktopController {
pub(super) is_ready: Arc<AtomicBool>, pub(super) is_ready: Arc<AtomicBool>,
pub(super) proxy: EventLoopProxy<UserWindowEvent>, pub(super) proxy: EventLoopProxy<UserWindowEvent>,
pub(super) event_tx: UnboundedSender<serde_json::Value>, pub(super) event_tx: UnboundedSender<serde_json::Value>,
#[cfg(debug_assertions)]
pub(super) templates_tx: UnboundedSender<Template<'static>>,
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
pub(super) views: Vec<*mut Object>, pub(super) views: Vec<*mut Object>,
@ -39,6 +41,7 @@ impl DesktopController {
) -> Self { ) -> Self {
let edit_queue = Arc::new(Mutex::new(Vec::new())); let edit_queue = Arc::new(Mutex::new(Vec::new()));
let (event_tx, mut event_rx) = unbounded(); let (event_tx, mut event_rx) = unbounded();
let (templates_tx, mut templates_rx) = unbounded();
let proxy2 = proxy.clone(); let proxy2 = proxy.clone();
let pending_edits = edit_queue.clone(); let pending_edits = edit_queue.clone();
@ -64,6 +67,18 @@ impl DesktopController {
loop { loop {
tokio::select! { tokio::select! {
template = {
#[allow(unused)]
fn maybe_future<'a>(templates_rx: &'a mut UnboundedReceiver<Template<'static>>) -> impl Future<Output = dioxus_core::Template<'static>> + 'a {
#[cfg(debug_assertions)]
return templates_rx.select_next_some();
#[cfg(not(debug_assertions))]
return std::future::pending();
}
maybe_future(&mut templates_rx)
} => {
dom.replace_template(template);
}
_ = dom.wait_for_work() => {} _ = dom.wait_for_work() => {}
Some(json_value) = event_rx.next() => { Some(json_value) = event_rx.next() => {
if let Ok(value) = serde_json::from_value::<EventMessage>(json_value) { if let Ok(value) = serde_json::from_value::<EventMessage>(json_value) {
@ -93,6 +108,8 @@ impl DesktopController {
quit_app_on_close: true, quit_app_on_close: true,
proxy: proxy2, proxy: proxy2,
event_tx, event_tx,
#[cfg(debug_assertions)]
templates_tx,
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
views: vec![], views: vec![],
} }
@ -123,8 +140,4 @@ impl DesktopController {
} }
} }
} }
pub(crate) fn set_template(&self, _serialized_template: String) {
todo!("hot reloading currently WIP")
}
} }

View file

@ -171,11 +171,6 @@ pub enum UserWindowEvent {
DragWindow, DragWindow,
FocusWindow, FocusWindow,
/// Set a new Dioxus template for hot-reloading
///
/// Is a no-op in release builds. Must fit the right format for templates
SetTemplate(String),
Visible(bool), Visible(bool),
Minimize(bool), Minimize(bool),
Maximize(bool), Maximize(bool),
@ -223,7 +218,6 @@ impl DesktopController {
match user_event { match user_event {
Initialize | EditsReady => self.try_load_ready_webviews(), Initialize | EditsReady => self.try_load_ready_webviews(),
SetTemplate(template) => self.set_template(template),
CloseWindow => *control_flow = ControlFlow::Exit, CloseWindow => *control_flow = ControlFlow::Exit,
DragWindow => { DragWindow => {
// if the drag_window has any errors, we don't do anything // if the drag_window has any errors, we don't do anything

View file

@ -1,6 +1,6 @@
#![allow(dead_code)] #![allow(dead_code)]
use dioxus_core::VirtualDom; use dioxus_core::Template;
use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
@ -13,7 +13,7 @@ fn handle_error(connection: std::io::Result<LocalSocketStream>) -> Option<LocalS
.ok() .ok()
} }
pub(crate) fn init(_dom: &VirtualDom) { pub(crate) fn init(proxy: futures_channel::mpsc::UnboundedSender<Template<'static>>) {
let latest_in_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> = let latest_in_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> =
Arc::new(Mutex::new(None)); Arc::new(Mutex::new(None));
@ -36,11 +36,9 @@ pub(crate) fn init(_dom: &VirtualDom) {
let mut buf = String::new(); let mut buf = String::new();
match conn.read_line(&mut buf) { match conn.read_line(&mut buf) {
Ok(_) => { Ok(_) => {
todo!() let msg: Template<'static> =
// let msg: SetTemplateMsg = serde_json::from_str(&buf).unwrap(); serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap();
// channel proxy.unbounded_send(msg).unwrap();
// .start_send(SchedulerMsg::SetTemplate(Box::new(msg)))
// .unwrap();
} }
Err(err) => { Err(err) => {
if err.kind() != std::io::ErrorKind::WouldBlock { if err.kind() != std::io::ErrorKind::WouldBlock {

View file

@ -106,6 +106,9 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
let event_loop = EventLoop::with_user_event(); let event_loop = EventLoop::with_user_event();
let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy()); let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
#[cfg(debug_assertions)]
hot_reload::init(desktop.templates_tx.clone());
event_loop.run(move |window_event, event_loop, control_flow| { event_loop.run(move |window_event, event_loop, control_flow| {
*control_flow = ControlFlow::Wait; *control_flow = ControlFlow::Wait;

View file

@ -42,7 +42,7 @@ fn create_rows(c: &mut Criterion) {
c.bench_function("create rows", |b| { c.bench_function("create rows", |b| {
let mut dom = VirtualDom::new(app); let mut dom = VirtualDom::new(app);
dom.rebuild(); let _ = dom.rebuild();
b.iter(|| { b.iter(|| {
let g = dom.rebuild(); let g = dom.rebuild();

View file

@ -2,12 +2,13 @@
use futures_channel::mpsc::UnboundedReceiver; use futures_channel::mpsc::UnboundedReceiver;
use dioxus_core::Template;
use wasm_bindgen::closure::Closure; use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use web_sys::{MessageEvent, WebSocket}; use web_sys::{MessageEvent, WebSocket};
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
pub(crate) fn init() -> UnboundedReceiver<String> { pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
let (tx, rx) = futures_channel::mpsc::unbounded(); let (tx, rx) = futures_channel::mpsc::unbounded();
std::mem::forget(tx); std::mem::forget(tx);
@ -16,7 +17,7 @@ pub(crate) fn init() -> UnboundedReceiver<String> {
} }
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
pub(crate) fn init() -> UnboundedReceiver<String> { pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
use std::convert::TryInto; use std::convert::TryInto;
let window = web_sys::window().unwrap(); let window = web_sys::window().unwrap();
@ -39,8 +40,11 @@ pub(crate) fn init() -> UnboundedReceiver<String> {
// change the rsx when new data is received // change the rsx when new data is received
let cl = Closure::wrap(Box::new(move |e: MessageEvent| { let cl = Closure::wrap(Box::new(move |e: MessageEvent| {
if let Ok(text) = e.data().dyn_into::<js_sys::JsString>() { if let Ok(text) = e.data().dyn_into::<js_sys::JsString>() {
if let Ok(val) = text.try_into() { let text: Result<String, _> = text.try_into();
_ = tx.unbounded_send(val); if let Ok(string) = text {
if let Ok(template) = serde_json::from_str(Box::leak(string.into_boxed_str())) {
_ = tx.unbounded_send(template);
}
} }
} }
}) as Box<dyn FnMut(MessageEvent)>); }) as Box<dyn FnMut(MessageEvent)>);

View file

@ -202,19 +202,23 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
// if virtualdom has nothing, wait for it to have something before requesting idle time // if virtualdom has nothing, wait for it to have something before requesting idle time
// if there is work then this future resolves immediately. // if there is work then this future resolves immediately.
let mut res = { let (mut res, template) = {
let work = dom.wait_for_work().fuse(); let work = dom.wait_for_work().fuse();
pin_mut!(work); pin_mut!(work);
futures_util::select! { futures_util::select! {
_ = work => None, _ = work => (None, None),
_new_template = hotreload_rx.next() => { new_template = hotreload_rx.next() => {
todo!("Implement hot reload"); (None, new_template)
} }
evt = rx.next() => evt evt = rx.next() => (evt, None)
} }
}; };
if let Some(template) = template {
dom.replace_template(template);
}
// Dequeue all of the events from the channel in send order // Dequeue all of the events from the channel in send order
// todo: we should re-order these if possible // todo: we should re-order these if possible
while let Some(evt) = res { while let Some(evt) = res {