allow sending async commands to engine (#6342)
* start of async Signed-off-by: Jess Frazelle <github@jessfraz.com> check at end if the async commands completed Signed-off-by: Jess Frazelle <github@jessfraz.com> run at the end of inner_run Signed-off-by: Jess Frazelle <github@jessfraz.com> set import as async 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> add to the wasm side Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> * fire Signed-off-by: Jess Frazelle <github@jessfraz.com> * flake Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixup for awaiting on import 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> * fix mock Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix mock 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> * add a test where we import then do a bunch of other stuff Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixup to see Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixups Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * cross platform time 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> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * another appearance tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * new docs and tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * dont loop so tight Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
@ -76,6 +76,9 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
/// Get the artifact commands that have accumulated so far.
|
||||
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>>;
|
||||
|
||||
/// Get the ids of the async commands we are waiting for.
|
||||
fn ids_of_async_commands(&self) -> Arc<RwLock<IndexMap<Uuid, SourceRange>>>;
|
||||
|
||||
/// Take the batch of commands that have accumulated so far and clear them.
|
||||
async fn take_batch(&self) -> Vec<(WebSocketRequest, SourceRange)> {
|
||||
std::mem::take(&mut *self.batch().write().await)
|
||||
@ -96,6 +99,11 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
std::mem::take(&mut *self.artifact_commands().write().await)
|
||||
}
|
||||
|
||||
/// Take the ids of async commands that have accumulated so far and clear them.
|
||||
async fn take_ids_of_async_commands(&self) -> IndexMap<Uuid, SourceRange> {
|
||||
std::mem::take(&mut *self.ids_of_async_commands().write().await)
|
||||
}
|
||||
|
||||
/// Take the responses that have accumulated so far and clear them.
|
||||
async fn take_responses(&self) -> IndexMap<Uuid, WebSocketResponse> {
|
||||
std::mem::take(&mut *self.responses().write().await)
|
||||
@ -136,8 +144,18 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
async fn clear_queues(&self) {
|
||||
self.batch().write().await.clear();
|
||||
self.batch_end().write().await.clear();
|
||||
self.ids_of_async_commands().write().await.clear();
|
||||
}
|
||||
|
||||
/// Send a modeling command and do not wait for the response message.
|
||||
async fn inner_fire_modeling_cmd(
|
||||
&self,
|
||||
id: uuid::Uuid,
|
||||
source_range: SourceRange,
|
||||
cmd: WebSocketRequest,
|
||||
id_to_source_range: HashMap<Uuid, SourceRange>,
|
||||
) -> Result<(), crate::errors::KclError>;
|
||||
|
||||
/// Send a modeling command and wait for the response message.
|
||||
async fn inner_send_modeling_cmd(
|
||||
&self,
|
||||
@ -180,6 +198,68 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure a specific async command has been completed.
|
||||
async fn ensure_async_command_completed(
|
||||
&self,
|
||||
id: uuid::Uuid,
|
||||
source_range: Option<SourceRange>,
|
||||
) -> Result<OkWebSocketResponseData, KclError> {
|
||||
let source_range = if let Some(source_range) = source_range {
|
||||
source_range
|
||||
} else {
|
||||
// Look it up if we don't have it.
|
||||
self.ids_of_async_commands()
|
||||
.read()
|
||||
.await
|
||||
.get(&id)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
let current_time = instant::Instant::now();
|
||||
while current_time.elapsed().as_secs() < 60 {
|
||||
let responses = self.responses().read().await.clone();
|
||||
let Some(resp) = responses.get(&id) else {
|
||||
// Sleep for a little so we don't hog the CPU.
|
||||
// No seriously WE DO NOT WANT TO PAUSE THE WHOLE APP ON THE JS SIDE.
|
||||
let duration = instant::Duration::from_millis(100);
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm_timer::Delay::new(duration).await.map_err(|err| {
|
||||
KclError::Internal(KclErrorDetails {
|
||||
message: format!("Failed to sleep: {:?}", err),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::time::sleep(duration).await;
|
||||
continue;
|
||||
};
|
||||
|
||||
// If the response is an error, return it.
|
||||
// Parsing will do that and we can ignore the result, we don't care.
|
||||
let response = self.parse_websocket_response(resp.clone(), source_range)?;
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
Err(KclError::Engine(KclErrorDetails {
|
||||
message: "async command timed out".to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
}))
|
||||
}
|
||||
|
||||
/// Ensure ALL async commands have been completed.
|
||||
async fn ensure_async_commands_completed(&self) -> Result<(), KclError> {
|
||||
// Check if all async commands have been completed.
|
||||
let ids = self.take_ids_of_async_commands().await;
|
||||
|
||||
// Try to get them from the responses.
|
||||
for (id, source_range) in ids {
|
||||
self.ensure_async_command_completed(id, Some(source_range)).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the visibility of edges.
|
||||
async fn set_edge_visibility(
|
||||
&self,
|
||||
@ -342,6 +422,36 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
self.run_batch(requests, source_range).await
|
||||
}
|
||||
|
||||
/// Send the modeling cmd async and don't wait for the response.
|
||||
/// Add it to our list of async commands.
|
||||
async fn async_modeling_cmd(
|
||||
&self,
|
||||
id: uuid::Uuid,
|
||||
source_range: SourceRange,
|
||||
cmd: &ModelingCmd,
|
||||
) -> Result<(), crate::errors::KclError> {
|
||||
// Add the command ID to the list of async commands.
|
||||
self.ids_of_async_commands().write().await.insert(id, source_range);
|
||||
|
||||
// Add to artifact commands.
|
||||
self.handle_artifact_command(cmd, id.into(), &HashMap::from([(id, source_range)]))
|
||||
.await?;
|
||||
|
||||
// Fire off the command now, but don't wait for the response, we don't care about it.
|
||||
self.inner_fire_modeling_cmd(
|
||||
id,
|
||||
source_range,
|
||||
WebSocketRequest::ModelingCmdReq(ModelingCmdReq {
|
||||
cmd: cmd.clone(),
|
||||
cmd_id: id.into(),
|
||||
}),
|
||||
HashMap::from([(id, source_range)]),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run the batch for the specific commands.
|
||||
async fn run_batch(
|
||||
&self,
|
||||
|
Reference in New Issue
Block a user