把 ui-design-doc 的阻塞 IO 挪出 async 执行器
src-tauri/src/agent/runtime_tools/ui_design_doc.rs 的 from-images 与 into-js 观察函数改为 async,核心逻辑用 tokio::task::spawn_blocking 执行:from-images 要逐张 image::open 解码设计图再写 manifest,into-js 要读整份文档、算 SHA-256 再落盘 JS,原先都在 async 执行器线程上同步跑,会和其它已派发的动作抢执行器 阻塞任务未完成(join 失败)时返回 status=error 的观察,沿用仓库既有的 spawn_blocking 失败兜底写法 src-tauri/src/agent/runtime_actions/action_execution.rs 两个分发分支改为 await;ui_design_doc.rs 的两个用例改成 #[tokio::test] 并 await
This commit is contained in:
@@ -154,13 +154,15 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action_with_pending_
|
||||
}
|
||||
"canvas.asset_import" => observe_agent_runtime_asset_import(root, &action.input).await,
|
||||
"ui-design-doc.from-images" => {
|
||||
observe_agent_runtime_ui_design_doc_from_images(root, &action.input)
|
||||
observe_agent_runtime_ui_design_doc_from_images(root, &action.input).await
|
||||
}
|
||||
"ui-design-doc.run-workflow" => {
|
||||
observe_agent_runtime_ui_design_doc_run_workflow(root, agent_id, run_id, &action.input)
|
||||
.await
|
||||
}
|
||||
"ui-design-doc.into-js" => observe_agent_runtime_ui_design_doc_into_js(root, &action.input),
|
||||
"ui-design-doc.into-js" => {
|
||||
observe_agent_runtime_ui_design_doc_into_js(root, &action.input).await
|
||||
}
|
||||
"project.index" => observe_agent_runtime_project_snapshot_with_lock(
|
||||
root,
|
||||
agent_id,
|
||||
|
||||
@@ -28,7 +28,7 @@ struct DesignDocArguments {
|
||||
design_doc_asset_id: String,
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn observe_agent_runtime_ui_design_doc_from_images(
|
||||
pub(in crate::agent) async fn observe_agent_runtime_ui_design_doc_from_images(
|
||||
root: &Path,
|
||||
input: &Value,
|
||||
) -> AgentRuntimeToolObservation {
|
||||
@@ -44,11 +44,25 @@ pub(in crate::agent) fn observe_agent_runtime_ui_design_doc_from_images(
|
||||
let revision_before = read_game_creator_agent_runtime_project_revision(root)
|
||||
.ok()
|
||||
.map(|snapshot| snapshot.revision);
|
||||
match create_ui_design_doc_from_images(CreateUiDesignDocFromImagesInput {
|
||||
project_path: root.to_string_lossy().into_owned(),
|
||||
expected_project_id,
|
||||
images: arguments.images,
|
||||
}) {
|
||||
// 新建文档要逐张解码设计图并写 manifest:整段阻塞 IO 交给 blocking 线程池,
|
||||
// 别把 async 执行器(同一个 runtime 还要继续跑其它动作)占住。
|
||||
let project_path = root.to_string_lossy().into_owned();
|
||||
let images = arguments.images;
|
||||
let created = match tokio::task::spawn_blocking(move || {
|
||||
create_ui_design_doc_from_images(CreateUiDesignDocFromImagesInput {
|
||||
project_path,
|
||||
expected_project_id,
|
||||
images,
|
||||
})
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(created) => created,
|
||||
Err(_) => {
|
||||
return error_observation(root, tool, "后台创建 UI 设计文档任务未完成".to_string())
|
||||
}
|
||||
};
|
||||
match created {
|
||||
Ok(created) => {
|
||||
if revision_before.is_none_or(|before| created.committed_project_revision > before) {
|
||||
emit_game_creator_manifest_invalidated(root, tool);
|
||||
@@ -135,7 +149,7 @@ pub(in crate::agent) async fn observe_agent_runtime_ui_design_doc_run_workflow(
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn observe_agent_runtime_ui_design_doc_into_js(
|
||||
pub(in crate::agent) async fn observe_agent_runtime_ui_design_doc_into_js(
|
||||
root: &Path,
|
||||
input: &Value,
|
||||
) -> AgentRuntimeToolObservation {
|
||||
@@ -148,11 +162,24 @@ pub(in crate::agent) fn observe_agent_runtime_ui_design_doc_into_js(
|
||||
Ok(project_id) => project_id,
|
||||
Err(error) => return rejected(root, tool, error),
|
||||
};
|
||||
match generate_ui_design_code_at(GenerateUiDesignCodeInput {
|
||||
project_path: root.to_string_lossy().into_owned(),
|
||||
expected_project_id,
|
||||
asset_id: arguments.design_doc_asset_id,
|
||||
}) {
|
||||
// 生成代码要读整份文档、算 SHA-256 再落盘,同样是阻塞 IO,放到 blocking 线程池。
|
||||
let project_path = root.to_string_lossy().into_owned();
|
||||
let asset_id = arguments.design_doc_asset_id;
|
||||
let generated = match tokio::task::spawn_blocking(move || {
|
||||
generate_ui_design_code_at(GenerateUiDesignCodeInput {
|
||||
project_path,
|
||||
expected_project_id,
|
||||
asset_id,
|
||||
})
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(generated) => generated,
|
||||
Err(_) => {
|
||||
return error_observation(root, tool, "后台生成 UI 设计代码任务未完成".to_string())
|
||||
}
|
||||
};
|
||||
match generated {
|
||||
Ok(result) => AgentRuntimeToolObservation {
|
||||
tool: tool.to_string(),
|
||||
status: "ok".to_string(),
|
||||
@@ -190,13 +217,14 @@ mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn from_images_rejects_unknown_fields_and_accepts_reference_pairs() {
|
||||
#[tokio::test]
|
||||
async fn from_images_rejects_unknown_fields_and_accepts_reference_pairs() {
|
||||
let root = Path::new(".");
|
||||
let rejected = observe_agent_runtime_ui_design_doc_from_images(
|
||||
root,
|
||||
&json!({ "images": [{ "assetId": "a", "path": null }], "extra": 1 }),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
assert_eq!(rejected.status, "rejected");
|
||||
assert!(rejected.summary.contains("extra"), "{}", rejected.summary);
|
||||
|
||||
@@ -204,21 +232,23 @@ mod tests {
|
||||
let invalid_project = observe_agent_runtime_ui_design_doc_from_images(
|
||||
root,
|
||||
&json!({ "images": [{ "assetId": "a", "path": null }] }),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
assert_eq!(invalid_project.status, "rejected");
|
||||
assert!(!invalid_project.summary.contains("输入无效"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn design_doc_asset_id_must_be_a_string() {
|
||||
// run-workflow 与 into-js 共用同一份入参解析,这里解析一次、同步调用一次。
|
||||
#[tokio::test]
|
||||
async fn design_doc_asset_id_must_be_a_string() {
|
||||
// run-workflow 与 into-js 共用同一份入参解析,这里解析一次、各调用一次。
|
||||
assert!(
|
||||
serde_json::from_value::<DesignDocArguments>(json!({ "designDocAssetId": 1 })).is_err()
|
||||
);
|
||||
let observation = observe_agent_runtime_ui_design_doc_into_js(
|
||||
Path::new("."),
|
||||
&json!({ "designDocAssetId": 1 }),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
assert_eq!(observation.status, "rejected");
|
||||
assert!(
|
||||
observation.summary.contains("输入无效"),
|
||||
|
||||
Reference in New Issue
Block a user