Merge pull request #1791 from ealmloff/breaking

Remove scope, use_state, use_ref, bump allocator and make everything 'static
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2024-02-05 14:19:47 -08:00
commit 4f8868d43d
517 files changed
+17126 -20669

No files matched your search

+69 -2
View File
@@ -1,5 +1,15 @@
use crate::{LiveViewError, LiveViewSocket};
use axum::extract::ws::{Message, WebSocket};
use std::sync::Arc;
use crate::{interpreter_glue, LiveViewError, LiveViewSocket, LiveviewRouter};
use axum::{
extract::{
ws::{Message, WebSocket},
WebSocketUpgrade,
},
response::Html,
routing::*,
Router,
};
use futures_util::{SinkExt, StreamExt};
/// Convert an Axum WebSocket into a `LiveViewSocket`.
@@ -22,3 +32,60 @@ fn transform_rx(message: Result<Message, axum::Error>) -> Result<Vec<u8>, LiveVi
async fn transform_tx(message: Vec<u8>) -> Result<Message, axum::Error> {
Ok(Message::Binary(message))
}
impl LiveviewRouter for Router {
fn create_default_liveview_router() -> Self {
Router::new()
}
fn with_virtual_dom(
self,
route: &str,
app: impl Fn() -> dioxus_core::prelude::VirtualDom + Send + Sync + 'static,
) -> Self {
let view = crate::LiveViewPool::new();
let ws_path = format!("{}/ws", route);
let title = crate::app_title();
let index_page_with_glue = move |glue: &str| {
Html(format!(
r#"
<!DOCTYPE html>
<html>
<head> <title>{title}</title> </head>
<body> <div id="main"></div> </body>
{glue}
</html>
"#,
))
};
let app = Arc::new(app);
self.route(
&ws_path,
get(move |ws: WebSocketUpgrade| async move {
let app = app.clone();
ws.on_upgrade(move |socket| async move {
_ = view
.launch_virtualdom(axum_socket(socket), move || app())
.await;
})
}),
)
.route(
route,
get(move || async move { index_page_with_glue(&interpreter_glue(&ws_path)) }),
)
}
async fn start(self, address: impl Into<std::net::SocketAddr>) {
if let Err(err) = axum::Server::bind(&address.into())
.serve(self.into_make_service())
.await
{
eprintln!("Failed to start axum server: {}", err);
}
}
}
+47
View File
@@ -0,0 +1,47 @@
use std::future::Future;
use dioxus_core::{Element, VirtualDom};
#[cfg(feature = "warp")]
pub mod warp_adapter;
#[cfg(feature = "warp")]
pub use warp_adapter::*;
#[cfg(feature = "axum")]
pub mod axum_adapter;
#[cfg(feature = "axum")]
pub use axum_adapter::*;
#[cfg(feature = "salvo")]
pub mod salvo_adapter;
#[cfg(feature = "salvo")]
pub use salvo_adapter::*;
#[cfg(feature = "rocket")]
pub mod rocket_adapter;
#[cfg(feature = "rocket")]
pub use rocket_adapter::*;
/// A trait for servers that can be used to host a LiveView app.
pub trait LiveviewRouter {
/// Create a new router.
fn create_default_liveview_router() -> Self;
/// Add a liveview route to the server from a component
fn with_app(self, route: &str, app: fn() -> Element) -> Self
where
Self: Sized,
{
self.with_virtual_dom(route, move || VirtualDom::new(app))
}
/// Add a liveview route to the server from a virtual dom.
fn with_virtual_dom(
self,
route: &str,
app: impl Fn() -> VirtualDom + Send + Sync + 'static,
) -> Self;
/// Start the server on an address.
fn start(self, address: impl Into<std::net::SocketAddr>) -> impl Future<Output = ()>;
}
@@ -1,6 +1,13 @@
use crate::LiveViewPool;
use crate::LiveviewRouter;
use crate::{LiveViewError, LiveViewSocket};
use rocket::futures::{SinkExt, StreamExt};
use rocket::response::content::RawHtml;
use rocket::{get, routes, State};
use rocket_ws::Channel;
use rocket_ws::WebSocket;
use rocket_ws::{result::Error, stream::DuplexStream, Message};
use std::sync::Arc;
/// Convert a Rocket WebSocket into a `LiveViewSocket`.
///
@@ -21,5 +28,86 @@ fn transform_rx(message: Result<Message, Error>) -> Result<Vec<u8>, LiveViewErro
}
async fn transform_tx(message: Vec<u8>) -> Result<Message, Error> {
Ok(Message::Text(String::from_utf8_lossy(&message).to_string()))
Ok(Message::Binary(message))
}
impl LiveviewRouter for rocket::Rocket<rocket::Build> {
fn create_default_liveview_router() -> Self {
Self::build()
}
fn with_virtual_dom(
self,
route: &str,
app: impl Fn() -> dioxus_core::prelude::VirtualDom + Send + Sync + 'static,
) -> Self {
#[get("/")]
async fn index(request: &rocket::route::Route) -> RawHtml<String> {
let route = request.uri.base();
let glue = crate::interpreter_glue(&format!("{route}/ws",));
let title = crate::app_title();
RawHtml(format!(
r#"
<!DOCTYPE html>
<html>
<head> <title>{title}</title> </head>
<body> <div id="main"></div> </body>
{glue}
</html>
"#
))
}
#[get("/ws")]
fn ws(ws: WebSocket, app: &State<LiveviewApp>) -> Channel<'static> {
let app = app.inner();
let pool = app.pool.clone();
let app = app.app.clone();
ws.channel(move |stream| {
Box::pin(async move {
let _ = pool
.launch_virtualdom(crate::rocket_socket(stream), move || app())
.await;
Ok(())
})
})
}
struct LiveviewApp {
app: Arc<dyn Fn() -> dioxus_core::prelude::VirtualDom + Send + Sync + 'static>,
pool: LiveViewPool,
}
let app = Arc::new(app);
let view = crate::LiveViewPool::new();
self.manage(LiveviewApp {
app: app,
pool: view,
})
.mount(route, routes![index, ws])
}
async fn start(self, address: impl Into<std::net::SocketAddr>) {
let address = address.into();
let figment = self
.figment()
.clone()
.merge((rocket::Config::PORT, address.port()))
.merge((rocket::Config::ADDRESS, address.ip()));
self.configure(figment)
.ignite()
.await
.expect("Failed to ignite rocket")
.launch()
.await
.expect("Failed to launch rocket");
}
}
@@ -1,7 +1,16 @@
use futures_util::{SinkExt, StreamExt};
use salvo::ws::{Message, WebSocket};
use crate::LiveViewPool;
use crate::LiveviewRouter;
use crate::{LiveViewError, LiveViewSocket};
use futures_util::{SinkExt, StreamExt};
use salvo::conn::TcpListener;
use salvo::http::StatusError;
use salvo::websocket::WebSocketUpgrade;
use salvo::websocket::{Message, WebSocket};
use salvo::writing::Text;
use salvo::Listener;
use salvo::Server;
use salvo::{handler, Depot, Request, Response, Router};
use std::sync::Arc;
/// Convert a Salvo WebSocket into a `LiveViewSocket`.
///
@@ -19,5 +28,81 @@ fn transform_rx(message: Result<Message, salvo::Error>) -> Result<Vec<u8>, LiveV
}
async fn transform_tx(message: Vec<u8>) -> Result<Message, salvo::Error> {
Ok(Message::text(String::from_utf8_lossy(&message).to_string()))
Ok(Message::binary(message))
}
#[derive(Clone)]
struct LiveviewApp {
app: Arc<dyn Fn() -> dioxus_core::prelude::VirtualDom + Send + Sync + 'static>,
pool: Arc<LiveViewPool>,
}
impl LiveviewRouter for Router {
fn create_default_liveview_router() -> Self {
Self::new()
}
fn with_virtual_dom(
self,
route: &str,
app: impl Fn() -> dioxus_core::prelude::VirtualDom + Send + Sync + 'static,
) -> Self {
let app = Arc::new(app);
let view = crate::LiveViewPool::new();
self.push(
Router::with_path(route)
.hoop(salvo::affix::inject(LiveviewApp {
app: app,
pool: Arc::new(view),
}))
.get(index)
.push(Router::with_path("ws").get(connect)),
)
}
async fn start(self, address: impl Into<std::net::SocketAddr>) {
let address = address.into();
let acceptor = TcpListener::new(address).bind().await;
Server::new(acceptor).serve(self).await;
}
}
#[handler]
fn index(req: &mut Request, res: &mut Response) {
let base = req.uri().path();
let title = crate::app_title();
res.render(Text::Html(format!(
r#"
<!DOCTYPE html>
<html>
<head> <title>{title}</title> </head>
<body> <div id="main"></div> </body>
{glue}
</html>
"#,
glue = crate::interpreter_glue(&format!("{base}/ws"))
)));
}
#[handler]
async fn connect(
req: &mut Request,
depot: &mut Depot,
res: &mut Response,
) -> Result<(), StatusError> {
let app = depot.obtain::<LiveviewApp>().unwrap().clone();
let view = app.pool.clone();
let app = app.app.clone();
WebSocketUpgrade::new()
.upgrade(req, res, |ws| async move {
_ = view
.launch_virtualdom(crate::salvo_socket(ws), move || app())
.await;
})
.await
}
@@ -21,5 +21,5 @@ fn transform_rx(message: Result<Message, warp::Error>) -> Result<Vec<u8>, LiveVi
}
async fn transform_tx(message: Vec<u8>) -> Result<Message, warp::Error> {
Ok(Message::text(String::from_utf8_lossy(&message).to_string()))
Ok(Message::binary(message))
}
+63
View File
@@ -0,0 +1,63 @@
use dioxus_cli_config::CURRENT_CONFIG;
use dioxus_core::VirtualDom;
use crate::LiveviewRouter;
pub(crate) fn app_title() -> String {
CURRENT_CONFIG
.as_ref()
.map(|c| c.dioxus_config.web.app.title.clone())
.unwrap_or_else(|_| "Dioxus Liveview App".to_string())
}
/// A configuration for the LiveView server.
pub struct Config<R: LiveviewRouter> {
router: R,
address: std::net::SocketAddr,
route: String,
}
impl<R: LiveviewRouter> Default for Config<R> {
fn default() -> Self {
Self {
router: R::create_default_liveview_router(),
address: ([127, 0, 0, 1], 8080).into(),
route: "/".to_string(),
}
}
}
impl<R: LiveviewRouter> Config<R> {
/// Set the route to use for the LiveView server.
pub fn route(mut self, route: impl Into<String>) -> Self {
self.route = route.into();
self
}
/// Create a new configuration for the LiveView server.
pub fn with_app(mut self, app: fn() -> dioxus_core::prelude::Element) -> Self {
self.router = self.router.with_app(&self.route, app);
self
}
/// Create a new configuration for the LiveView server.
pub fn with_virtual_dom(
mut self,
virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
) -> Self {
self.router = self.router.with_virtual_dom(&self.route, virtual_dom);
self
}
/// Set the address to listen on.
pub fn address(mut self, address: impl Into<std::net::SocketAddr>) -> Self {
self.address = address.into();
self
}
/// Launch the LiveView server.
pub async fn launch(self) {
println!("{} started on http://{}", app_title(), self.address);
self.router.start(self.address).await
}
}
+29 -27
View File
@@ -1,61 +1,62 @@
#![allow(clippy::await_holding_refcell_ref)]
use async_trait::async_trait;
use dioxus_core::ScopeState;
use dioxus_core::ScopeId;
use dioxus_html::prelude::{EvalError, EvalProvider, Evaluator};
use std::{cell::RefCell, rc::Rc};
use generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};
use std::rc::Rc;
use crate::query::{Query, QueryEngine};
/// Provides the DesktopEvalProvider through [`cx.provide_context`].
pub fn init_eval(cx: &ScopeState) {
let query = cx.consume_context::<QueryEngine>().unwrap();
pub fn init_eval() {
let query = ScopeId::ROOT.consume_context::<QueryEngine>().unwrap();
let provider: Rc<dyn EvalProvider> = Rc::new(DesktopEvalProvider { query });
cx.provide_context(provider);
ScopeId::ROOT.provide_context(provider);
}
/// Reprents the desktop-target's provider of evaluators.
/// Reprints the desktop-target's provider of evaluators.
pub struct DesktopEvalProvider {
query: QueryEngine,
}
impl EvalProvider for DesktopEvalProvider {
fn new_evaluator(&self, js: String) -> Result<Rc<dyn Evaluator>, EvalError> {
Ok(Rc::new(DesktopEvaluator::new(self.query.clone(), js)))
fn new_evaluator(&self, js: String) -> Result<GenerationalBox<Box<dyn Evaluator>>, EvalError> {
Ok(DesktopEvaluator::create(self.query.clone(), js))
}
}
/// Reprents a desktop-target's JavaScript evaluator.
pub(crate) struct DesktopEvaluator {
query: Rc<RefCell<Query<serde_json::Value>>>,
query: Query<serde_json::Value>,
}
impl DesktopEvaluator {
/// Creates a new evaluator for desktop-based targets.
pub fn new(query: QueryEngine, js: String) -> Self {
let query = query.new_query(&js);
pub fn create(query_engine: QueryEngine, js: String) -> GenerationalBox<Box<dyn Evaluator>> {
let query = query_engine.new_query(&js);
// We create a generational box that is owned by the query slot so that when we drop the query slot, the generational box is also dropped.
let owner = UnsyncStorage::owner();
let query_id = query.id;
let query = owner.insert(Box::new(DesktopEvaluator { query }) as Box<dyn Evaluator>);
query_engine.active_requests.slab.borrow_mut()[query_id].owner = Some(owner);
Self {
query: Rc::new(RefCell::new(query)),
}
query
}
}
#[async_trait(?Send)]
impl Evaluator for DesktopEvaluator {
/// # Panics
/// This will panic if the query is currently being awaited.
async fn join(&self) -> Result<serde_json::Value, EvalError> {
fn poll_join(
&mut self,
context: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<serde_json::Value, EvalError>> {
self.query
.borrow_mut()
.result()
.await
.poll_result(context)
.map_err(|e| EvalError::Communication(e.to_string()))
}
/// Sends a message to the evaluated JavaScript.
fn send(&self, data: serde_json::Value) -> Result<(), EvalError> {
if let Err(e) = self.query.borrow_mut().send(data) {
if let Err(e) = self.query.send(data) {
return Err(EvalError::Communication(e.to_string()));
}
Ok(())
@@ -65,11 +66,12 @@ impl Evaluator for DesktopEvaluator {
///
/// # Panics
/// This will panic if the query is currently being awaited.
async fn recv(&self) -> Result<serde_json::Value, EvalError> {
fn poll_recv(
&mut self,
context: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<serde_json::Value, EvalError>> {
self.query
.borrow_mut()
.recv()
.await
.poll_recv(context)
.map_err(|e| EvalError::Communication(e.to_string()))
}
}
+35
View File
@@ -0,0 +1,35 @@
use dioxus_core::*;
use std::any::Any;
#[cfg(feature = "axum")]
pub type Config = crate::Config<axum::Router>;
#[cfg(feature = "salvo")]
pub type Config = crate::Config<salvo::Router>;
#[cfg(feature = "rocket")]
pub type Config = crate::Config<rocket::Rocket<rocket::Build>>;
/// Launches the WebView and runs the event loop, with configuration and root props.
pub fn launch(
root: fn() -> Element,
contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
platform_config: Config,
) {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async move {
platform_config
.with_virtual_dom(move || {
let mut virtual_dom = VirtualDom::new(root);
for context in &contexts {
virtual_dom.insert_any_root_context(context());
}
virtual_dom
})
.launch()
.await;
});
}
+69 -25
View File
@@ -2,29 +2,8 @@
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
pub mod adapters {
#[cfg(feature = "warp")]
pub mod warp_adapter;
#[cfg(feature = "warp")]
pub use warp_adapter::*;
#[cfg(feature = "axum")]
pub mod axum_adapter;
#[cfg(feature = "axum")]
pub use axum_adapter::*;
#[cfg(feature = "salvo")]
pub mod salvo_adapter;
#[cfg(feature = "salvo")]
pub use salvo_adapter::*;
#[cfg(feature = "rocket")]
pub mod rocket_adapter;
#[cfg(feature = "rocket")]
pub use rocket_adapter::*;
}
mod adapters;
#[allow(unused_imports)]
pub use adapters::*;
mod element;
@@ -32,8 +11,11 @@ pub mod pool;
mod query;
use futures_util::{SinkExt, StreamExt};
pub use pool::*;
mod config;
mod eval;
mod events;
pub use config::*;
pub mod launch;
pub trait WebsocketTx: SinkExt<String, Error = LiveViewError> {}
impl<T> WebsocketTx for T where T: SinkExt<String, Error = LiveViewError> {}
@@ -47,7 +29,67 @@ pub enum LiveViewError {
SendingFailed,
}
static MINIFIED: &str = include_str!("./minified.js");
fn handle_edits_code() -> String {
use dioxus_interpreter_js::binary_protocol::SLEDGEHAMMER_JS;
use minify_js::{minify, Session, TopLevelMode};
let serialize_file_uploads = r#"if (
target.tagName === "INPUT" &&
(event.type === "change" || event.type === "input")
) {
const type = target.getAttribute("type");
if (type === "file") {
async function read_files() {
const files = target.files;
const file_contents = {};
for (let i = 0; i < files.length; i++) {
const file = files[i];
file_contents[file.name] = Array.from(
new Uint8Array(await file.arrayBuffer())
);
}
let file_engine = {
files: file_contents,
};
contents.files = file_engine;
if (realId === null) {
return;
}
const message = window.interpreter.serializeIpcMessage("user_event", {
name: name,
element: parseInt(realId),
data: contents,
bubbles,
});
window.ipc.postMessage(message);
}
read_files();
return;
}
}"#;
let mut interpreter = SLEDGEHAMMER_JS
.replace("/*POST_EVENT_SERIALIZATION*/", serialize_file_uploads)
.replace("export", "");
while let Some(import_start) = interpreter.find("import") {
let import_end = interpreter[import_start..]
.find(|c| c == ';' || c == '\n')
.map(|i| i + import_start)
.unwrap_or_else(|| interpreter.len());
interpreter.replace_range(import_start..import_end, "");
}
let main_js = include_str!("./main.js");
let js = format!("{interpreter}\n{main_js}");
let session = Session::new();
let mut out = Vec::new();
minify(&session, TopLevelMode::Module, js.as_bytes(), &mut out).unwrap();
String::from_utf8(out).unwrap()
}
/// This script that gets injected into your app connects this page to the websocket endpoint
///
@@ -85,6 +127,8 @@ pub fn interpreter_glue(url_or_path: &str) -> String {
"return path;"
};
let handle_edits = handle_edits_code();
format!(
r#"
<script>
@@ -93,7 +137,7 @@ pub fn interpreter_glue(url_or_path: &str) -> String {
}}
var WS_ADDR = __dioxusGetWsUrl("{url_or_path}");
{MINIFIED}
{handle_edits}
</script>
"#
)
+26 -155
View File
@@ -5,11 +5,10 @@ use crate::{
query::{QueryEngine, QueryResult},
LiveViewError,
};
use dioxus_core::{prelude::*, BorrowedAttributeValue, Mutations};
use dioxus_html::{event_bubbles, EventData, HtmlEvent, PlatformEventData};
use dioxus_interpreter_js::binary_protocol::Channel;
use dioxus_core::prelude::*;
use dioxus_html::{EventData, HtmlEvent, PlatformEventData};
use dioxus_interpreter_js::MutationState;
use futures_util::{pin_mut, SinkExt, StreamExt};
use rustc_hash::FxHashMap;
use serde::Serialize;
use std::{rc::Rc, time::Duration};
use tokio_util::task::LocalPoolHandle;
@@ -38,15 +37,15 @@ impl LiveViewPool {
pub async fn launch(
&self,
ws: impl LiveViewSocket,
app: fn(Scope<()>) -> Element,
app: fn() -> Element,
) -> Result<(), LiveViewError> {
self.launch_with_props(ws, app, ()).await
self.launch_with_props(ws, |app| app(), app).await
}
pub async fn launch_with_props<T: Send + 'static>(
pub async fn launch_with_props<T: Clone + Send + 'static>(
&self,
ws: impl LiveViewSocket,
app: fn(Scope<T>) -> Element,
app: fn(T) -> Element,
props: T,
) -> Result<(), LiveViewError> {
self.launch_virtualdom(ws, move || VirtualDom::new_with_props(app, props))
@@ -126,27 +125,22 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
rx
};
let mut templates: FxHashMap<String, u16> = Default::default();
let mut max_template_count = 0;
let mut mutations = MutationState::default();
// Create the a proxy for query engine
let (query_tx, mut query_rx) = tokio::sync::mpsc::unbounded_channel();
let query_engine = QueryEngine::new(query_tx);
vdom.base_scope().provide_context(query_engine.clone());
init_eval(vdom.base_scope());
vdom.in_runtime(|| {
ScopeId::ROOT.provide_context(query_engine.clone());
init_eval();
});
// pin the futures so we can use select!
pin_mut!(ws);
let mut edit_channel = Channel::default();
if let Some(edits) = {
let mutations = vdom.rebuild();
apply_edits(
mutations,
&mut edit_channel,
&mut templates,
&mut max_template_count,
)
vdom.rebuild(&mut mutations);
take_edits(&mut mutations)
} {
// send the initial render to the client
ws.send(edits).await?;
@@ -233,18 +227,16 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
}
}
let edits = vdom
.render_with_deadline(tokio::time::sleep(Duration::from_millis(10)))
.await;
// wait for suspense to resolve in a 10ms window
tokio::select! {
_ = tokio::time::sleep(Duration::from_millis(10)) => {}
_ = vdom.wait_for_suspense() => {}
}
if let Some(edits) = {
apply_edits(
edits,
&mut edit_channel,
&mut templates,
&mut max_template_count,
)
} {
// render the vdom
vdom.render_immediate(&mut mutations);
if let Some(edits) = take_edits(&mut mutations) {
ws.send(edits).await?;
}
}
@@ -256,132 +248,11 @@ fn text_frame(text: &str) -> Vec<u8> {
bytes
}
fn add_template(
template: &Template<'static>,
channel: &mut Channel,
templates: &mut FxHashMap<String, u16>,
max_template_count: &mut u16,
) {
for root in template.roots.iter() {
create_template_node(channel, root);
templates.insert(template.name.to_owned(), *max_template_count);
}
channel.add_templates(*max_template_count, template.roots.len() as u16);
*max_template_count += 1
}
fn create_template_node(channel: &mut Channel, v: &'static TemplateNode<'static>) {
use TemplateNode::*;
match v {
Element {
tag,
namespace,
attrs,
children,
..
} => {
// Push the current node onto the stack
match namespace {
Some(ns) => channel.create_element_ns(tag, ns),
None => channel.create_element(tag),
}
// Set attributes on the current node
for attr in *attrs {
if let TemplateAttribute::Static {
name,
value,
namespace,
} = attr
{
channel.set_top_attribute(name, value, namespace.unwrap_or_default())
}
}
// Add each child to the stack
for child in *children {
create_template_node(channel, child);
}
// Add all children to the parent
channel.append_children_to_top(children.len() as u16);
}
Text { text } => channel.create_raw_text(text),
DynamicText { .. } => channel.create_raw_text("p"),
Dynamic { .. } => channel.add_placeholder(),
}
}
fn apply_edits(
mutations: Mutations,
channel: &mut Channel,
templates: &mut FxHashMap<String, u16>,
max_template_count: &mut u16,
) -> Option<Vec<u8>> {
use dioxus_core::Mutation::*;
if mutations.templates.is_empty() && mutations.edits.is_empty() {
return None;
}
for template in mutations.templates {
add_template(&template, channel, templates, max_template_count);
}
for edit in mutations.edits {
match edit {
AppendChildren { id, m } => channel.append_children(id.0 as u32, m as u16),
AssignId { path, id } => channel.assign_id(path, id.0 as u32),
CreatePlaceholder { id } => channel.create_placeholder(id.0 as u32),
CreateTextNode { value, id } => channel.create_text_node(value, id.0 as u32),
HydrateText { path, value, id } => channel.hydrate_text(path, value, id.0 as u32),
LoadTemplate { name, index, id } => {
if let Some(tmpl_id) = templates.get(name) {
channel.load_template(*tmpl_id, index as u16, id.0 as u32)
}
}
ReplaceWith { id, m } => channel.replace_with(id.0 as u32, m as u16),
ReplacePlaceholder { path, m } => channel.replace_placeholder(path, m as u16),
InsertAfter { id, m } => channel.insert_after(id.0 as u32, m as u16),
InsertBefore { id, m } => channel.insert_before(id.0 as u32, m as u16),
SetAttribute {
name,
value,
id,
ns,
} => match value {
BorrowedAttributeValue::Text(txt) => {
channel.set_attribute(id.0 as u32, name, txt, ns.unwrap_or_default())
}
BorrowedAttributeValue::Float(f) => {
channel.set_attribute(id.0 as u32, name, &f.to_string(), ns.unwrap_or_default())
}
BorrowedAttributeValue::Int(n) => {
channel.set_attribute(id.0 as u32, name, &n.to_string(), ns.unwrap_or_default())
}
BorrowedAttributeValue::Bool(b) => channel.set_attribute(
id.0 as u32,
name,
if b { "true" } else { "false" },
ns.unwrap_or_default(),
),
BorrowedAttributeValue::None => {
channel.remove_attribute(id.0 as u32, name, ns.unwrap_or_default())
}
_ => unreachable!(),
},
SetText { value, id } => channel.set_text(id.0 as u32, value),
NewEventListener { name, id, .. } => {
channel.new_event_listener(name, id.0 as u32, event_bubbles(name) as u8)
}
RemoveEventListener { name, id } => {
channel.remove_event_listener(name, id.0 as u32, event_bubbles(name) as u8)
}
Remove { id } => channel.remove(id.0 as u32),
PushRoot { id } => channel.push_root(id.0 as u32),
}
}
fn take_edits(mutations: &mut MutationState) -> Option<Vec<u8>> {
// Add an extra one at the beginning to tell the shim this is a binary frame
let mut bytes = vec![1];
bytes.extend(channel.export_memory());
channel.reset();
Some(bytes)
mutations.write_memory_into(&mut bytes);
(bytes.len() > 1).then_some(bytes)
}
#[derive(Serialize)]
+29 -10
View File
@@ -1,5 +1,7 @@
use std::{cell::RefCell, rc::Rc};
use futures_util::FutureExt;
use generational_box::{Owner, UnsyncStorage};
use serde::{de::DeserializeOwned, Deserialize};
use serde_json::Value;
use slab::Slab;
@@ -42,8 +44,8 @@ let dioxus = {
/// Tracks what query ids are currently active
struct SharedSlab<T = ()> {
slab: Rc<RefCell<Slab<T>>>,
pub(crate) struct SharedSlab<T = ()> {
pub(crate) slab: Rc<RefCell<Slab<T>>>,
}
impl<T> Clone for SharedSlab<T> {
@@ -62,9 +64,10 @@ impl<T> Default for SharedSlab<T> {
}
}
struct QueryEntry {
pub(crate) struct QueryEntry {
channel_sender: tokio::sync::mpsc::UnboundedSender<Value>,
return_sender: Option<tokio::sync::oneshot::Sender<Value>>,
pub(crate) owner: Option<Owner<UnsyncStorage>>,
}
const QUEUE_NAME: &str = "__msg_queues";
@@ -72,7 +75,7 @@ const QUEUE_NAME: &str = "__msg_queues";
/// Handles sending and receiving arbitrary queries from the webview. Queries can be resolved non-sequentially, so we use ids to track them.
#[derive(Clone)]
pub(crate) struct QueryEngine {
active_requests: SharedSlab<QueryEntry>,
pub(crate) active_requests: SharedSlab<QueryEntry>,
query_tx: tokio::sync::mpsc::UnboundedSender<String>,
}
@@ -91,6 +94,7 @@ impl QueryEngine {
let request_id = self.active_requests.slab.borrow_mut().insert(QueryEntry {
channel_sender: tx,
return_sender: Some(return_tx),
owner: None,
});
// start the query
@@ -162,7 +166,7 @@ pub(crate) struct Query<V: DeserializeOwned> {
query_engine: QueryEngine,
pub receiver: tokio::sync::mpsc::UnboundedReceiver<Value>,
pub return_receiver: Option<tokio::sync::oneshot::Receiver<Value>>,
id: usize,
pub id: usize,
phantom: std::marker::PhantomData<V>,
}
@@ -198,12 +202,14 @@ impl<V: DeserializeOwned> Query<V> {
Ok(())
}
/// Receive a message from the query
pub async fn recv(&mut self) -> Result<Value, QueryError> {
/// Poll the query for a message
pub fn poll_recv(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<Value, QueryError>> {
self.receiver
.recv()
.await
.ok_or(QueryError::Recv(RecvError::Closed))
.poll_recv(cx)
.map(|result| result.ok_or(QueryError::Recv(RecvError::Closed)))
}
/// Receive the result of the query
@@ -215,6 +221,19 @@ impl<V: DeserializeOwned> Query<V> {
None => Err(QueryError::Finished),
}
}
/// Poll the query for a result
pub fn poll_result(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<Value, QueryError>> {
match self.return_receiver.as_mut() {
Some(receiver) => receiver
.poll_unpin(cx)
.map_err(|_| QueryError::Recv(RecvError::Closed)),
None => std::task::Poll::Ready(Err(QueryError::Finished)),
}
}
}
impl<V: DeserializeOwned> Drop for Query<V> {