Compare commits

..

7 Commits

Author SHA1 Message Date
043bfe263f Update Cargo.lock 2024-05-22 12:24:13 -07:00
66064dca58 Merge branch 'main' into coredump-enginecommandmanager 2024-05-22 11:51:20 -07:00
cdb12f8e6f Merge branch 'main' into coredump-enginecommandmanager 2024-05-22 10:51:59 -07:00
9f43dc5fc8 Update Cargo.lock 2024-05-22 10:50:30 -07:00
36dc589b32 Finish Rust implementation of ClientState 2024-05-22 10:50:10 -07:00
e7d6bccc60 Merge branch 'main' into coredump-enginecommandmanager 2024-05-21 12:30:21 -07:00
a76dcd76fc Implement structs for clientState
Including engineCommandManager and its engineConnection
2024-05-20 23:26:02 -07:00
15 changed files with 159 additions and 235 deletions

4
src-tauri/Cargo.lock generated
View File

@ -6060,7 +6060,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]] [[package]]
name = "ts-rs" name = "ts-rs"
version = "8.1.0" version = "8.1.0"
source = "git+https://github.com/Aleph-Alpha/ts-rs#f898578d80d3e2a54080c1c046c45f9eaa2435c3" source = "git+https://github.com/Aleph-Alpha/ts-rs#badbac08e61e65b312880aa64e9ece2976f1bbef"
dependencies = [ dependencies = [
"chrono", "chrono",
"thiserror", "thiserror",
@ -6072,7 +6072,7 @@ dependencies = [
[[package]] [[package]]
name = "ts-rs-macros" name = "ts-rs-macros"
version = "8.1.0" version = "8.1.0"
source = "git+https://github.com/Aleph-Alpha/ts-rs#f898578d80d3e2a54080c1c046c45f9eaa2435c3" source = "git+https://github.com/Aleph-Alpha/ts-rs#badbac08e61e65b312880aa64e9ece2976f1bbef"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -56,33 +56,6 @@
] ]
}, },
"shell:allow-open", "shell:allow-open",
{
"identifier": "shell:allow-execute",
"allow": [
{
"name": "open",
"cmd": "open",
"args": [
"-R",
{
"validator": "\\S+"
}
],
"sidecar": false
},
{
"name": "explorer",
"cmd": "explorer",
"args": [
"/select",
{
"validator": "\\S+"
}
],
"sidecar": false
}
]
},
"dialog:allow-open", "dialog:allow-open",
"dialog:allow-save", "dialog:allow-save",
"dialog:allow-message", "dialog:allow-message",

View File

@ -18,6 +18,7 @@ use oauth2::TokenResponse;
use tauri::{ipc::InvokeError, Manager}; use tauri::{ipc::InvokeError, Manager};
use tauri_plugin_cli::CliExt; use tauri_plugin_cli::CliExt;
use tauri_plugin_shell::ShellExt; use tauri_plugin_shell::ShellExt;
use tokio::process::Command;
const DEFAULT_HOST: &str = "https://api.zoo.dev"; const DEFAULT_HOST: &str = "https://api.zoo.dev";
const SETTINGS_FILE_NAME: &str = "settings.toml"; const SETTINGS_FILE_NAME: &str = "settings.toml";
@ -331,20 +332,10 @@ async fn get_user(token: &str, hostname: &str) -> Result<kittycad::types::User,
/// From this GitHub comment: https://github.com/tauri-apps/tauri/issues/4062#issuecomment-1338048169 /// From this GitHub comment: https://github.com/tauri-apps/tauri/issues/4062#issuecomment-1338048169
/// But with the Linux support removed since we don't need it for now. /// But with the Linux support removed since we don't need it for now.
#[tauri::command] #[tauri::command]
fn show_in_folder(app: tauri::AppHandle, path: &str) -> Result<(), InvokeError> { fn show_in_folder(path: &str) -> Result<(), InvokeError> {
// Check if the file exists.
// If it doesn't, return an error.
if !Path::new(path).exists() {
return Err(InvokeError::from_anyhow(anyhow::anyhow!(
"The file `{}` does not exist",
path
)));
}
#[cfg(not(unix))] #[cfg(not(unix))]
{ {
app.shell() Command::new("explorer")
.command("explorer")
.args(["/select,", path]) // The comma after select is not a typo .args(["/select,", path]) // The comma after select is not a typo
.spawn() .spawn()
.map_err(|e| InvokeError::from_anyhow(e.into()))?; .map_err(|e| InvokeError::from_anyhow(e.into()))?;
@ -352,8 +343,7 @@ fn show_in_folder(app: tauri::AppHandle, path: &str) -> Result<(), InvokeError>
#[cfg(unix)] #[cfg(unix)]
{ {
app.shell() Command::new("open")
.command("open")
.args(["-R", path]) .args(["-R", path])
.spawn() .spawn()
.map_err(|e| InvokeError::from_anyhow(e.into()))?; .map_err(|e| InvokeError::from_anyhow(e.into()))?;

View File

@ -1,7 +1,6 @@
import { parse, recast, initPromise } from './wasm' import { parse, recast, initPromise } from './wasm'
import { import {
findAllPreviousVariables, findAllPreviousVariables,
findUnusedVariables,
isNodeSafeToReplace, isNodeSafeToReplace,
isTypeInValue, isTypeInValue,
getNodePathFromSourceRange, getNodePathFromSourceRange,
@ -61,91 +60,6 @@ const variableBelowShouldNotBeIncluded = 3
}) })
}) })
describe('Test findUnusedVariables', () => {
it('should find unused variable in common kcl code', () => {
// example code
const code = `
const xRel001 = -20
const xRel002 = -50
const part001 = startSketchOn('-XZ')
|> startProfileAt([175.73, 109.38], %)
|> line([xRel001, 178.25], %)
|> line([-265.39, -87.86], %)
|> tangentialArcTo([543.32, -355.04], %)
`
// parse into ast
const ast = parse(code)
// find unused variables
const unusedVariables = findUnusedVariables(ast)
// check wether unused variables match the expected result
expect(
unusedVariables
.map((node) => node.declarations.map((decl) => decl.id.name))
.flat()
).toEqual(['xRel002'])
})
it("should not find used variable, even if it's heavy nested", () => {
// example code
const code = `
const deepWithin = 1
const veryNested = [
{ key: [{ key2: max(5, deepWithin) }] }
]
`
// parse into ast
const ast = parse(code)
// find unused variables
const unusedVariables = findUnusedVariables(ast)
// check wether unused variables match the expected result
expect(
unusedVariables.find((node) =>
node.declarations.find((decl) => decl.id.name === 'deepWithin')
)
).toBeFalsy()
})
it('should not find used variable, even if used in a closure', () => {
// example code
const code = `const usedInClosure = 1
fn myFunction = () => {
return usedInClosure
}
`
// parse into ast
const ast = parse(code)
// find unused variables
const unusedVariables = findUnusedVariables(ast)
// check wether unused variables match the expected result
expect(
unusedVariables.find((node) =>
node.declarations.find((decl) => decl.id.name === 'usedInClosure')
)
).toBeFalsy()
})
// TODO: The commented code in the below does not even parse due to a KCL bug
// When it does parse correctly we'de expect 'a' to be defined but unused
// as it's shadowed by the 'a' in the inner scope
// it('should find unused variable when the same identifier is used in deeper scope', () => {
// const code = `const a = 1
// const b = 2
// fn (a) => {
// return a + 1
// }
// const myVar = b + 5`
// // parse into ast
// const ast = parse(code)
// console.log('ast', ast)
// // find unused variables
// const unusedVariables = findUnusedVariables(ast)
// console.log('unusedVariables', unusedVariables)
// // check wether unused variables match the expected result
// expect(
// unusedVariables
// .map((node) => node.declarations.map((decl) => decl.id.name))
// .flat()
// ).toEqual(['a'])
// })
})
describe('testing argIsNotIdentifier', () => { describe('testing argIsNotIdentifier', () => {
const code = `const part001 = startSketchOn('XY') const code = `const part001 = startSketchOn('XY')
|> startProfileAt([-1.2, 4.83], %) |> startProfileAt([-1.2, 4.83], %)

View File

@ -392,68 +392,6 @@ export function findAllPreviousVariables(
} }
} }
export function findUnusedVariables(ast: Program): Array<VariableDeclaration> {
const declaredVariables = new Map<string, VariableDeclarator>() // Map to store declared variables
const usedVariables = new Set<string>() // Set to track used variables
// 1. Traverse and populate
ast.body.forEach((node) => {
traverse(node, {
enter(node) {
if (node.type === 'VariableDeclarator') {
// if node is a VariableDeclarator,
// add it to declaredVariables
declaredVariables.set(node.id.name, node)
} else if (node.type === 'Identifier') {
// if the node is Identifier, (use of a variable)
// check if it is a declared value,
// just in case...
// to be sure it's a part of the declared variables
if (declaredVariables.has(node.name)) {
// if yes - mark it as used
usedVariables.add(node.name)
}
} else if (node.type === 'VariableDeclaration') {
// check if the declaration is model-defining (contains PipeExpression)
const isModelDefining = node.declarations.some(
(decl) => decl.init?.type === 'PipeExpression'
)
if (isModelDefining) {
// If it is, mark all contained variables as used
node.declarations.forEach((decl) => {
usedVariables.add(decl.id.name)
})
}
}
},
})
})
// 2. Remove used variables from declaredVariables
usedVariables.forEach((name) => {
declaredVariables.delete(name)
})
// 3. collect unused VariableDeclarations
const unusedVariableDeclarations: Array<VariableDeclaration> = []
ast.body.forEach((node) => {
if (node.type === 'VariableDeclaration') {
const unusedDeclarators = node.declarations.filter((declarator) =>
declaredVariables.has(declarator.id.name)
)
if (unusedDeclarators.length > 0) {
unusedVariableDeclarations.push({
...node,
declarations: unusedDeclarators,
})
}
}
})
// 4. Return the unused variables
return unusedVariableDeclarations
}
type ReplacerFn = (_ast: Program, varName: string) => { modifiedAst: Program } type ReplacerFn = (_ast: Program, varName: string) => { modifiedAst: Program }
export function isNodeSafeToReplace( export function isNodeSafeToReplace(

View File

@ -55,6 +55,10 @@ impl CoreDump for CoreDumper {
Ok(crate::coredump::WebrtcStats::default()) Ok(crate::coredump::WebrtcStats::default())
} }
async fn get_client_state(&self) -> Result<crate::coredump::ClientState> {
Ok(crate::coredump::ClientState::default())
}
async fn screenshot(&self) -> Result<String> { async fn screenshot(&self) -> Result<String> {
// Take a screenshot of the engine. // Take a screenshot of the engine.
todo!() todo!()

View File

@ -27,6 +27,8 @@ pub trait CoreDump: Clone {
async fn get_webrtc_stats(&self) -> Result<WebrtcStats>; async fn get_webrtc_stats(&self) -> Result<WebrtcStats>;
async fn get_client_state(&self) -> Result<ClientState>;
/// Return a screenshot of the app. /// Return a screenshot of the app.
async fn screenshot(&self) -> Result<String>; async fn screenshot(&self) -> Result<String>;
@ -64,6 +66,7 @@ pub trait CoreDump: Clone {
let webrtc_stats = self.get_webrtc_stats().await?; let webrtc_stats = self.get_webrtc_stats().await?;
let os = self.os().await?; let os = self.os().await?;
let screenshot_url = self.upload_screenshot().await?; let screenshot_url = self.upload_screenshot().await?;
let client_state = self.get_client_state().await?;
let mut app_info = AppInfo { let mut app_info = AppInfo {
version: self.version()?, version: self.version()?,
@ -74,6 +77,7 @@ pub trait CoreDump: Clone {
webrtc_stats, webrtc_stats,
github_issue_url: None, github_issue_url: None,
pool: self.pool()?, pool: self.pool()?,
client_state,
}; };
app_info.set_github_issue_url(&screenshot_url)?; app_info.set_github_issue_url(&screenshot_url)?;
@ -109,6 +113,9 @@ pub struct AppInfo {
/// Engine pool the client is connected to. /// Engine pool the client is connected to.
pub pool: String, pub pool: String,
/// The client state (singletons and xstate)
pub client_state: ClientState,
} }
impl AppInfo { impl AppInfo {
@ -197,3 +204,23 @@ pub struct WebrtcStats {
/// Packet jitter for this synchronizing source, measured in seconds. /// Packet jitter for this synchronizing source, measured in seconds.
pub jitter: f32, pub jitter: f32,
} }
/// Client State Structure
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
pub struct ClientState {
pub engine_command_manager: EngineCommandManager,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
pub struct EngineCommandManager {
pub engine_connection: EngineConnection,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct EngineConnection {
}

View File

@ -33,6 +33,9 @@ extern "C" {
#[wasm_bindgen(method, js_name = screenshot, catch)] #[wasm_bindgen(method, js_name = screenshot, catch)]
fn screenshot(this: &CoreDumpManager) -> Result<js_sys::Promise, js_sys::Error>; fn screenshot(this: &CoreDumpManager) -> Result<js_sys::Promise, js_sys::Error>;
#[wasm_bindgen(method, js_name = getWebrtcStats, catch)]
fn get_client_state(this: &CoreDumpManager) -> Result<js_sys::Promise, js_sys::Error>;
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -140,4 +143,25 @@ impl CoreDump for CoreDumper {
Ok(s) Ok(s)
} }
async fn get_client_state(&self) -> Result<crate::coredump::ClientState> {
let promise = self
.manager
.get_client_state()
.map_err(|e| anyhow::anyhow!("Failed to get promise from get client state: {:?}", e))?;
let value = JsFuture::from(promise)
.await
.map_err(|e| anyhow::anyhow!("Failed to get response from client state: {:?}", e))?;
// Parse the value as a string.
let s = value
.as_string()
.ok_or_else(|| anyhow::anyhow!("Failed to get string from response from client stat: `{:?}`", value))?;
let client_state: crate::coredump::ClientState =
serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse client state: {:?}", e))?;
Ok(client_state)
}
} }

View File

@ -1121,7 +1121,7 @@ impl ExecutorContext {
&self, &self,
program: crate::ast::types::Program, program: crate::ast::types::Program,
memory: &mut ProgramMemory, memory: &mut ProgramMemory,
body_type: BodyType, _body_type: BodyType,
) -> Result<ProgramMemory, KclError> { ) -> Result<ProgramMemory, KclError> {
let pipe_info = PipeInfo::default(); let pipe_info = PipeInfo::default();
@ -1328,10 +1328,8 @@ impl ExecutorContext {
} }
} }
if BodyType::Root == body_type { // Flush the batch queue.
// Flush the batch queue. self.engine.flush_batch(SourceRange([program.end, program.end])).await?;
self.engine.flush_batch(SourceRange([program.end, program.end])).await?;
}
Ok(memory.clone()) Ok(memory.clone())
} }

View File

@ -0,0 +1,42 @@
fn make_circle = (face, tag, pos, radius) => {
const sg0 = startSketchOn(face, tag)
const sg1 = startProfileAt([pos[0] + radius, pos[1]], sg0)
const sg2 = arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, sg1, 'arc-' + tag)
return close(sg2)
}
fn pentagon = (len) => {
const sg3 = startSketchOn('XY')
const sg4 = startProfileAt([-len / 2, -len / 2], sg3)
const sg5 = angledLine({ angle: 0, length: len }, sg4, 'a')
const sg6 = angledLine({
angle: segAng('a', sg5) + 180 - 108,
length: len
},sg5, 'b')
const sg7 = angledLine({
angle: segAng('b', sg6) + 180 - 108,
length: len
}, sg6, 'c')
const sg8 = angledLine({
angle: segAng('c', sg7) + 180 - 108,
length: len
}, sg7, 'd')
return angledLine({
angle: segAng('d', sg8) + 180 - 108,
length: len
}, sg8)
}
const p = pentagon(48)
const pe = extrude(30, p)
const plumbus0 = make_circle(pe, 'a', [0, 0], 9)
const plumbus1 = extrude(18, plumbus0)
const plumbus2 = fillet({
radius: 0.5,
tags: ['arc-a', getOppositeEdge('arc-a', plumbus1)]
}, plumbus1)

View File

@ -1,41 +1,46 @@
fn triangle = (len) => { fn make_circle = (face, tag, pos, radius) => {
return startSketchOn('XY') const sg = startSketchOn(face, tag)
|> startProfileAt([0, 0], %) |> startProfileAt([pos[0] + radius, pos[1]], %)
|> angledLine({angle: 60, length: len}, %, 'a') |> arc({
|> angledLine({angle: 180, length: len}, %, 'b') angle_end: 360,
|> angledLine({angle: 300, length: len}, %, 'c') angle_start: 0,
} radius: radius
}, %, 'arc-' + tag)
let triangleHeight = 200
let plumbusLen = 100
let radius = 80
let circ = {angle_start: 0, angle_end: 360, radius: radius}
let triangleLen = 500
const p = triangle(triangleLen)
|> extrude(triangleHeight, %)
fn circl = (x, tag) => {
return startSketchOn(p, tag)
|> startProfileAt([x + radius, triangleHeight/2], %)
|> arc(circ, %, 'arc-' + tag)
|> close(%) |> close(%)
return sg
} }
const plumbus1 = fn pentagon = (len) => {
circl(-200, 'c') const sg = startSketchOn('XY')
|> extrude(plumbusLen, %) |> startProfileAt([-len / 2, -len / 2], %)
|> fillet({ |> angledLine({ angle: 0, length: len }, %, 'a')
radius: 5, |> angledLine({
tags: ['arc-c', getOppositeEdge('arc-c', %)] angle: segAng('a', %) + 180 - 108,
length: len
}, %, 'b')
|> angledLine({
angle: segAng('b', %) + 180 - 108,
length: len
}, %, 'c')
|> angledLine({
angle: segAng('c', %) + 180 - 108,
length: len
}, %, 'd')
|> angledLine({
angle: segAng('d', %) + 180 - 108,
length: len
}, %) }, %)
const plumbus0 = return sg
circl(200, 'a') }
|> extrude(plumbusLen, %)
const p = pentagon(48)
|> extrude(30, %)
const plumbus0 = make_circle(p, 'a', [0, 0], 9)
|> extrude(18, %)
|> fillet({ |> fillet({
radius: 5, radius: 0.5,
tags: ['arc-a', getOppositeEdge('arc-a', %)] tags: ['arc-a', getOppositeEdge('arc-a', %)]
}, %) }, %)

View File

@ -128,6 +128,15 @@ async fn serial_test_lego() {
twenty_twenty::assert_image("tests/executor/outputs/lego.png", &result, 0.999); twenty_twenty::assert_image("tests/executor/outputs/lego.png", &result, 0.999);
} }
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_pentagon_fillet_desugar() {
let code = include_str!("inputs/pentagon_fillet_desugar.kcl");
let result = execute_and_snapshot(code, kcl_lib::settings::types::UnitLength::Cm)
.await
.unwrap();
twenty_twenty::assert_image("tests/executor/outputs/pentagon_fillet_desugar.png", &result, 0.999);
}
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn serial_test_pentagon_fillet_sugar() { async fn serial_test_pentagon_fillet_sugar() {
let code = include_str!("inputs/pentagon_fillet_sugar.kcl"); let code = include_str!("inputs/pentagon_fillet_sugar.kcl");
@ -1946,12 +1955,12 @@ const plumbus0 = make_circle(p, 'a', [0, 0], 2.5)
tags: ['arc-a', getOppositeEdge('arc-a', %)] tags: ['arc-a', getOppositeEdge('arc-a', %)]
}, %) }, %)
const plumbus1 = make_circle(p, 'b', [0, 0], 2.5) // const plumbus1 = make_circle(p, 'b', [0, 0], 2.5)
|> extrude(10, %) // |> extrude(10, %)
|> fillet({ // |> fillet({
radius: 0.5, // radius: 0.5,
tags: ['arc-b', getOppositeEdge('arc-b', %)] // tags: ['arc-b', getOppositeEdge('arc-b', %)]
}, %) // }, %)
"#; "#;
let result = execute_and_snapshot(code, kcl_lib::settings::types::UnitLength::Mm) let result = execute_and_snapshot(code, kcl_lib::settings::types::UnitLength::Mm)

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 127 KiB