get responses back from batch (#2687)

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* remove my stupid println

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* weird typescript

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* better batch stuff;

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* ckeanup

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* typpo

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* batch more

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* thing

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* up[dates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixces

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* cleanups

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* images

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* empty

* cleanups

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* console log all the things

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* fixups

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* console log cleanup

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* nicer types

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* remove logs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* remove logs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Jess Frazelle
2024-06-19 13:57:50 -07:00
committed by GitHub
parent b26764bc9a
commit 7bf50d8fe0
36 changed files with 382 additions and 219 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -32,9 +32,7 @@ import {
SKETCH_GROUP_SEGMENTS,
SKETCH_LAYER,
X_AXIS,
XZ_PLANE,
Y_AXIS,
YZ_PLANE,
} from './sceneInfra'
import { isQuaternionVertical, quaternionFromUpNForward } from './helpers'
import {

View File

@ -47,7 +47,6 @@ import {
TANGENTIAL_ARC_TO_SEGMENT,
getParentGroup,
getSketchOrientationDetails,
getSketchQuaternion,
} from 'clientSideScene/sceneEntities'
import {
moveValueIntoNewVariablePath,

View File

@ -1,8 +1,7 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { render, screen } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import ProjectSidebarMenu from './ProjectSidebarMenu'
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
import { APP_NAME } from 'lib/constants'
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
import { Project } from 'wasm-lib/kcl/bindings/Project'

View File

@ -92,7 +92,6 @@ export class KclManager {
return this._kclErrors
}
set kclErrors(kclErrors) {
console.log('[lsp] not lsp, actually typescript: ', kclErrors)
this._kclErrors = kclErrors
let diagnostics = kclErrorsToDiagnostics(kclErrors)
editorManager.addDiagnostics(diagnostics)

View File

@ -58,6 +58,9 @@ function isHighlightSetEntity_type(
type WebSocketResponse = Models['WebSocketResponse_type']
type OkWebSocketResponseData = Models['OkWebSocketResponseData_type']
type BatchResponseMap = {
[key: string]: Models['BatchResponse_type']
}
type ResultCommand = CommandInfo & {
type: 'result'
@ -1316,7 +1319,8 @@ export class EngineCommandManager extends EventTarget {
)
if (
message.success &&
message.resp.type === 'modeling' &&
(message.resp.type === 'modeling' ||
message.resp.type === 'modeling_batch') &&
message.request_id
) {
this.handleModelingCommand(
@ -1380,19 +1384,60 @@ export class EngineCommandManager extends EventTarget {
id: string,
raw: WebSocketResponse
) {
if (message.type !== 'modeling') {
if (!(message.type === 'modeling' || message.type === 'modeling_batch')) {
return
}
const modelingResponse = message.data.modeling_response
const command = this.artifactMap[id]
let modelingResponse: Models['OkModelingCmdResponse_type'] = {
type: 'empty',
}
if ('modeling_response' in message.data) {
modelingResponse = message.data.modeling_response
}
if (
command?.type === 'pending' &&
command.commandType === 'batch' &&
command?.additionalData?.type === 'batch-ids'
) {
if ('responses' in message.data) {
const batchResponse = message.data.responses as BatchResponseMap
// Iterate over the map of responses.
Object.entries(batchResponse).forEach(([key, response]) => {
// If the response is a success, we resolve the promise.
if ('response' in response && response.response) {
this.handleModelingCommand(
{
type: 'modeling',
data: {
modeling_response: response.response,
},
},
key,
{
request_id: key,
resp: {
type: 'modeling',
data: {
modeling_response: response.response,
},
},
success: true,
}
)
} else if ('errors' in response) {
this.handleFailedModelingCommand(key, {
request_id: key,
success: false,
errors: response.errors,
})
}
})
} else {
command.additionalData.ids.forEach((id) => {
this.handleModelingCommand(message, id, raw)
})
}
// batch artifact is just a container, we don't need to keep it
// once we process all the commands inside it
const resolve = command.resolve
@ -1401,7 +1446,6 @@ export class EngineCommandManager extends EventTarget {
id,
commandType: command.commandType,
range: command.range,
data: modelingResponse,
raw,
})
return
@ -1733,7 +1777,7 @@ export class EngineCommandManager extends EventTarget {
command: EngineCommand
ast: Program
idToRangeMap?: { [key: string]: SourceRange }
}): Promise<any> {
}): Promise<ResolveCommand | void> {
if (this.engineConnection === undefined) {
return Promise.resolve()
}
@ -1802,11 +1846,13 @@ export class EngineCommandManager extends EventTarget {
command: Models['ModelingCmd_type'],
ast?: Program,
range?: SourceRange
) {
): Promise<ResolveCommand | void> {
let resolve: (val: any) => void = () => {}
const promise = new Promise((_resolve, reject) => {
const promise: Promise<ResolveCommand | void> = new Promise(
(_resolve, reject) => {
resolve = _resolve
})
}
)
const getParentId = (): string | undefined => {
if (command.type === 'extend_path') return command.path
if (command.type === 'solid3d_get_extrusion_face_info') {
@ -1867,11 +1913,13 @@ export class EngineCommandManager extends EventTarget {
idToRangeMap?: { [key: string]: SourceRange },
ast?: Program,
range?: SourceRange
) {
): Promise<ResolveCommand | void> {
let resolve: (val: any) => void = () => {}
const promise = new Promise((_resolve, reject) => {
const promise: Promise<ResolveCommand | void> = new Promise(
(_resolve, reject) => {
resolve = _resolve
})
}
)
if (!idToRangeMap) {
throw new Error('idToRangeMap is required for batch commands')
@ -1891,7 +1939,7 @@ export class EngineCommandManager extends EventTarget {
resolve,
}
await Promise.all(
Promise.all(
commands.map((c) =>
this.handlePendingCommand(c.cmd_id, c.cmd, ast, idToRangeMap[c.cmd_id])
)
@ -1903,7 +1951,7 @@ export class EngineCommandManager extends EventTarget {
rangeStr: string,
commandStr: string,
idToRangeStr: string
): Promise<any> {
): Promise<string | void> {
if (this.engineConnection === undefined) {
return Promise.resolve()
}
@ -1932,13 +1980,13 @@ export class EngineCommandManager extends EventTarget {
command,
ast: this.getAst(),
idToRangeMap,
}).then(({ raw }: { raw: WebSocketResponse | undefined | null }) => {
if (raw === undefined || raw === null) {
}).then((resp) => {
if (!resp) {
throw new Error(
'returning modeling cmd response to the rust side is undefined or null'
)
}
return JSON.stringify(raw)
return JSON.stringify(resp.raw)
})
}
commandResult(id: string): Promise<any> {

View File

@ -1,20 +1,26 @@
//! Executes KCL programs.
//! The server reuses the same engine session for each KCL program it receives.
use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use std::{
net::SocketAddr,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
time::Duration,
};
use hyper::body::Bytes;
use hyper::header::CONTENT_TYPE;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Error, Response, Server};
use kcl_lib::executor::ExecutorContext;
use kcl_lib::settings::types::UnitLength;
use kcl_lib::test_server::RequestBody;
use tokio::sync::{mpsc, oneshot};
use tokio::task::JoinHandle;
use tokio::time::sleep;
use hyper::{
body::Bytes,
header::CONTENT_TYPE,
service::{make_service_fn, service_fn},
Body, Error, Response, Server,
};
use kcl_lib::{executor::ExecutorContext, settings::types::UnitLength, test_server::RequestBody};
use tokio::{
sync::{mpsc, oneshot},
task::JoinHandle,
time::sleep,
};
#[derive(Debug)]
pub struct ServerArgs {

View File

@ -3,7 +3,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value as JValue;
use super::{Literal, Value};
use crate::ast::types::{Literal, Value};
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
#[databake(path = kcl_lib::ast::types)]

View File

@ -4,8 +4,10 @@ use databake::*;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::ConstraintLevel;
use crate::executor::{MemoryItem, SourceRange, UserVal};
use crate::{
ast::types::ConstraintLevel,
executor::{MemoryItem, SourceRange, UserVal},
};
/// KCL value for an optional parameter which was not given an argument.
/// (remember, parameters are in the function declaration,

View File

@ -1,15 +1,17 @@
//! Functions for generating docs for our stdlib functions.
use crate::std::Primitive;
use std::path::Path;
use anyhow::Result;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::path::Path;
use tower_lsp::lsp_types::{
CompletionItem, CompletionItemKind, CompletionItemLabelDetails, Documentation, InsertTextFormat, MarkupContent,
MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation,
};
use crate::std::Primitive;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]

View File

@ -6,7 +6,7 @@ use std::sync::{Arc, Mutex};
use anyhow::{anyhow, Result};
use dashmap::DashMap;
use futures::{SinkExt, StreamExt};
use kittycad::types::{OkWebSocketResponseData, WebSocketRequest, WebSocketResponse};
use kittycad::types::{WebSocketRequest, WebSocketResponse};
use tokio::sync::{mpsc, oneshot, RwLock};
use tokio_tungstenite::tungstenite::Message as WsMsg;
@ -183,6 +183,39 @@ impl EngineConnection {
for e in ws_resp.errors.iter().flatten() {
println!("got error message: {} {}", e.error_code, e.message);
}
// If we got a batch response, add all the inner responses.
println!("got response: {:?}", ws_resp);
if let Some(kittycad::types::OkWebSocketResponseData::ModelingBatch { responses }) =
&ws_resp.resp
{
for (resp_id, batch_response) in responses {
let id: uuid::Uuid = resp_id.parse().unwrap();
if let Some(response) = &batch_response.response {
responses_clone.insert(
id,
kittycad::types::WebSocketResponse {
request_id: Some(id),
resp: Some(kittycad::types::OkWebSocketResponseData::Modeling {
modeling_response: response.clone(),
}),
errors: None,
success: Some(true),
},
);
} else {
responses_clone.insert(
id,
kittycad::types::WebSocketResponse {
request_id: Some(id),
resp: None,
errors: batch_response.errors.clone(),
success: Some(false),
},
);
}
}
}
if let Some(id) = ws_resp.request_id {
responses_clone.insert(id, ws_resp.clone());
}
@ -246,7 +279,7 @@ impl EngineManager for EngineConnection {
source_range: crate::executor::SourceRange,
cmd: kittycad::types::WebSocketRequest,
_id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>,
) -> Result<OkWebSocketResponseData, KclError> {
) -> Result<WebSocketResponse, KclError> {
let (tx, rx) = oneshot::channel();
// Send the request to the engine, via the actor.
@ -291,14 +324,7 @@ impl EngineManager for EngineConnection {
}
// We pop off the responses to cleanup our mappings.
if let Some((_, resp)) = self.responses.remove(&id) {
return if let Some(data) = &resp.resp {
Ok(data.clone())
} else {
Err(KclError::Engine(KclErrorDetails {
message: format!("Modeling command failed: {:?}", resp.errors),
source_ranges: vec![source_range],
}))
};
return Ok(resp);
}
}

View File

@ -1,10 +1,13 @@
//! Functions for setting up our WebSocket and WebRTC connections for communications with the
//! engine.
use std::sync::{Arc, Mutex};
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use anyhow::Result;
use kittycad::types::{OkWebSocketResponseData, WebSocketRequest};
use kittycad::types::{OkWebSocketResponseData, WebSocketRequest, WebSocketResponse};
use crate::{errors::KclError, executor::DefaultPlanes};
@ -37,13 +40,43 @@ impl crate::engine::EngineManager for EngineConnection {
async fn inner_send_modeling_cmd(
&self,
_id: uuid::Uuid,
id: uuid::Uuid,
_source_range: crate::executor::SourceRange,
_cmd: kittycad::types::WebSocketRequest,
cmd: kittycad::types::WebSocketRequest,
_id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>,
) -> Result<OkWebSocketResponseData, KclError> {
Ok(OkWebSocketResponseData::Modeling {
modeling_response: kittycad::types::OkModelingCmdResponse::Empty {},
) -> Result<WebSocketResponse, KclError> {
match cmd {
WebSocketRequest::ModelingCmdBatchReq {
ref requests,
batch_id: _,
responses: _,
} => {
// Create the empty responses.
let mut responses = HashMap::new();
for request in requests {
responses.insert(
request.cmd_id.to_string(),
kittycad::types::BatchResponse {
response: Some(kittycad::types::OkModelingCmdResponse::Empty {}),
errors: None,
},
);
}
Ok(WebSocketResponse {
request_id: Some(id),
resp: Some(OkWebSocketResponseData::ModelingBatch { responses }),
success: Some(true),
errors: None,
})
}
_ => Ok(WebSocketResponse {
request_id: Some(id),
resp: Some(OkWebSocketResponseData::Modeling {
modeling_response: kittycad::types::OkModelingCmdResponse::Empty {},
}),
success: Some(true),
errors: None,
}),
}
}
}

View File

@ -130,7 +130,7 @@ impl crate::engine::EngineManager for EngineConnection {
source_range: crate::executor::SourceRange,
cmd: kittycad::types::WebSocketRequest,
id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>,
) -> Result<kittycad::types::OkWebSocketResponseData, KclError> {
) -> Result<kittycad::types::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),
@ -182,18 +182,6 @@ impl crate::engine::EngineManager for EngineConnection {
})
})?;
if let Some(data) = &ws_result.resp {
Ok(data.clone())
} else if let Some(errors) = &ws_result.errors {
Err(KclError::Engine(KclErrorDetails {
message: format!("Modeling command failed: {:?}", errors),
source_ranges: vec![source_range],
}))
} else {
Err(KclError::Engine(KclErrorDetails {
message: format!("Modeling command failed: {:?}", ws_result),
source_ranges: vec![source_range],
}))
}
Ok(ws_result)
}
}

View File

@ -47,13 +47,13 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
source_range: crate::executor::SourceRange,
cmd: kittycad::types::WebSocketRequest,
id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>,
) -> Result<kittycad::types::OkWebSocketResponseData, crate::errors::KclError>;
) -> Result<kittycad::types::WebSocketResponse, crate::errors::KclError>;
async fn clear_scene(&self, source_range: crate::executor::SourceRange) -> Result<(), crate::errors::KclError> {
self.send_modeling_cmd(
self.batch_modeling_cmd(
uuid::Uuid::new_v4(),
source_range,
kittycad::types::ModelingCmd::SceneClearAll {},
&kittycad::types::ModelingCmd::SceneClearAll {},
)
.await?;
@ -67,12 +67,13 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
Ok(())
}
async fn send_modeling_cmd(
// Add a modeling command to the batch but don't fire it right away.
async fn batch_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
cmd: kittycad::types::ModelingCmd,
) -> Result<kittycad::types::OkWebSocketResponseData, crate::errors::KclError> {
cmd: &kittycad::types::ModelingCmd,
) -> Result<(), crate::errors::KclError> {
let req = WebSocketRequest::ModelingCmdReq {
cmd: cmd.clone(),
cmd_id: id,
@ -81,17 +82,18 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
// Add cmd to the batch.
self.batch().lock().unwrap().push((req, source_range));
// If the batch only has this one command that expects a return value,
// fire it right away, or if we want to flush batch queue.
let is_sending = is_cmd_with_return_values(&cmd);
// Return a fake modeling_request empty response.
if !is_sending {
return Ok(OkWebSocketResponseData::Modeling {
modeling_response: kittycad::types::OkModelingCmdResponse::Empty {},
});
Ok(())
}
/// Send the modeling cmd and wait for the response.
async fn send_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
cmd: kittycad::types::ModelingCmd,
) -> Result<kittycad::types::OkWebSocketResponseData, crate::errors::KclError> {
self.batch_modeling_cmd(id, source_range, &cmd).await?;
// Flush the batch queue.
self.flush_batch(source_range).await
}
@ -124,7 +126,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
let batched_requests = WebSocketRequest::ModelingCmdBatchReq {
requests,
batch_id: uuid::Uuid::new_v4(),
responses: false,
responses: true,
};
let final_req = if self.batch().lock().unwrap().len() == 1 {
@ -155,23 +157,41 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
self.batch().lock().unwrap().clear();
// We pop off the responses to cleanup our mappings.
let id_final = match final_req {
match final_req {
WebSocketRequest::ModelingCmdBatchReq {
requests: _,
ref requests,
batch_id,
responses: _,
} => batch_id,
WebSocketRequest::ModelingCmdReq { cmd: _, cmd_id } => cmd_id,
_ => {
return Err(KclError::Engine(KclErrorDetails {
} => {
// Get the last command ID.
let last_id = requests.last().unwrap().cmd_id;
let ws_resp = self
.inner_send_modeling_cmd(batch_id, source_range, final_req, id_to_source_range.clone())
.await?;
let response = self.parse_websocket_response(ws_resp, source_range)?;
// If we have a batch response, we want to return the specific id we care about.
if let kittycad::types::OkWebSocketResponseData::ModelingBatch { responses } = &response {
self.parse_batch_responses(last_id, id_to_source_range, responses.clone())
} else {
// We should never get here.
Err(KclError::Engine(KclErrorDetails {
message: format!("Failed to get batch response: {:?}", response),
source_ranges: vec![source_range],
}))
}
}
WebSocketRequest::ModelingCmdReq { cmd: _, cmd_id } => {
let ws_resp = self
.inner_send_modeling_cmd(cmd_id, source_range, final_req, id_to_source_range)
.await?;
self.parse_websocket_response(ws_resp, source_range)
}
_ => Err(KclError::Engine(KclErrorDetails {
message: format!("The final request is not a modeling command: {:?}", final_req),
source_ranges: vec![source_range],
}));
})),
}
};
self.inner_send_modeling_cmd(id_final, source_range, final_req, id_to_source_range)
.await
}
async fn make_default_plane(
@ -186,10 +206,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
let default_origin = Point3d { x: 0.0, y: 0.0, z: 0.0 }.into();
let plane_id = uuid::Uuid::new_v4();
self.send_modeling_cmd(
self.batch_modeling_cmd(
plane_id,
source_range,
ModelingCmd::MakePlane {
&ModelingCmd::MakePlane {
clobber: false,
origin: default_origin,
size: default_size,
@ -202,10 +222,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
if let Some(color) = color {
// Set the color.
self.send_modeling_cmd(
self.batch_modeling_cmd(
uuid::Uuid::new_v4(),
source_range,
ModelingCmd::PlaneSetColor { color, plane_id },
&ModelingCmd::PlaneSetColor { color, plane_id },
)
.await?;
}
@ -312,62 +332,79 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
neg_yz: planes[&PlaneName::NegYz],
})
}
}
pub fn is_cmd_with_return_values(cmd: &kittycad::types::ModelingCmd) -> bool {
let (kittycad::types::ModelingCmd::Export { .. }
| kittycad::types::ModelingCmd::Extrude { .. }
| kittycad::types::ModelingCmd::DefaultCameraLookAt { .. }
| kittycad::types::ModelingCmd::DefaultCameraFocusOn { .. }
| kittycad::types::ModelingCmd::DefaultCameraGetSettings { .. }
| kittycad::types::ModelingCmd::DefaultCameraPerspectiveSettings { .. }
| kittycad::types::ModelingCmd::DefaultCameraZoom { .. }
| kittycad::types::ModelingCmd::SketchModeDisable { .. }
| kittycad::types::ModelingCmd::ObjectBringToFront { .. }
| kittycad::types::ModelingCmd::SelectWithPoint { .. }
| kittycad::types::ModelingCmd::HighlightSetEntity { .. }
| kittycad::types::ModelingCmd::EntityGetChildUuid { .. }
| kittycad::types::ModelingCmd::EntityGetNumChildren { .. }
| kittycad::types::ModelingCmd::EntityGetParentId { .. }
| kittycad::types::ModelingCmd::EntityGetAllChildUuids { .. }
| kittycad::types::ModelingCmd::CameraDragMove { .. }
| kittycad::types::ModelingCmd::CameraDragEnd { .. }
| kittycad::types::ModelingCmd::SelectGet { .. }
| kittycad::types::ModelingCmd::Solid3DGetAllEdgeFaces { .. }
| kittycad::types::ModelingCmd::Solid3DGetAllOppositeEdges { .. }
| kittycad::types::ModelingCmd::Solid3DGetOppositeEdge { .. }
| kittycad::types::ModelingCmd::Solid3DGetNextAdjacentEdge { .. }
| kittycad::types::ModelingCmd::Solid3DGetPrevAdjacentEdge { .. }
| kittycad::types::ModelingCmd::GetEntityType { .. }
| kittycad::types::ModelingCmd::CurveGetControlPoints { .. }
| kittycad::types::ModelingCmd::CurveGetType { .. }
| kittycad::types::ModelingCmd::MouseClick { .. }
| kittycad::types::ModelingCmd::TakeSnapshot { .. }
| kittycad::types::ModelingCmd::PathGetInfo { .. }
| kittycad::types::ModelingCmd::PathGetCurveUuidsForVertices { .. }
| kittycad::types::ModelingCmd::PathGetVertexUuids { .. }
| kittycad::types::ModelingCmd::CurveGetEndPoints { .. }
| kittycad::types::ModelingCmd::FaceIsPlanar { .. }
| kittycad::types::ModelingCmd::FaceGetPosition { .. }
| kittycad::types::ModelingCmd::FaceGetGradient { .. }
| kittycad::types::ModelingCmd::PlaneIntersectAndProject { .. }
| kittycad::types::ModelingCmd::ImportFiles { .. }
| kittycad::types::ModelingCmd::Mass { .. }
| kittycad::types::ModelingCmd::Volume { .. }
| kittycad::types::ModelingCmd::Density { .. }
| kittycad::types::ModelingCmd::SurfaceArea { .. }
| kittycad::types::ModelingCmd::CenterOfMass { .. }
| kittycad::types::ModelingCmd::GetSketchModePlane { .. }
| kittycad::types::ModelingCmd::EntityGetDistance { .. }
| kittycad::types::ModelingCmd::EntityLinearPattern { .. }
| kittycad::types::ModelingCmd::EntityCircularPattern { .. }
| kittycad::types::ModelingCmd::ZoomToFit { .. }
| kittycad::types::ModelingCmd::Solid3DGetExtrusionFaceInfo { .. }) = cmd
else {
return false;
};
fn parse_websocket_response(
&self,
response: kittycad::types::WebSocketResponse,
source_range: crate::executor::SourceRange,
) -> Result<kittycad::types::OkWebSocketResponseData, crate::errors::KclError> {
if let Some(data) = &response.resp {
Ok(data.clone())
} else if let Some(errors) = &response.errors {
Err(KclError::Engine(KclErrorDetails {
message: format!("Modeling command failed: {:?}", errors),
source_ranges: vec![source_range],
}))
} else {
// We should never get here.
Err(KclError::Engine(KclErrorDetails {
message: "Modeling command failed: no response or errors".to_string(),
source_ranges: vec![source_range],
}))
}
}
true
fn parse_batch_responses(
&self,
// The last response we are looking for.
id: uuid::Uuid,
// The mapping of source ranges to command IDs.
id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>,
// The response from the engine.
responses: HashMap<String, kittycad::types::BatchResponse>,
) -> Result<kittycad::types::OkWebSocketResponseData, crate::errors::KclError> {
// Iterate over the responses and check for errors.
for (cmd_id, resp) in responses.iter() {
let cmd_id = uuid::Uuid::parse_str(cmd_id).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to parse command ID: {:?}", e),
source_ranges: vec![id_to_source_range[&id]],
})
})?;
if let Some(errors) = resp.errors.as_ref() {
// Get the source range for the command.
let source_range = id_to_source_range.get(&cmd_id).cloned().ok_or_else(|| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to get source range for command ID: {:?}", cmd_id),
source_ranges: vec![],
})
})?;
return Err(KclError::Engine(KclErrorDetails {
message: format!("Modeling command failed: {:?}", errors),
source_ranges: vec![source_range],
}));
}
if let Some(response) = resp.response.as_ref() {
if cmd_id == id {
// This is the response we care about.
return Ok(kittycad::types::OkWebSocketResponseData::Modeling {
modeling_response: response.clone(),
});
} else {
// Continue the loop if this is not the response we care about.
continue;
}
}
}
// Return an error that we did not get an error or the response we wanted.
// This should never happen but who knows.
Err(KclError::Engine(KclErrorDetails {
message: format!("Failed to find response for command ID: {:?}", id),
source_ranges: vec![],
}))
}
}
#[derive(Debug, Hash, Eq, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]

View File

@ -1066,10 +1066,10 @@ impl ExecutorContext {
// Set the edge visibility.
engine
.send_modeling_cmd(
.batch_modeling_cmd(
uuid::Uuid::new_v4(),
SourceRange::default(),
kittycad::types::ModelingCmd::EdgeLinesVisible {
&kittycad::types::ModelingCmd::EdgeLinesVisible {
hidden: !settings.highlight_edges,
},
)
@ -1145,10 +1145,10 @@ impl ExecutorContext {
) -> Result<ProgramMemory, KclError> {
// Before we even start executing the program, set the units.
self.engine
.send_modeling_cmd(
.batch_modeling_cmd(
uuid::Uuid::new_v4(),
SourceRange::default(),
kittycad::types::ModelingCmd::SetSceneUnits {
&kittycad::types::ModelingCmd::SetSceneUnits {
unit: self.settings.units.clone().into(),
},
)

View File

@ -1,9 +1,12 @@
use super::Node;
use crate::ast::types::{
use anyhow::Result;
use crate::{
ast::types::{
BinaryPart, BodyItem, LiteralIdentifier, MemberExpression, MemberObject, ObjectExpression, ObjectProperty,
Parameter, Program, UnaryExpression, Value, VariableDeclarator,
},
lint::Node,
};
use anyhow::Result;
/// Walker is implemented by things that are able to walk an AST tree to
/// produce lints. This trait is implemented automatically for a few of the

View File

@ -1,3 +1,5 @@
use anyhow::Result;
use crate::{
ast::types::VariableDeclarator,
executor::SourceRange,
@ -7,8 +9,6 @@ use crate::{
},
};
use anyhow::Result;
def_finding!(
Z0001,
"Identifiers must be lowerCamelCase",

View File

@ -1,9 +1,15 @@
use super::{walk, Node};
use crate::{ast::types::Program, executor::SourceRange, lsp::IntoDiagnostic};
use anyhow::Result;
use std::sync::{Arc, Mutex};
use anyhow::Result;
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
use crate::{
ast::types::Program,
executor::SourceRange,
lint::{walk, Node},
lsp::IntoDiagnostic,
};
/// Check the provided AST for any found rule violations.
///
/// The Rule trait is automatically implemented for a few other types,
@ -105,7 +111,6 @@ macro_rules! finding {
};
}
pub(crate) use finding;
#[cfg(test)]
pub(crate) use test::{assert_finding, assert_no_finding, test_finding, test_no_finding};

View File

@ -109,7 +109,7 @@ async fn inner_chamfer(
}
};
args.send_modeling_cmd(
args.batch_modeling_cmd(
uuid::Uuid::new_v4(),
ModelingCmd::Solid3DFilletEdge {
edge_id,

View File

@ -111,13 +111,13 @@ pub(crate) async fn do_post_extrude(
// We need to do this after extrude for sketch on face.
if let SketchSurface::Face(_) = sketch_group.on {
// Disable the sketch mode.
args.send_modeling_cmd(uuid::Uuid::new_v4(), kittycad::types::ModelingCmd::SketchModeDisable {})
args.batch_modeling_cmd(uuid::Uuid::new_v4(), kittycad::types::ModelingCmd::SketchModeDisable {})
.await?;
}
// Bring the object to the front of the scene.
// See: https://github.com/KittyCAD/modeling-app/issues/806
args.send_modeling_cmd(
args.batch_modeling_cmd(
uuid::Uuid::new_v4(),
kittycad::types::ModelingCmd::ObjectBringToFront {
object_id: sketch_group.id,

View File

@ -110,7 +110,7 @@ async fn inner_fillet(
}
};
args.send_modeling_cmd(
args.batch_modeling_cmd(
uuid::Uuid::new_v4(),
ModelingCmd::Solid3DFilletEdge {
edge_id,

View File

@ -59,7 +59,7 @@ async fn inner_helix(
args: Args,
) -> Result<Box<ExtrudeGroup>, KclError> {
let id = uuid::Uuid::new_v4();
args.send_modeling_cmd(
args.batch_modeling_cmd(
id,
ModelingCmd::EntityMakeHelix {
cylinder_id: extrude_group.id,

View File

@ -1,6 +1,7 @@
use std::path::Path;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::path::Path;
use crate::{
ast::types::{BodyItem, FunctionExpression, Program, Value},

View File

@ -215,6 +215,16 @@ impl Args {
}
}
// Add a modeling command to the batch but don't fire it right away.
pub async fn batch_modeling_cmd(
&self,
id: uuid::Uuid,
cmd: kittycad::types::ModelingCmd,
) -> Result<(), crate::errors::KclError> {
self.ctx.engine.batch_modeling_cmd(id, self.source_range, &cmd).await
}
/// Send the modeling cmd and wait for the response.
pub async fn send_modeling_cmd(
&self,
id: uuid::Uuid,

View File

@ -242,7 +242,7 @@ async fn inner_revolve(
match data.axis {
RevolveAxis::Axis(axis) => {
let (axis, origin) = axis.axis_and_origin()?;
args.send_modeling_cmd(
args.batch_modeling_cmd(
id,
ModelingCmd::Revolve {
angle,
@ -274,7 +274,7 @@ async fn inner_revolve(
.id
}
};
args.send_modeling_cmd(
args.batch_modeling_cmd(
id,
ModelingCmd::RevolveAboutEdge {
angle,

View File

@ -4,11 +4,10 @@ use anyhow::Result;
use derive_docs::stdlib;
use schemars::JsonSchema;
use super::utils::between;
use crate::{
errors::{KclError, KclErrorDetails},
executor::{MemoryItem, SketchGroup},
std::Args,
std::{utils::between, Args},
};
/// Returns the segment end of x.

View File

@ -48,7 +48,6 @@ pub async fn circle(args: Args) -> Result<MemoryItem, KclError> {
/// |> hole(circle([0, 15], 5, %), %)
///
/// const example = extrude(5, exampleSketch)
///
#[stdlib {
name = "circle",
}]

View File

@ -126,7 +126,7 @@ async fn inner_shell(
}));
}
args.send_modeling_cmd(
args.batch_modeling_cmd(
uuid::Uuid::new_v4(),
ModelingCmd::Solid3DShellFace {
face_ids,

View File

@ -55,7 +55,7 @@ async fn inner_line_to(
let from = sketch_group.current_pen_position()?;
let id = uuid::Uuid::new_v4();
args.send_modeling_cmd(
args.batch_modeling_cmd(
id,
ModelingCmd::ExtendPath {
path: sketch_group.id,
@ -217,7 +217,7 @@ async fn inner_line(
let id = uuid::Uuid::new_v4();
args.send_modeling_cmd(
args.batch_modeling_cmd(
id,
ModelingCmd::ExtendPath {
path: sketch_group.id,
@ -409,7 +409,7 @@ async fn inner_angled_line(
},
};
args.send_modeling_cmd(
args.batch_modeling_cmd(
id,
ModelingCmd::ExtendPath {
path: sketch_group.id,
@ -1071,7 +1071,7 @@ async fn start_sketch_on_face(
// Enter sketch mode on the face.
let id = uuid::Uuid::new_v4();
args.send_modeling_cmd(
args.batch_modeling_cmd(
id,
ModelingCmd::EnableSketchMode {
animated: false,
@ -1117,7 +1117,7 @@ async fn start_sketch_on_plane(data: PlaneData, args: Args) -> Result<Box<Plane>
} => {
// Create the custom plane on the fly.
let id = uuid::Uuid::new_v4();
args.send_modeling_cmd(
args.batch_modeling_cmd(
id,
ModelingCmd::MakePlane {
clobber: false,
@ -1135,7 +1135,7 @@ async fn start_sketch_on_plane(data: PlaneData, args: Args) -> Result<Box<Plane>
};
// Enter sketch mode on the plane.
args.send_modeling_cmd(
args.batch_modeling_cmd(
uuid::Uuid::new_v4(),
ModelingCmd::EnableSketchMode {
animated: false,
@ -1205,8 +1205,8 @@ pub(crate) async fn inner_start_profile_at(
let id = uuid::Uuid::new_v4();
let path_id = uuid::Uuid::new_v4();
args.send_modeling_cmd(path_id, ModelingCmd::StartPath {}).await?;
args.send_modeling_cmd(
args.batch_modeling_cmd(path_id, ModelingCmd::StartPath {}).await?;
args.batch_modeling_cmd(
id,
ModelingCmd::MovePathPen {
path: path_id,
@ -1359,7 +1359,7 @@ pub(crate) async fn inner_close(
let id = uuid::Uuid::new_v4();
args.send_modeling_cmd(
args.batch_modeling_cmd(
id,
ModelingCmd::ClosePath {
path_id: sketch_group.id,
@ -1370,7 +1370,7 @@ pub(crate) async fn inner_close(
// If we are sketching on a plane we can close the sketch group now.
if let SketchSurface::Plane(_) = sketch_group.on {
// We were on a plane, disable the sketch mode.
args.send_modeling_cmd(uuid::Uuid::new_v4(), kittycad::types::ModelingCmd::SketchModeDisable {})
args.batch_modeling_cmd(uuid::Uuid::new_v4(), kittycad::types::ModelingCmd::SketchModeDisable {})
.await?;
}
@ -1436,7 +1436,6 @@ pub async fn arc(args: Args) -> Result<MemoryItem, KclError> {
/// radius: 16
/// }, %)
/// |> close(%)
///
// const example = extrude(10, exampleSketch)
/// ```
#[stdlib {
@ -1469,7 +1468,7 @@ pub(crate) async fn inner_arc(
let id = uuid::Uuid::new_v4();
args.send_modeling_cmd(
args.batch_modeling_cmd(
id,
ModelingCmd::ExtendPath {
path: sketch_group.id,
@ -1565,7 +1564,7 @@ async fn inner_tangential_arc(
let start_angle = Angle::from_degrees(0.0);
let (_, to) = arc_center_and_end(from, start_angle, end_angle, *radius);
args.send_modeling_cmd(
args.batch_modeling_cmd(
id,
ModelingCmd::ExtendPath {
path: sketch_group.id,
@ -1582,7 +1581,7 @@ async fn inner_tangential_arc(
to.into()
}
TangentialArcData::Point(to) => {
args.send_modeling_cmd(id, tan_arc_to(&sketch_group, to)).await?;
args.batch_modeling_cmd(id, tan_arc_to(&sketch_group, to)).await?;
*to
}
@ -1692,7 +1691,7 @@ async fn inner_tangential_arc_to(
let delta = [to_x - from.x, to_y - from.y];
let id = uuid::Uuid::new_v4();
args.send_modeling_cmd(id, tan_arc_to(&sketch_group, &delta)).await?;
args.batch_modeling_cmd(id, tan_arc_to(&sketch_group, &delta)).await?;
let current_path = Path::TangentialArcTo {
base: BasePath {
@ -1769,7 +1768,7 @@ async fn inner_bezier_curve(
let id = uuid::Uuid::new_v4();
args.send_modeling_cmd(
args.batch_modeling_cmd(
id,
ModelingCmd::ExtendPath {
path: sketch_group.id,
@ -1864,7 +1863,7 @@ async fn inner_hole(
match hole_sketch_group {
SketchGroupSet::SketchGroup(hole_sketch_group) => {
args.send_modeling_cmd(
args.batch_modeling_cmd(
uuid::Uuid::new_v4(),
ModelingCmd::Solid2DAddHole {
object_id: sketch_group.id,
@ -1874,7 +1873,7 @@ async fn inner_hole(
.await?;
// suggestion (mike)
// we also hide the source hole since its essentially "consumed" by this operation
args.send_modeling_cmd(
args.batch_modeling_cmd(
uuid::Uuid::new_v4(),
ModelingCmd::ObjectVisible {
object_id: hole_sketch_group.id,
@ -1885,7 +1884,7 @@ async fn inner_hole(
}
SketchGroupSet::SketchGroups(hole_sketch_groups) => {
for hole_sketch_group in hole_sketch_groups {
args.send_modeling_cmd(
args.batch_modeling_cmd(
uuid::Uuid::new_v4(),
ModelingCmd::Solid2DAddHole {
object_id: sketch_group.id,
@ -1895,7 +1894,7 @@ async fn inner_hole(
.await?;
// suggestion (mike)
// we also hide the source hole since its essentially "consumed" by this operation
args.send_modeling_cmd(
args.batch_modeling_cmd(
uuid::Uuid::new_v4(),
ModelingCmd::ObjectVisible {
object_id: hole_sketch_group.id,

View File

@ -28,12 +28,22 @@ pub async fn execute_wasm(
let memory: kcl_lib::executor::ProgramMemory = serde_json::from_str(memory_str).map_err(|e| e.to_string())?;
let units = kcl_lib::settings::types::UnitLength::from_str(units).map_err(|e| e.to_string())?;
let engine = kcl_lib::engine::conn_wasm::EngineConnection::new(engine_manager)
let engine: std::sync::Arc<Box<dyn kcl_lib::engine::EngineManager>> = if is_mock {
Arc::new(Box::new(
kcl_lib::engine::conn_mock::EngineConnection::new()
.await
.map_err(|e| format!("{:?}", e))?;
.map_err(|e| format!("{:?}", e))?,
))
} else {
Arc::new(Box::new(
kcl_lib::engine::conn_wasm::EngineConnection::new(engine_manager)
.await
.map_err(|e| format!("{:?}", e))?,
))
};
let fs = Arc::new(kcl_lib::fs::FileManager::new(fs_manager));
let ctx = kcl_lib::executor::ExecutorContext {
engine: Arc::new(Box::new(engine)),
engine,
fs,
stdlib: std::sync::Arc::new(kcl_lib::std::StdLib::new()),
settings: ExecutorSettings {

View File

@ -459,7 +459,7 @@ async fn serial_test_execute_engine_error_return() {
assert!(result.is_err());
assert_eq!(
result.err().unwrap().to_string(),
r#"engine: KclErrorDetails { source_ranges: [SourceRange([222, 235])], message: "Modeling command failed: Some([ApiError { error_code: BadRequest, message: \"The path is not closed. Solid2D construction requires a closed path!\" }])" }"#,
r#"engine: KclErrorDetails { source_ranges: [SourceRange([222, 235])], message: "Modeling command failed: [ApiError { error_code: BadRequest, message: \"The path is not closed. Solid2D construction requires a closed path!\" }]" }"#,
);
}
@ -1614,7 +1614,7 @@ const sketch001 = startSketchOn(box, "revolveAxis")
assert!(result.is_err());
assert_eq!(
result.err().unwrap().to_string(),
r#"engine: KclErrorDetails { source_ranges: [SourceRange([349, 409])], message: "Modeling command failed: Some([ApiError { error_code: InternalEngine, message: \"Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis\" }])" }"#
r#"engine: KclErrorDetails { source_ranges: [SourceRange([349, 409])], message: "Modeling command failed: [ApiError { error_code: InternalEngine, message: \"Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis\" }]" }"#
);
}
@ -1873,7 +1873,7 @@ const bracket = startSketchOn('XY')
assert!(result.is_err());
assert_eq!(
result.err().unwrap().to_string(),
r#"engine: KclErrorDetails { source_ranges: [SourceRange([1443, 1443])], message: "Modeling command failed: Some([ApiError { error_code: BadRequest, message: \"Fillet failed\" }])" }"#
r#"engine: KclErrorDetails { source_ranges: [SourceRange([1443, 1443])], message: "Modeling command failed: [ApiError { error_code: BadRequest, message: \"Fillet failed\" }]" }"#
);
}