Add batch support to current KCL implementation (#1871)

* wip

* wip

* updates

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

* more batch shit

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

* fixes

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

* push up mods

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

* go back to batch id

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

* add unlocks back

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

* remove logging

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

* port to wasm

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

* use a trait

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

* updates

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

* snapshojts

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

* artifact map fix

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

* cleanup

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

* remove the blur

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

* hacks on hacks

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

* artifact map clean up

* tweak

* fix so extrudes work

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

* start of id to source range infra

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

* basic map to ids and source ranges

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

* make typescript happy

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

* flush at end of exxecute

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

* small thing for flush

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

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

* trigger ci

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: 49lf <ircsurfer33@gmail.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Jess Frazelle
2024-03-23 15:45:55 -07:00
committed by GitHub
parent 846fc99bbc
commit 0983dcca22
53 changed files with 866 additions and 836 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -4,7 +4,6 @@ import { getNormalisedCoordinates } from '../lib/utils'
import Loading from './Loading'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useModelingContext } from 'hooks/useModelingContext'
import { useKclContext } from 'lang/KclProvider'
import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp'
import { NetworkHealthState, useNetworkStatus } from './NetworkHealthIndicator'
import { butName } from 'lib/cameraControls'
@ -29,7 +28,6 @@ export const Stream = ({ className = '' }: { className?: string }) => {
}))
const { settings } = useSettingsAuthContext()
const { state } = useModelingContext()
const { isExecuting } = useKclContext()
const { overallState } = useNetworkStatus()
const isNetworkOkay = overallState === NetworkHealthState.Ok
@ -103,7 +101,7 @@ export const Stream = ({ className = '' }: { className?: string }) => {
controls={false}
onPlay={() => setIsLoading(false)}
onMouseMoveCapture={handleMouseMove}
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
className="w-full cursor-pointer h-full"
disablePictureInPicture
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
id="video-stream"

View File

@ -13,10 +13,16 @@ interface CommandInfo {
range: SourceRange
pathToNode: PathToNode
parentId?: string
additionalData?: {
additionalData?:
| {
type: 'cap'
info: 'start' | 'end'
}
| {
type: 'batch-ids'
ids: string[]
info?: null
}
}
type WebSocketResponse = Models['OkWebSocketResponseData_type']
@ -792,7 +798,7 @@ failed cmd type was ${artifactThatFailed?.commandType}`
export type EngineCommand = Models['WebSocketRequest_type']
type ModelTypes = Models['OkModelingCmdResponse_type']['type']
type CommandTypes = Models['ModelingCmd_type']['type']
type CommandTypes = Models['ModelingCmd_type']['type'] | 'batch'
type UnreliableResponses = Extract<
Models['OkModelingCmdResponse_type'],
@ -1073,6 +1079,27 @@ export class EngineCommandManager {
}
const modelingResponse = message.data.modeling_response
const command = this.artifactMap[id]
if (
command?.type === 'pending' &&
command.commandType === 'batch' &&
command?.additionalData?.type === 'batch-ids'
) {
command.additionalData.ids.forEach((id) => {
this.handleModelingCommand(message, id)
})
// 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
delete this.artifactMap[id]
resolve({
id,
commandType: command.commandType,
range: command.range,
data: modelingResponse,
raw: message,
})
return
}
const sceneCommand = this.sceneCommandArtifacts[id]
this.addCommandLog({
type: 'receive-reliable',
@ -1400,11 +1427,13 @@ export class EngineCommandManager {
range,
command,
ast,
idToRangeMap,
}: {
id: string
range: SourceRange
command: EngineCommand | string
ast: Program
idToRangeMap?: { [key: string]: SourceRange }
}): Promise<any> {
if (this.engineConnection === undefined) {
return Promise.resolve()
@ -1427,10 +1456,22 @@ export class EngineCommandManager {
this.engineConnection?.send(command)
if (typeof command !== 'string' && command.type === 'modeling_cmd_req') {
return this.handlePendingCommand(id, command?.cmd, ast, range)
} else if (
typeof command !== 'string' &&
command.type === 'modeling_cmd_batch_req'
) {
return this.handlePendingBatchCommand(id, command.requests, idToRangeMap)
} else if (typeof command === 'string') {
const parseCommand: EngineCommand = JSON.parse(command)
if (parseCommand.type === 'modeling_cmd_req')
if (parseCommand.type === 'modeling_cmd_req') {
return this.handlePendingCommand(id, parseCommand?.cmd, ast, range)
} else if (parseCommand.type === 'modeling_cmd_batch_req') {
return this.handlePendingBatchCommand(
id,
parseCommand.requests,
idToRangeMap
)
}
}
throw Error('shouldnt reach here')
}
@ -1491,10 +1532,48 @@ export class EngineCommandManager {
}
return promise
}
async handlePendingBatchCommand(
id: string,
commands: Models['ModelingCmdReq_type'][],
idToRangeMap?: { [key: string]: SourceRange },
ast?: Program,
range?: SourceRange
) {
let resolve: (val: any) => void = () => {}
const promise = new Promise((_resolve, reject) => {
resolve = _resolve
})
if (!idToRangeMap) {
throw new Error('idToRangeMap is required for batch commands')
}
// Add the overall batch command to the artifact map just so we can track all of the
// individual commands that are part of the batch.
// we'll delete this artifact once all of the individual commands have been processed.
this.artifactMap[id] = {
range: range || [0, 0],
pathToNode: [],
type: 'pending',
commandType: 'batch',
additionalData: { type: 'batch-ids', ids: commands.map((c) => c.cmd_id) },
parentId: undefined,
promise,
resolve,
}
await Promise.all(
commands.map((c) =>
this.handlePendingCommand(c.cmd_id, c.cmd, ast, idToRangeMap[c.cmd_id])
)
)
return promise
}
sendModelingCommandFromWasm(
id: string,
rangeStr: string,
commandStr: string
commandStr: string,
idToRangeStr: string
): Promise<any> {
if (this.engineConnection === undefined) {
return Promise.resolve()
@ -1512,6 +1591,8 @@ export class EngineCommandManager {
throw new Error('commandStr is undefined')
}
const range: SourceRange = JSON.parse(rangeStr)
const idToRangeMap: { [key: string]: SourceRange } =
JSON.parse(idToRangeStr)
// We only care about the modeling command response.
return this.sendModelingCommand({
@ -1519,6 +1600,7 @@ export class EngineCommandManager {
range,
command: commandStr,
ast: this.getAst(),
idToRangeMap,
}).then(({ raw }) => JSON.stringify(raw))
}
commandResult(id: string): Promise<any> {

1175
src/wasm-lib/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -59,7 +59,7 @@ members = [
]
[workspace.dependencies]
kittycad = { version = "0.2.61", default-features = false, features = ["js", "requests"] }
kittycad = { version = "0.2.62", default-features = false, features = ["js", "requests"] }
kittycad-execution-plan = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
kittycad-execution-plan-macros = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
kittycad-execution-plan-traits = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }

View File

@ -1,7 +1,7 @@
[package]
name = "derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.11"
version = "0.1.12"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -23,6 +23,7 @@ serde_tokenstream = "0.2"
syn = { version = "2.0.53", features = ["full"] }
[dev-dependencies]
anyhow = "1.0.81"
expectorate = "1.1.0"
openapitor = { git = "https://github.com/KittyCAD/kittycad.rs", branch = "main" }
pretty_assertions = "1.4.0"
rustfmt-wrapper = "0.2.1"

View File

@ -764,7 +764,11 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
// Create the client.
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
// Set a local engine address if it's set.
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))

View File

@ -1,7 +1,27 @@
use anyhow::Result;
use quote::quote;
use crate::{do_stdlib, parse_array_type};
fn clean_text(s: &str) -> String {
// Add newlines after end-braces at <= two levels of indentation.
if cfg!(not(windows)) {
let regex = regex::Regex::new(r"(})(\n\s{0,8}[^} ])").unwrap();
regex.replace_all(s, "$1\n$2").to_string()
} else {
let regex = regex::Regex::new(r"(})(\r\n\s{0,8}[^} ])").unwrap();
regex.replace_all(s, "$1\r\n$2").to_string()
}
}
/// Format a TokenStream as a string and run `rustfmt` on the result.
fn get_text_fmt(output: &proc_macro2::TokenStream) -> Result<String> {
// Format the file with rustfmt.
let content = rustfmt_wrapper::rustfmt(output).unwrap();
Ok(clean_text(&content))
}
#[test]
fn test_get_inner_array_type() {
for (expected, input) in [
@ -47,7 +67,7 @@ fn test_stdlib_line_to() {
let _expected = quote! {};
assert!(errors.is_empty());
expectorate::assert_contents("tests/lineTo.gen", &openapitor::types::get_text_fmt(&item).unwrap());
expectorate::assert_contents("tests/lineTo.gen", &get_text_fmt(&item).unwrap());
}
#[test]
@ -88,7 +108,7 @@ fn test_stdlib_min() {
let _expected = quote! {};
assert!(errors.is_empty());
expectorate::assert_contents("tests/min.gen", &openapitor::types::get_text_fmt(&item).unwrap());
expectorate::assert_contents("tests/min.gen", &get_text_fmt(&item).unwrap());
}
#[test]
@ -115,7 +135,7 @@ fn test_stdlib_show() {
let _expected = quote! {};
assert!(errors.is_empty());
expectorate::assert_contents("tests/show.gen", &openapitor::types::get_text_fmt(&item).unwrap());
expectorate::assert_contents("tests/show.gen", &get_text_fmt(&item).unwrap());
}
#[test]
@ -143,7 +163,7 @@ fn test_stdlib_box() {
let _expected = quote! {};
assert!(errors.is_empty());
expectorate::assert_contents("tests/box.gen", &openapitor::types::get_text_fmt(&item).unwrap());
expectorate::assert_contents("tests/box.gen", &get_text_fmt(&item).unwrap());
}
#[test]
@ -170,7 +190,7 @@ fn test_stdlib_option() {
.unwrap();
assert!(errors.is_empty());
expectorate::assert_contents("tests/option.gen", &openapitor::types::get_text_fmt(&item).unwrap());
expectorate::assert_contents("tests/option.gen", &get_text_fmt(&item).unwrap());
}
#[test]
@ -203,7 +223,7 @@ fn test_stdlib_array() {
.unwrap();
assert!(errors.is_empty());
expectorate::assert_contents("tests/array.gen", &openapitor::types::get_text_fmt(&item).unwrap());
expectorate::assert_contents("tests/array.gen", &get_text_fmt(&item).unwrap());
}
#[test]
@ -230,10 +250,7 @@ fn test_stdlib_option_input_format() {
.unwrap();
assert!(errors.is_empty());
expectorate::assert_contents(
"tests/option_input_format.gen",
&openapitor::types::get_text_fmt(&item).unwrap(),
);
expectorate::assert_contents("tests/option_input_format.gen", &get_text_fmt(&item).unwrap());
}
#[test]
@ -260,10 +277,7 @@ fn test_stdlib_return_vec_sketch_group() {
.unwrap();
assert!(errors.is_empty());
expectorate::assert_contents(
"tests/return_vec_sketch_group.gen",
&openapitor::types::get_text_fmt(&item).unwrap(),
);
expectorate::assert_contents("tests/return_vec_sketch_group.gen", &get_text_fmt(&item).unwrap());
}
#[test]
@ -290,10 +304,7 @@ fn test_stdlib_return_vec_box_sketch_group() {
.unwrap();
assert!(errors.is_empty());
expectorate::assert_contents(
"tests/return_vec_box_sketch_group.gen",
&openapitor::types::get_text_fmt(&item).unwrap(),
);
expectorate::assert_contents("tests/return_vec_box_sketch_group.gen", &get_text_fmt(&item).unwrap());
}
#[test]
@ -326,10 +337,7 @@ fn test_stdlib_doc_comment_with_code() {
.unwrap();
assert!(errors.is_empty());
expectorate::assert_contents(
"tests/doc_comment_with_code.gen",
&openapitor::types::get_text_fmt(&item).unwrap(),
);
expectorate::assert_contents("tests/doc_comment_with_code.gen", &get_text_fmt(&item).unwrap());
}
#[test]
@ -364,7 +372,7 @@ fn test_stdlib_doc_comment_with_code_on_ignored_function() {
assert!(errors.is_empty());
expectorate::assert_contents(
"tests/doc_comment_with_code_on_ignored_function.gen",
&openapitor::types::get_text_fmt(&item).unwrap(),
&get_text_fmt(&item).unwrap(),
);
}

View File

@ -15,7 +15,11 @@ mod test_examples_show {
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))
@ -55,7 +59,11 @@ mod test_examples_show {
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))

View File

@ -15,7 +15,11 @@ mod test_examples_show {
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))

View File

@ -15,7 +15,11 @@ mod test_examples_my_func {
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))
@ -55,7 +59,11 @@ mod test_examples_my_func {
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))

View File

@ -16,7 +16,11 @@ mod test_examples_import {
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))
@ -57,7 +61,11 @@ mod test_examples_import {
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))

View File

@ -15,7 +15,11 @@ mod test_examples_line_to {
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))
@ -55,7 +59,11 @@ mod test_examples_line_to {
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))

View File

@ -15,7 +15,11 @@ mod test_examples_min {
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))
@ -55,7 +59,11 @@ mod test_examples_min {
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))

View File

@ -15,7 +15,11 @@ mod test_examples_show {
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))

View File

@ -16,7 +16,11 @@ mod test_examples_import {
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))

View File

@ -16,7 +16,11 @@ mod test_examples_import {
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))

View File

@ -16,7 +16,11 @@ mod test_examples_import {
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))

View File

@ -15,7 +15,11 @@ mod test_examples_show {
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
.commands_ws(None, None, None, None, None, Some(false))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -18,8 +18,8 @@ chrono = "0.4.35"
clap = { version = "4.5.3", features = ["cargo", "derive", "env", "unicode"], optional = true }
dashmap = "5.5.3"
databake = { version = "0.1.7", features = ["derive"] }
derive-docs = { version = "0.1.11" }
#derive-docs = { path = "../derive-docs" }
#derive-docs = { version = "0.1.12" }
derive-docs = { path = "../derive-docs" }
futures = { version = "0.3.30" }
gltf-json = "1.4.0"
kittycad = { workspace = true }

View File

@ -75,6 +75,7 @@ pub async fn modify_ast_for_sketch(
// Let's get the path info.
let resp = engine
.send_modeling_cmd(
false,
uuid::Uuid::new_v4(),
SourceRange::default(),
ModelingCmd::PathGetInfo { path_id: sketch_id },
@ -99,6 +100,7 @@ pub async fn modify_ast_for_sketch(
for segment in &path_info.segments {
if let Some(command_id) = &segment.command_id {
let h = engine.send_modeling_cmd(
false,
uuid::Uuid::new_v4(),
SourceRange::default(),
ModelingCmd::CurveGetControlPoints { curve_id: *command_id },

View File

@ -29,6 +29,7 @@ pub struct EngineConnection {
responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>>,
tcp_read_handle: Arc<TcpReadHandle>,
socket_health: Arc<Mutex<SocketHealth>>,
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
}
pub struct TcpRead {
@ -154,27 +155,30 @@ impl EngineConnection {
}),
responses,
socket_health,
batch: Arc::new(Mutex::new(Vec::new())),
})
}
}
#[async_trait::async_trait]
impl EngineManager for EngineConnection {
async fn send_modeling_cmd(
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>> {
self.batch.clone()
}
async fn inner_send_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
cmd: kittycad::types::ModelingCmd,
cmd: kittycad::types::WebSocketRequest,
_id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>,
) -> Result<OkWebSocketResponseData, KclError> {
let (tx, rx) = oneshot::channel();
// Send the request to the engine, via the actor.
self.engine_req_tx
.send(ToEngineReq {
req: WebSocketRequest::ModelingCmdReq {
cmd: cmd.clone(),
cmd_id: id,
},
req: cmd.clone(),
request_sent: tx,
})
.await

View File

@ -1,27 +1,38 @@
//! Functions for setting up our WebSocket and WebRTC connections for communications with the
//! engine.
use std::sync::{Arc, Mutex};
use anyhow::Result;
use kittycad::types::OkWebSocketResponseData;
use kittycad::types::{OkWebSocketResponseData, WebSocketRequest};
use crate::errors::KclError;
#[derive(Debug, Clone)]
pub struct EngineConnection {}
pub struct EngineConnection {
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
}
impl EngineConnection {
pub async fn new() -> Result<EngineConnection> {
Ok(EngineConnection {})
Ok(EngineConnection {
batch: Arc::new(Mutex::new(Vec::new())),
})
}
}
#[async_trait::async_trait]
impl crate::engine::EngineManager for EngineConnection {
async fn send_modeling_cmd(
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>> {
self.batch.clone()
}
async fn inner_send_modeling_cmd(
&self,
_id: uuid::Uuid,
_source_range: crate::executor::SourceRange,
_cmd: kittycad::types::ModelingCmd,
_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 {},

View File

@ -1,6 +1,6 @@
//! Functions for setting up our WebSocket and WebRTC connections for communications with the
//! engine.
use std::sync::Arc;
use std::sync::{Arc, Mutex};
use anyhow::Result;
use kittycad::types::WebSocketRequest;
@ -19,12 +19,14 @@ extern "C" {
id: String,
rangeStr: String,
cmdStr: String,
idToRangeStr: String,
) -> Result<js_sys::Promise, js_sys::Error>;
}
#[derive(Debug, Clone)]
pub struct EngineConnection {
manager: Arc<EngineCommandManager>,
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
}
// Safety: WebAssembly will only ever run in a single-threaded context.
@ -35,17 +37,23 @@ impl EngineConnection {
pub async fn new(manager: EngineCommandManager) -> Result<EngineConnection, JsValue> {
Ok(EngineConnection {
manager: Arc::new(manager),
batch: Arc::new(Mutex::new(Vec::new())),
})
}
}
#[async_trait::async_trait]
impl crate::engine::EngineManager for EngineConnection {
async fn send_modeling_cmd(
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>> {
self.batch.clone()
}
async fn inner_send_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
cmd: kittycad::types::ModelingCmd,
cmd: kittycad::types::WebSocketRequest,
id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>,
) -> Result<kittycad::types::OkWebSocketResponseData, KclError> {
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
KclError::Engine(KclErrorDetails {
@ -53,17 +61,22 @@ impl crate::engine::EngineManager for EngineConnection {
source_ranges: vec![source_range],
})
})?;
let ws_msg = WebSocketRequest::ModelingCmdReq { cmd, cmd_id: id };
let cmd_str = serde_json::to_string(&ws_msg).map_err(|e| {
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)
.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(),

View File

@ -8,13 +8,177 @@ pub mod conn_mock;
#[cfg(feature = "engine")]
pub mod conn_wasm;
use std::sync::{Arc, Mutex};
use kittycad::types::{OkWebSocketResponseData, WebSocketRequest};
use crate::errors::{KclError, KclErrorDetails};
#[async_trait::async_trait]
pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
/// Get the batch of commands to be sent to the engine.
fn batch(&self) -> Arc<Mutex<Vec<(kittycad::types::WebSocketRequest, crate::executor::SourceRange)>>>;
/// Send a modeling command and wait for the response message.
async fn send_modeling_cmd(
async fn inner_send_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
cmd: kittycad::types::ModelingCmd,
cmd: kittycad::types::WebSocketRequest,
id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>,
) -> Result<kittycad::types::OkWebSocketResponseData, crate::errors::KclError>;
async fn send_modeling_cmd(
&self,
flush_batch: bool,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
cmd: kittycad::types::ModelingCmd,
) -> Result<kittycad::types::OkWebSocketResponseData, crate::errors::KclError> {
let req = WebSocketRequest::ModelingCmdReq {
cmd: cmd.clone(),
cmd_id: id,
};
if !flush_batch {
self.batch().lock().unwrap().push((req.clone(), 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) && self.batch().lock().unwrap().len() == 1)
|| flush_batch
|| 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 {},
});
}
// Flush the batch queue.
self.flush_batch(source_range).await
}
/// Force flush the batch queue.
async fn flush_batch(
&self,
source_range: crate::executor::SourceRange,
) -> Result<kittycad::types::OkWebSocketResponseData, crate::errors::KclError> {
// Return early if we have no commands to send.
if self.batch().lock().unwrap().is_empty() {
return Ok(OkWebSocketResponseData::Modeling {
modeling_response: kittycad::types::OkModelingCmdResponse::Empty {},
});
}
let batched_requests = WebSocketRequest::ModelingCmdBatchReq {
requests: self.batch().lock().unwrap().iter().fold(vec![], |mut acc, (val, _)| {
let WebSocketRequest::ModelingCmdReq { cmd, cmd_id } = val else {
return acc;
};
acc.push(kittycad::types::ModelingCmdReq {
cmd: cmd.clone(),
cmd_id: *cmd_id,
});
acc
}),
batch_id: uuid::Uuid::new_v4(),
};
let final_req = if self.batch().lock().unwrap().len() == 1 {
// We can unwrap here because we know the batch has only one element.
self.batch().lock().unwrap().first().unwrap().0.clone()
} else {
batched_requests
};
// Create the map of original command IDs to source range.
// This is for the wasm side, kurt needs it for selections.
let mut id_to_source_range = std::collections::HashMap::new();
for (req, range) in self.batch().lock().unwrap().iter() {
match req {
WebSocketRequest::ModelingCmdReq { cmd: _, cmd_id } => {
id_to_source_range.insert(*cmd_id, *range);
}
_ => {
return Err(KclError::Engine(KclErrorDetails {
message: format!("The request is not a modeling command: {:?}", req),
source_ranges: vec![*range],
}));
}
}
}
// Throw away the old batch queue.
self.batch().lock().unwrap().clear();
// We pop off the responses to cleanup our mappings.
let id_final = match final_req {
WebSocketRequest::ModelingCmdBatchReq { requests: _, batch_id } => batch_id,
WebSocketRequest::ModelingCmdReq { cmd: _, cmd_id } => cmd_id,
_ => {
return 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
}
}
pub fn is_cmd_with_return_values(cmd: &kittycad::types::ModelingCmd) -> bool {
let (kittycad::types::ModelingCmd::Export { .. }
| kittycad::types::ModelingCmd::Extrude { .. }
| 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::DefaultCameraGetSettings { .. }
| kittycad::types::ModelingCmd::DefaultCameraZoom { .. }
| 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::Solid3DGetExtrusionFaceInfo { .. }) = cmd
else {
return false;
};
true
}

View File

@ -1010,6 +1010,7 @@ pub async fn execute(
// Before we even start executing the program, set the units.
ctx.engine
.send_modeling_cmd(
false,
uuid::Uuid::new_v4(),
SourceRange::default(),
kittycad::types::ModelingCmd::SetSceneUnits {
@ -1221,6 +1222,9 @@ pub async fn execute(
}
}
// Flush the batch queue.
ctx.engine.flush_batch(SourceRange::default()).await?;
Ok(memory.clone())
}

View File

@ -206,7 +206,10 @@ impl Args {
id: uuid::Uuid,
cmd: kittycad::types::ModelingCmd,
) -> Result<OkWebSocketResponseData, KclError> {
self.ctx.engine.send_modeling_cmd(id, self.source_range, cmd).await
self.ctx
.engine
.send_modeling_cmd(false, id, self.source_range, cmd)
.await
}
fn make_user_val_from_json(&self, j: serde_json::Value) -> Result<MemoryItem, KclError> {

View File

@ -21,9 +21,11 @@ async fn execute_and_snapshot(code: &str, units: kittycad::types::UnitLength) ->
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
// Create the client.
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
// uncomment to use a local server
//client.set_base_url("http://system76-pc:8080/");
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
// Set a local engine address if it's set.
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
@ -45,6 +47,7 @@ async fn execute_and_snapshot(code: &str, units: kittycad::types::UnitLength) ->
ctx.engine
.send_modeling_cmd(
false,
uuid::Uuid::new_v4(),
kcl_lib::executor::SourceRange::default(),
kittycad::types::ModelingCmd::DefaultCameraLookAt {
@ -60,6 +63,7 @@ async fn execute_and_snapshot(code: &str, units: kittycad::types::UnitLength) ->
let resp = ctx
.engine
.send_modeling_cmd(
false,
uuid::Uuid::new_v4(),
kcl_lib::executor::SourceRange::default(),
kittycad::types::ModelingCmd::TakeSnapshot {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -25,7 +25,11 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, uuid
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
// Create the client.
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
// Set a local engine address if it's set.
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
client.set_base_url(addr);
}
let ws = client
.modeling()
@ -49,6 +53,7 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, uuid
let plane_id = uuid::Uuid::new_v4();
ctx.engine
.send_modeling_cmd(
false,
plane_id,
SourceRange::default(),
ModelingCmd::MakePlane {
@ -67,6 +72,7 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, uuid
// You can however get path info without sketch mode.
ctx.engine
.send_modeling_cmd(
false,
uuid::Uuid::new_v4(),
SourceRange::default(),
ModelingCmd::SketchModeEnable {
@ -82,6 +88,7 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, uuid
// We can't get control points of an existing sketch without being in edit mode.
ctx.engine
.send_modeling_cmd(
false,
uuid::Uuid::new_v4(),
SourceRange::default(),
ModelingCmd::EditModeEnter { target: sketch_id },