2023-08-24 15:34:51 -07:00
|
|
|
//! Functions for setting up our WebSocket and WebRTC connections for communications with the
|
|
|
|
//! engine.
|
2025-02-18 13:50:13 -08:00
|
|
|
use std::{collections::HashMap, sync::Arc};
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
|
|
use anyhow::Result;
|
2024-09-19 14:06:29 -07:00
|
|
|
use indexmap::IndexMap;
|
2025-02-19 00:11:11 -05:00
|
|
|
use kcmc::websocket::{WebSocketRequest, WebSocketResponse};
|
2024-09-18 17:04:04 -05:00
|
|
|
use kittycad_modeling_cmds as kcmc;
|
2025-02-18 13:50:13 -08:00
|
|
|
use tokio::sync::RwLock;
|
2025-01-08 20:02:30 -05:00
|
|
|
use uuid::Uuid;
|
2023-08-24 15:34:51 -07:00
|
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
|
2024-04-15 17:18:32 -07:00
|
|
|
use crate::{
|
2025-04-16 11:52:14 -07:00
|
|
|
engine::EngineStats,
|
2024-04-15 17:18:32 -07:00
|
|
|
errors::{KclError, KclErrorDetails},
|
2025-01-08 20:02:30 -05:00
|
|
|
execution::{ArtifactCommand, DefaultPlanes, IdGenerator},
|
2024-12-03 16:39:51 +13:00
|
|
|
SourceRange,
|
2024-04-15 17:18:32 -07:00
|
|
|
};
|
2023-08-24 15:34:51 -07:00
|
|
|
|
2025-03-01 13:59:01 -08:00
|
|
|
#[wasm_bindgen(module = "/../../src/lang/std/engineConnection.ts")]
|
2023-08-24 15:34:51 -07:00
|
|
|
extern "C" {
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub type EngineCommandManager;
|
|
|
|
|
2024-02-12 12:18:37 -08:00
|
|
|
#[wasm_bindgen(method, js_name = sendModelingCommandFromWasm, catch)]
|
|
|
|
fn send_modeling_cmd_from_wasm(
|
2023-08-24 15:34:51 -07:00
|
|
|
this: &EngineCommandManager,
|
|
|
|
id: String,
|
|
|
|
rangeStr: String,
|
|
|
|
cmdStr: String,
|
2024-03-23 15:45:55 -07:00
|
|
|
idToRangeStr: String,
|
2024-02-12 12:18:37 -08:00
|
|
|
) -> Result<js_sys::Promise, js_sys::Error>;
|
2024-04-15 17:18:32 -07:00
|
|
|
|
|
|
|
#[wasm_bindgen(method, js_name = startNewSession, catch)]
|
|
|
|
fn start_new_session(this: &EngineCommandManager) -> Result<js_sys::Promise, js_sys::Error>;
|
2023-08-24 15:34:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct EngineConnection {
|
2023-09-20 18:27:08 -07:00
|
|
|
manager: Arc<EngineCommandManager>,
|
2025-02-18 13:50:13 -08:00
|
|
|
batch: Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>,
|
|
|
|
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
|
|
|
|
responses: Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>,
|
|
|
|
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
|
2025-03-15 10:08:39 -07:00
|
|
|
/// The default planes for the scene.
|
|
|
|
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
|
2025-03-18 16:19:24 +13:00
|
|
|
stats: EngineStats,
|
2023-08-24 15:34:51 -07:00
|
|
|
}
|
|
|
|
|
2024-03-12 13:37:47 -07:00
|
|
|
// Safety: WebAssembly will only ever run in a single-threaded context.
|
|
|
|
unsafe impl Send for EngineConnection {}
|
|
|
|
unsafe impl Sync for EngineConnection {}
|
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
impl EngineConnection {
|
|
|
|
pub async fn new(manager: EngineCommandManager) -> Result<EngineConnection, JsValue> {
|
2025-02-05 17:53:49 +13:00
|
|
|
#[allow(clippy::arc_with_non_send_sync)]
|
2023-09-20 18:27:08 -07:00
|
|
|
Ok(EngineConnection {
|
|
|
|
manager: Arc::new(manager),
|
2025-02-18 13:50:13 -08:00
|
|
|
batch: Arc::new(RwLock::new(Vec::new())),
|
|
|
|
batch_end: Arc::new(RwLock::new(IndexMap::new())),
|
|
|
|
responses: Arc::new(RwLock::new(IndexMap::new())),
|
|
|
|
artifact_commands: Arc::new(RwLock::new(Vec::new())),
|
2025-03-15 10:08:39 -07:00
|
|
|
default_planes: Default::default(),
|
2025-03-18 16:19:24 +13:00
|
|
|
stats: Default::default(),
|
2023-09-20 18:27:08 -07:00
|
|
|
})
|
2023-08-24 15:34:51 -07:00
|
|
|
}
|
|
|
|
|
2025-02-18 13:50:13 -08:00
|
|
|
async fn do_send_modeling_cmd(
|
2025-01-08 20:02:30 -05:00
|
|
|
&self,
|
2025-02-18 13:50:13 -08:00
|
|
|
id: uuid::Uuid,
|
|
|
|
source_range: SourceRange,
|
|
|
|
cmd: WebSocketRequest,
|
|
|
|
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
|
|
|
|
) -> Result<WebSocketResponse, KclError> {
|
|
|
|
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
|
|
|
|
KclError::Engine(KclErrorDetails {
|
|
|
|
message: format!("Failed to serialize source range: {:?}", e),
|
|
|
|
source_ranges: vec![source_range],
|
|
|
|
})
|
|
|
|
})?;
|
|
|
|
let cmd_str = serde_json::to_string(&cmd).map_err(|e| {
|
|
|
|
KclError::Engine(KclErrorDetails {
|
|
|
|
message: format!("Failed to serialize modeling command: {:?}", e),
|
|
|
|
source_ranges: vec![source_range],
|
|
|
|
})
|
|
|
|
})?;
|
|
|
|
let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| {
|
|
|
|
KclError::Engine(KclErrorDetails {
|
|
|
|
message: format!("Failed to serialize id to source range: {:?}", e),
|
|
|
|
source_ranges: vec![source_range],
|
|
|
|
})
|
|
|
|
})?;
|
|
|
|
|
|
|
|
let promise = self
|
|
|
|
.manager
|
|
|
|
.send_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str)
|
|
|
|
.map_err(|e| {
|
|
|
|
KclError::Engine(KclErrorDetails {
|
|
|
|
message: e.to_string().into(),
|
|
|
|
source_ranges: vec![source_range],
|
|
|
|
})
|
|
|
|
})?;
|
|
|
|
|
|
|
|
let value = crate::wasm::JsFuture::from(promise).await.map_err(|e| {
|
2025-03-18 20:25:51 -07:00
|
|
|
// Try to parse the error as an engine error.
|
|
|
|
let err_str = e.as_string().unwrap_or_default();
|
|
|
|
if let Ok(kittycad_modeling_cmds::websocket::FailureWebSocketResponse { errors, .. }) =
|
|
|
|
serde_json::from_str(&err_str)
|
|
|
|
{
|
|
|
|
KclError::Engine(KclErrorDetails {
|
|
|
|
message: errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
|
|
|
|
source_ranges: vec![source_range],
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
KclError::Engine(KclErrorDetails {
|
|
|
|
message: format!("Failed to wait for promise from send modeling command: {:?}", e),
|
|
|
|
source_ranges: vec![source_range],
|
|
|
|
})
|
|
|
|
}
|
2025-02-18 13:50:13 -08:00
|
|
|
})?;
|
|
|
|
|
2025-03-28 14:48:47 -04:00
|
|
|
if value.is_null() || value.is_undefined() {
|
|
|
|
return Err(KclError::Engine(KclErrorDetails {
|
|
|
|
message: "Received null or undefined response from engine".into(),
|
|
|
|
source_ranges: vec![source_range],
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2025-03-18 20:25:51 -07:00
|
|
|
// Convert JsValue to a Uint8Array
|
|
|
|
let data = js_sys::Uint8Array::from(value);
|
2025-02-18 13:50:13 -08:00
|
|
|
|
2025-03-18 20:25:51 -07:00
|
|
|
let ws_result: WebSocketResponse = bson::from_slice(&data.to_vec()).map_err(|e| {
|
2025-02-18 13:50:13 -08:00
|
|
|
KclError::Engine(KclErrorDetails {
|
2025-03-18 20:25:51 -07:00
|
|
|
message: format!("Failed to deserialize bson response from engine: {:?}", e),
|
2025-02-18 13:50:13 -08:00
|
|
|
source_ranges: vec![source_range],
|
|
|
|
})
|
|
|
|
})?;
|
|
|
|
|
|
|
|
Ok(ws_result)
|
2025-01-08 20:02:30 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-12 13:37:47 -07:00
|
|
|
#[async_trait::async_trait]
|
2023-09-17 21:57:43 -07:00
|
|
|
impl crate::engine::EngineManager for EngineConnection {
|
2025-02-18 13:50:13 -08:00
|
|
|
fn batch(&self) -> Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>> {
|
2024-03-23 15:45:55 -07:00
|
|
|
self.batch.clone()
|
2024-04-15 17:18:32 -07:00
|
|
|
}
|
|
|
|
|
2025-02-18 13:50:13 -08:00
|
|
|
fn batch_end(&self) -> Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
|
2024-06-22 14:31:37 -07:00
|
|
|
self.batch_end.clone()
|
|
|
|
}
|
|
|
|
|
2025-02-18 13:50:13 -08:00
|
|
|
fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>> {
|
|
|
|
self.responses.clone()
|
2025-01-17 14:34:36 -05:00
|
|
|
}
|
|
|
|
|
2025-03-18 16:19:24 +13:00
|
|
|
fn stats(&self) -> &EngineStats {
|
|
|
|
&self.stats
|
|
|
|
}
|
|
|
|
|
2025-02-18 13:50:13 -08:00
|
|
|
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> {
|
|
|
|
self.artifact_commands.clone()
|
2025-01-08 20:02:30 -05:00
|
|
|
}
|
|
|
|
|
2025-03-15 10:08:39 -07:00
|
|
|
fn get_default_planes(&self) -> Arc<RwLock<Option<DefaultPlanes>>> {
|
|
|
|
self.default_planes.clone()
|
2024-04-15 17:18:32 -07:00
|
|
|
}
|
|
|
|
|
2024-10-09 19:38:40 -04:00
|
|
|
async fn clear_scene_post_hook(
|
|
|
|
&self,
|
2025-03-15 10:08:39 -07:00
|
|
|
id_generator: &mut IdGenerator,
|
2024-12-03 16:39:51 +13:00
|
|
|
source_range: SourceRange,
|
2024-10-09 19:38:40 -04:00
|
|
|
) -> Result<(), KclError> {
|
2025-03-15 10:08:39 -07:00
|
|
|
// Remake the default planes, since they would have been removed after the scene was cleared.
|
|
|
|
let new_planes = self.new_default_planes(id_generator, source_range).await?;
|
|
|
|
*self.default_planes.write().await = Some(new_planes);
|
2024-04-15 17:18:32 -07:00
|
|
|
|
|
|
|
// Start a new session.
|
|
|
|
let promise = self.manager.start_new_session().map_err(|e| {
|
|
|
|
KclError::Engine(KclErrorDetails {
|
|
|
|
message: e.to_string().into(),
|
|
|
|
source_ranges: vec![source_range],
|
|
|
|
})
|
|
|
|
})?;
|
|
|
|
|
|
|
|
crate::wasm::JsFuture::from(promise).await.map_err(|e| {
|
|
|
|
KclError::Engine(KclErrorDetails {
|
|
|
|
message: format!("Failed to wait for promise from start new session: {:?}", e),
|
|
|
|
source_ranges: vec![source_range],
|
|
|
|
})
|
|
|
|
})?;
|
|
|
|
|
|
|
|
Ok(())
|
2024-03-23 15:45:55 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn inner_send_modeling_cmd(
|
2023-09-20 16:59:03 -05:00
|
|
|
&self,
|
2023-09-17 21:57:43 -07:00
|
|
|
id: uuid::Uuid,
|
2024-12-03 16:39:51 +13:00
|
|
|
source_range: SourceRange,
|
2024-09-18 17:04:04 -05:00
|
|
|
cmd: WebSocketRequest,
|
2025-01-08 20:02:30 -05:00
|
|
|
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
|
2024-09-18 17:04:04 -05:00
|
|
|
) -> Result<WebSocketResponse, KclError> {
|
2025-02-18 13:50:13 -08:00
|
|
|
let ws_result = self
|
|
|
|
.do_send_modeling_cmd(id, source_range, cmd, id_to_source_range)
|
|
|
|
.await?;
|
2025-01-08 20:02:30 -05:00
|
|
|
|
2025-02-18 13:50:13 -08:00
|
|
|
let mut responses = self.responses.write().await;
|
2025-01-17 14:34:36 -05:00
|
|
|
responses.insert(id, ws_result.clone());
|
2025-02-18 13:50:13 -08:00
|
|
|
drop(responses);
|
2025-01-17 14:34:36 -05:00
|
|
|
|
2024-06-19 13:57:50 -07:00
|
|
|
Ok(ws_result)
|
2023-09-17 21:57:43 -07:00
|
|
|
}
|
2025-01-10 20:05:27 -05:00
|
|
|
|
|
|
|
// maybe we can actually impl this here? not sure how atm.
|
|
|
|
async fn close(&self) {}
|
2023-08-24 15:34:51 -07:00
|
|
|
}
|