Add batch support to current KCL implementation
This commit is contained in:
		@ -3,6 +3,7 @@
 | 
			
		||||
 | 
			
		||||
use std::sync::{Arc, Mutex};
 | 
			
		||||
 | 
			
		||||
use crate::executor::SourceRange;
 | 
			
		||||
use anyhow::{anyhow, Result};
 | 
			
		||||
use dashmap::DashMap;
 | 
			
		||||
use futures::{SinkExt, StreamExt};
 | 
			
		||||
@ -71,20 +72,113 @@ struct ToEngineReq {
 | 
			
		||||
    request_sent: oneshot::Sender<Result<()>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl EngineConnection {
 | 
			
		||||
    /// Start waiting for incoming engine requests, and send each one over the WebSocket to the engine.
 | 
			
		||||
    async fn start_write_actor(mut tcp_write: WebSocketTcpWrite, mut engine_req_rx: mpsc::Receiver<ToEngineReq>) {
 | 
			
		||||
        let mut batch: Vec<kittycad::types::ModelingCmdReq> = vec![];
 | 
			
		||||
 | 
			
		||||
        while let Some(req) = engine_req_rx.recv().await {
 | 
			
		||||
            let ToEngineReq { req, request_sent } = req;
 | 
			
		||||
            let res = if let kittycad::types::WebSocketRequest::ModelingCmdReq {
 | 
			
		||||
                cmd: kittycad::types::ModelingCmd::ImportFiles { .. },
 | 
			
		||||
                cmd_id: _,
 | 
			
		||||
            } = &req
 | 
			
		||||
            {
 | 
			
		||||
            let kittycad::types::WebSocketRequest::ModelingCmdReq { cmd, cmd_id } = &req else {
 | 
			
		||||
                return;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            let res = if let kittycad::types::ModelingCmd::ImportFiles { .. } = cmd {
 | 
			
		||||
                // Send it as binary.
 | 
			
		||||
                Self::inner_send_to_engine_binary(req, &mut tcp_write).await
 | 
			
		||||
            } else {
 | 
			
		||||
                Self::inner_send_to_engine(req, &mut tcp_write).await
 | 
			
		||||
                // Backported from the new Grackle-core KCL.
 | 
			
		||||
                // We will batch all commands until we hit one which has
 | 
			
		||||
                // return values we want to wait for. Currently, that means
 | 
			
		||||
                // waiting for API requests with return values that are not just
 | 
			
		||||
                // request confirmations (ex. face or edge data).
 | 
			
		||||
 | 
			
		||||
                batch.push(kittycad::types::ModelingCmdReq {
 | 
			
		||||
                    cmd: cmd.clone(),
 | 
			
		||||
                    cmd_id: *cmd_id,
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                if is_cmd_with_return_values(&cmd) || *cmd_id == uuid::Uuid::nil() {
 | 
			
		||||
                    // If the batch has zero commands, and we're about to load
 | 
			
		||||
                    // a command that has return values, don't wrap it in a
 | 
			
		||||
                    // ModelingCmdBatchReq.
 | 
			
		||||
                    let future = if batch.len() == 1 {
 | 
			
		||||
                        Self::inner_send_to_engine(
 | 
			
		||||
                            kittycad::types::WebSocketRequest::ModelingCmdReq {
 | 
			
		||||
                                cmd: cmd.clone(),
 | 
			
		||||
                                cmd_id: *cmd_id,
 | 
			
		||||
                            },
 | 
			
		||||
                            &mut tcp_write,
 | 
			
		||||
                        )
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // Serde will properly serialize these.
 | 
			
		||||
                        Self::inner_send_to_engine(
 | 
			
		||||
                            kittycad::types::WebSocketRequest::ModelingCmdBatchReq {
 | 
			
		||||
                                requests: batch.clone(),
 | 
			
		||||
                            },
 | 
			
		||||
                            &mut tcp_write,
 | 
			
		||||
                        )
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    // Prepare for a new batch of instructions.
 | 
			
		||||
                    batch.clear();
 | 
			
		||||
 | 
			
		||||
                    future.await
 | 
			
		||||
                } else {
 | 
			
		||||
                    Ok(())
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            let _ = request_sent.send(res);
 | 
			
		||||
        }
 | 
			
		||||
@ -163,7 +257,7 @@ impl EngineManager for EngineConnection {
 | 
			
		||||
    async fn send_modeling_cmd(
 | 
			
		||||
        &self,
 | 
			
		||||
        id: uuid::Uuid,
 | 
			
		||||
        source_range: crate::executor::SourceRange,
 | 
			
		||||
        source_range: SourceRange,
 | 
			
		||||
        cmd: kittycad::types::ModelingCmd,
 | 
			
		||||
    ) -> Result<OkWebSocketResponseData, KclError> {
 | 
			
		||||
        let (tx, rx) = oneshot::channel();
 | 
			
		||||
@ -200,7 +294,18 @@ impl EngineManager for EngineConnection {
 | 
			
		||||
                })
 | 
			
		||||
            })?;
 | 
			
		||||
 | 
			
		||||
        // Wait for the response.
 | 
			
		||||
        // Only wait for a response if it's a command *with* return values
 | 
			
		||||
        // So most of the time, this condition will be true.
 | 
			
		||||
        if !is_cmd_with_return_values(&cmd) {
 | 
			
		||||
            // Simulate an empty response type
 | 
			
		||||
            return Ok(OkWebSocketResponseData::Modeling {
 | 
			
		||||
                modeling_response: kittycad::types::OkModelingCmdResponse::Empty {},
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If it's a submitted batch, we want to check the batch return value
 | 
			
		||||
        // in case of any errors.
 | 
			
		||||
 | 
			
		||||
        let current_time = std::time::Instant::now();
 | 
			
		||||
        while current_time.elapsed().as_secs() < 60 {
 | 
			
		||||
            if let Ok(guard) = self.socket_health.lock() {
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,24 @@ pub mod conn_wasm;
 | 
			
		||||
 | 
			
		||||
#[async_trait::async_trait]
 | 
			
		||||
pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
 | 
			
		||||
    /// Tell the EngineManager there will be no more commands.
 | 
			
		||||
    /// We send a "dummy command" to signal EngineConnection that we're
 | 
			
		||||
    /// at the end of the program and there'll be no more requests.
 | 
			
		||||
    /// This means in tests, where it's impossible to look ahead, we'll need to
 | 
			
		||||
    /// add this to mark the end of commands.
 | 
			
		||||
    /// In compiled KCL tests, it will be auto-inserted.
 | 
			
		||||
    async fn signal_end(&self) -> Result<kittycad::types::OkWebSocketResponseData, crate::errors::KclError> {
 | 
			
		||||
        self.send_modeling_cmd(
 | 
			
		||||
            // THE NIL UUID IS THE SIGNAL OF THE END OF TIMES FOR THIS POOR PROGRAM.
 | 
			
		||||
            uuid::Uuid::nil(),
 | 
			
		||||
            // This will be ignored.
 | 
			
		||||
            crate::executor::SourceRange([0, 0]),
 | 
			
		||||
            // This will be ignored. It was one I found with no fields.
 | 
			
		||||
            kittycad::types::ModelingCmd::EditModeExit {},
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Send a modeling command and wait for the response message.
 | 
			
		||||
    async fn send_modeling_cmd(
 | 
			
		||||
        &self,
 | 
			
		||||
 | 
			
		||||
@ -1219,6 +1219,9 @@ pub async fn execute(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Signal to engine we're done. Flush the batch.
 | 
			
		||||
    ctx.engine.signal_end().await?;
 | 
			
		||||
 | 
			
		||||
    Ok(memory.clone())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -165,6 +165,7 @@ async fn inner_get_opposite_edge(tag: String, extrude_group: Box<ExtrudeGroup>,
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
    let kittycad::types::OkWebSocketResponseData::Modeling {
 | 
			
		||||
        modeling_response: kittycad::types::OkModelingCmdResponse::Solid3DGetOppositeEdge { data: opposite_edge },
 | 
			
		||||
    } = &resp
 | 
			
		||||
 | 
			
		||||
@ -915,7 +915,7 @@ async fn start_sketch_on_face(
 | 
			
		||||
            })
 | 
			
		||||
            .ok_or_else(|| {
 | 
			
		||||
                KclError::Type(KclErrorDetails {
 | 
			
		||||
                    message: format!("Expected a face with the tag `{}`", tag),
 | 
			
		||||
                    message: format!("Expected a face with the tag `{}` for sketch", tag),
 | 
			
		||||
                    source_ranges: vec![args.source_range],
 | 
			
		||||
                })
 | 
			
		||||
            })??,
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user