Refactor and fix eval channels (#2302)
* wip * pull out eval into the interpreter * fix web eval * fix DioxusChannel name * properly drop dioxus channel * use typescript dioxus chanel in desktop * add more comments to native eval * add desktop headless eval tests * expand web playwright eval tests * fix web headless tests * fix default hasher path * run eval tests on windows * restore desktop query drop code * remove data from drop desktop query message * catch syntax errors in desktop eval * catch js runtime errors in desktop * fix typo interprerter -> interpreter --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
1 parent
1d72ef16c4
commit
cbeda0af76
23 files changed
+477
-218
No files matched your search
@@ -3,7 +3,7 @@ use generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};
|
||||
|
||||
use crate::{query::Query, DesktopContext};
|
||||
|
||||
/// Reprents the desktop-target's provider of evaluators.
|
||||
/// Represents the desktop-target's provider of evaluators.
|
||||
pub struct DesktopEvalProvider {
|
||||
pub(crate) desktop_ctx: DesktopContext,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{assets::*, edits::EditQueue};
|
||||
use dioxus_interpreter_js::eval::NATIVE_EVAL_JS;
|
||||
use dioxus_interpreter_js::unified_bindings::SLEDGEHAMMER_JS;
|
||||
use dioxus_interpreter_js::NATIVE_JS;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -196,7 +197,7 @@ fn module_loader(root_id: &str, headless: bool) -> String {
|
||||
// And then extend it with our native bindings
|
||||
{NATIVE_JS}
|
||||
|
||||
// The nativeinterprerter extends the sledgehammer interpreter with a few extra methods that we use for IPC
|
||||
// The native interpreter extends the sledgehammer interpreter with a few extra methods that we use for IPC
|
||||
window.interpreter = new NativeInterpreter("{EDITS_PATH}");
|
||||
|
||||
// Wait for the page to load before sending the initialize message
|
||||
@@ -209,6 +210,10 @@ fn module_loader(root_id: &str, headless: bool) -> String {
|
||||
window.interpreter.waitForRequest({headless});
|
||||
}}
|
||||
</script>
|
||||
<script type="module">
|
||||
// Include the code for eval
|
||||
{NATIVE_EVAL_JS}
|
||||
</script>
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
+78
-113
@@ -7,45 +7,6 @@ use slab::Slab;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use thiserror::Error;
|
||||
|
||||
/*
|
||||
todo:
|
||||
- write this in the interpreter itself rather than in blobs of inline javascript...
|
||||
- it could also be simpler, probably?
|
||||
*/
|
||||
const DIOXUS_CODE: &str = r#"
|
||||
let dioxus = {
|
||||
recv: function () {
|
||||
return new Promise((resolve, _reject) => {
|
||||
// Ever 50 ms check for new data
|
||||
let timeout = setTimeout(() => {
|
||||
let __msg = null;
|
||||
while (true) {
|
||||
let __data = _message_queue.shift();
|
||||
if (__data) {
|
||||
__msg = __data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
clearTimeout(timeout);
|
||||
resolve(__msg);
|
||||
}, 50);
|
||||
});
|
||||
},
|
||||
|
||||
send: function (value) {
|
||||
window.ipc.postMessage(
|
||||
JSON.stringify({
|
||||
"method":"query",
|
||||
"params": {
|
||||
"id": _request_id,
|
||||
"data": value,
|
||||
"returned_value": false
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}"#;
|
||||
|
||||
/// Tracks what query ids are currently active
|
||||
pub(crate) struct SharedSlab<T = ()> {
|
||||
pub slab: Rc<RefCell<Slab<T>>>,
|
||||
@@ -69,12 +30,10 @@ impl<T> Default for SharedSlab<T> {
|
||||
|
||||
pub(crate) struct QueryEntry {
|
||||
channel_sender: futures_channel::mpsc::UnboundedSender<Value>,
|
||||
return_sender: Option<futures_channel::oneshot::Sender<Value>>,
|
||||
return_sender: Option<futures_channel::oneshot::Sender<Result<Value, String>>>,
|
||||
pub owner: Option<Owner>,
|
||||
}
|
||||
|
||||
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, Default)]
|
||||
pub(crate) struct QueryEngine {
|
||||
@@ -100,40 +59,51 @@ impl QueryEngine {
|
||||
// We embed the return of the eval in a function so we can send it back to the main thread
|
||||
if let Err(err) = context.webview.evaluate_script(&format!(
|
||||
r#"(function(){{
|
||||
(async (resolve, _reject) => {{
|
||||
{DIOXUS_CODE}
|
||||
if (!window.{QUEUE_NAME}) {{
|
||||
window.{QUEUE_NAME} = [];
|
||||
}}
|
||||
|
||||
let _request_id = {request_id};
|
||||
|
||||
if (!window.{QUEUE_NAME}[{request_id}]) {{
|
||||
window.{QUEUE_NAME}[{request_id}] = [];
|
||||
}}
|
||||
let _message_queue = window.{QUEUE_NAME}[{request_id}];
|
||||
|
||||
{script}
|
||||
}})().then((result)=>{{
|
||||
let dioxus = window.createQuery({request_id});
|
||||
let post_error = function(err) {{
|
||||
let returned_value = {{
|
||||
"method":"query",
|
||||
"method": "query",
|
||||
"params": {{
|
||||
"id": {request_id},
|
||||
"data": result,
|
||||
"returned_value": true
|
||||
"data": {{
|
||||
"data": err,
|
||||
"method": "return_error"
|
||||
}}
|
||||
}}
|
||||
}};
|
||||
window.ipc.postMessage(
|
||||
JSON.stringify(returned_value)
|
||||
);
|
||||
}})
|
||||
}};
|
||||
try {{
|
||||
const AsyncFunction = async function () {{}}.constructor;
|
||||
let promise = (new AsyncFunction("dioxus", {script:?}))(dioxus);
|
||||
promise
|
||||
.then((result)=>{{
|
||||
let returned_value = {{
|
||||
"method": "query",
|
||||
"params": {{
|
||||
"id": {request_id},
|
||||
"data": {{
|
||||
"data": result,
|
||||
"method": "return"
|
||||
}}
|
||||
}}
|
||||
}};
|
||||
window.ipc.postMessage(
|
||||
JSON.stringify(returned_value)
|
||||
);
|
||||
}})
|
||||
.catch(err => post_error(`Error running JS: ${{err}}`));
|
||||
}} catch (error) {{
|
||||
post_error(`Invalid JS: ${{error}}`);
|
||||
}}
|
||||
}})();"#
|
||||
)) {
|
||||
tracing::warn!("Query error: {err}");
|
||||
}
|
||||
|
||||
Query {
|
||||
slab: self.active_requests.clone(),
|
||||
id: request_id,
|
||||
receiver: rx,
|
||||
return_receiver: Some(return_rx),
|
||||
@@ -144,19 +114,26 @@ impl QueryEngine {
|
||||
|
||||
/// Send a query channel message to the correct query
|
||||
pub fn send(&self, data: QueryResult) {
|
||||
let QueryResult {
|
||||
id,
|
||||
data,
|
||||
returned_value,
|
||||
} = data;
|
||||
let QueryResult { id, data } = data;
|
||||
let mut slab = self.active_requests.slab.borrow_mut();
|
||||
if let Some(entry) = slab.get_mut(id) {
|
||||
if returned_value {
|
||||
if let Some(sender) = entry.return_sender.take() {
|
||||
let _ = sender.send(data);
|
||||
match data {
|
||||
QueryResultData::Return { data } => {
|
||||
if let Some(sender) = entry.return_sender.take() {
|
||||
let _ = sender.send(Ok(data.unwrap_or_default()));
|
||||
}
|
||||
}
|
||||
QueryResultData::ReturnError { data } => {
|
||||
if let Some(sender) = entry.return_sender.take() {
|
||||
let _ = sender.send(Err(data.to_string()));
|
||||
}
|
||||
}
|
||||
QueryResultData::Drop => {
|
||||
slab.remove(id);
|
||||
}
|
||||
QueryResultData::Send { data } => {
|
||||
let _ = entry.channel_sender.unbounded_send(data);
|
||||
}
|
||||
} else {
|
||||
let _ = entry.channel_sender.unbounded_send(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,9 +141,8 @@ impl QueryEngine {
|
||||
|
||||
pub(crate) struct Query<V: DeserializeOwned> {
|
||||
desktop: DesktopContext,
|
||||
slab: SharedSlab<QueryEntry>,
|
||||
receiver: futures_channel::mpsc::UnboundedReceiver<Value>,
|
||||
return_receiver: Option<futures_channel::oneshot::Receiver<Value>>,
|
||||
return_receiver: Option<futures_channel::oneshot::Receiver<Result<Value, String>>>,
|
||||
pub id: usize,
|
||||
phantom: std::marker::PhantomData<V>,
|
||||
}
|
||||
@@ -183,18 +159,7 @@ impl<V: DeserializeOwned> Query<V> {
|
||||
let queue_id = self.id;
|
||||
|
||||
let data = message.to_string();
|
||||
let script = format!(
|
||||
r#"
|
||||
if (!window.{QUEUE_NAME}) {{
|
||||
window.{QUEUE_NAME} = [];
|
||||
}}
|
||||
|
||||
if (!window.{QUEUE_NAME}[{queue_id}]) {{
|
||||
window.{QUEUE_NAME}[{queue_id}] = [];
|
||||
}}
|
||||
window.{QUEUE_NAME}[{queue_id}].push({data});
|
||||
"#
|
||||
);
|
||||
let script = format!(r#"window.getQuery({queue_id}).rustSend({data});"#);
|
||||
|
||||
self.desktop
|
||||
.webview
|
||||
@@ -211,13 +176,17 @@ impl<V: DeserializeOwned> Query<V> {
|
||||
) -> std::task::Poll<Result<Value, QueryError>> {
|
||||
self.receiver
|
||||
.poll_next_unpin(cx)
|
||||
.map(|result| result.ok_or(QueryError::Recv))
|
||||
.map(|result| result.ok_or(QueryError::Recv(String::from("Receive channel closed"))))
|
||||
}
|
||||
|
||||
/// Receive the result of the query
|
||||
pub async fn result(&mut self) -> Result<Value, QueryError> {
|
||||
match self.return_receiver.take() {
|
||||
Some(receiver) => receiver.await.map_err(|_| QueryError::Recv),
|
||||
Some(receiver) => match receiver.await {
|
||||
Ok(Ok(data)) => Ok(data),
|
||||
Ok(Err(err)) => Err(QueryError::Recv(err)),
|
||||
Err(err) => Err(QueryError::Recv(err.to_string())),
|
||||
},
|
||||
None => Err(QueryError::Finished),
|
||||
}
|
||||
}
|
||||
@@ -228,36 +197,21 @@ impl<V: DeserializeOwned> Query<V> {
|
||||
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),
|
||||
Some(receiver) => receiver.poll_unpin(cx).map(|result| match result {
|
||||
Ok(Ok(data)) => Ok(data),
|
||||
Ok(Err(err)) => Err(QueryError::Recv(err)),
|
||||
Err(err) => Err(QueryError::Recv(err.to_string())),
|
||||
}),
|
||||
None => std::task::Poll::Ready(Err(QueryError::Finished)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: DeserializeOwned> Drop for Query<V> {
|
||||
fn drop(&mut self) {
|
||||
self.slab.slab.borrow_mut().remove(self.id);
|
||||
let queue_id = self.id;
|
||||
|
||||
_ = self.desktop.webview.evaluate_script(&format!(
|
||||
r#"
|
||||
if (!window.{QUEUE_NAME}) {{
|
||||
window.{QUEUE_NAME} = [];
|
||||
}}
|
||||
|
||||
if (window.{QUEUE_NAME}[{queue_id}]) {{
|
||||
window.{QUEUE_NAME}[{queue_id}] = [];
|
||||
}}
|
||||
"#
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum QueryError {
|
||||
#[error("Error receiving query result.")]
|
||||
Recv,
|
||||
#[error("Error receiving query result: {0}")]
|
||||
Recv(String),
|
||||
#[error("Error sending message to query: {0}")]
|
||||
Send(String),
|
||||
#[error("Error deserializing query result: {0}")]
|
||||
@@ -269,7 +223,18 @@ pub enum QueryError {
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub(crate) struct QueryResult {
|
||||
id: usize,
|
||||
data: Value,
|
||||
#[serde(default)]
|
||||
returned_value: bool,
|
||||
data: QueryResultData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(tag = "method")]
|
||||
enum QueryResultData {
|
||||
#[serde(rename = "return")]
|
||||
Return { data: Option<Value> },
|
||||
#[serde(rename = "return_error")]
|
||||
ReturnError { data: Value },
|
||||
#[serde(rename = "send")]
|
||||
Send { data: Value },
|
||||
#[serde(rename = "drop")]
|
||||
Drop,
|
||||
}
|
||||
Reference in new issue
Block a user