perf: 性能与可靠性优化 - CommandQueue超时恢复/HTTP限制/日志轮转/调试日志清理/applyProperties修复
This commit is contained in:
@@ -249,3 +249,37 @@
|
|||||||
### 5. 日志仅输出关键信息到编辑器控制台
|
### 5. 日志仅输出关键信息到编辑器控制台
|
||||||
|
|
||||||
- **优化**: `addLog` 函数不再将所有类型的日志输出到编辑器控制台,仅 `error` 和 `warn` 级别日志通过 `Editor.error()` / `Editor.warn()` 输出,防止 `info` / `success` / `mcp` 类型日志刷屏干扰开发者。
|
- **优化**: `addLog` 函数不再将所有类型的日志输出到编辑器控制台,仅 `error` 和 `warn` 级别日志通过 `Editor.error()` / `Editor.warn()` 输出,防止 `info` / `success` / `mcp` 类型日志刷屏干扰开发者。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 性能与可靠性优化 (2026-02-28)
|
||||||
|
|
||||||
|
### 1. CommandQueue 超时保护恢复
|
||||||
|
|
||||||
|
- **问题**: 合并冲突解决时 `enqueueCommand` 中的 60 秒兜底超时保护代码丢失,导致如果工具函数内部异常未调用 `done()`,整个指令队列将永久停滞,后续所有操作将卡死不再响应。
|
||||||
|
- **修复**: 在 `enqueueCommand` 中为每个入队指令注册 `setTimeout(60000)` 超时定时器,正常完成时通过 `clearTimeout` 取消。
|
||||||
|
|
||||||
|
### 2. HTTP 请求体大小限制
|
||||||
|
|
||||||
|
- **问题**: `_handleRequest` 中 `body += chunk` 无上限保护,超大请求体(恶意或异常客户端)可能耗尽编辑器进程内存。
|
||||||
|
- **修复**: 新增 5MB (`5 * 1024 * 1024`) 请求体上限,超出时返回 HTTP 413 并销毁连接。
|
||||||
|
|
||||||
|
### 3. 日志文件轮转机制
|
||||||
|
|
||||||
|
- **问题**: `settings/mcp-bridge.log` 文件持续追加写入,长期使用会无限增长占用磁盘空间。
|
||||||
|
- **修复**: 在 `getLogFilePath()` 初始化时检查文件大小,超过 2MB 自动将旧日志重命名为 `.old` 备份后创建新文件。
|
||||||
|
|
||||||
|
### 4. 清理冗余调试日志
|
||||||
|
|
||||||
|
- **问题**: `scene-script.js` 中 `update-node-transform` 和 `applyProperties` 共有 8 处 `Editor.log` 调试日志,每次操作都输出到编辑器控制台造成刷屏。
|
||||||
|
- **修复**: 移除所有冗余 `Editor.log` 调试输出,保留必要的 `Editor.warn` 警告(如资源加载失败、属性解析失败等)。
|
||||||
|
|
||||||
|
### 5. `applyProperties` 逻辑修复
|
||||||
|
|
||||||
|
- **问题**: `applyProperties` 启发式资源解析分支中使用了 `return` 而非 `continue`,导致处理到该分支后会直接退出整个 `for...of` 循环,跳过后续属性的设置。
|
||||||
|
- **修复**: 将 `return` 改为 `continue`,确保多属性同时更新时所有属性都能被正确处理。
|
||||||
|
|
||||||
|
### 6. `instantiate-prefab` 统一使用 `findNode`
|
||||||
|
|
||||||
|
- **问题**: `instantiate-prefab` 中查找父节点直接调用 `cc.engine.getInstanceById(parentId)`,绕过了 `findNode` 函数的压缩 UUID 解压与兼容逻辑。
|
||||||
|
- **修复**: 统一改用 `findNode(parentId)`,确保所有场景操作对压缩和非压缩 UUID 格式的兼容性一致。
|
||||||
|
|||||||
36
main.js
36
main.js
@@ -29,7 +29,14 @@ let isProcessingCommand = false;
|
|||||||
*/
|
*/
|
||||||
function enqueueCommand(fn) {
|
function enqueueCommand(fn) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
commandQueue.push({ fn, resolve });
|
// 兜底超时保护:防止 fn 内部未调用 done() 导致队列永久停滞
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
addLog("error", "[CommandQueue] 指令执行超时(60s),强制释放队列");
|
||||||
|
isProcessingCommand = false;
|
||||||
|
resolve();
|
||||||
|
processNextCommand();
|
||||||
|
}, 60000);
|
||||||
|
commandQueue.push({ fn, resolve, timeoutId });
|
||||||
processNextCommand();
|
processNextCommand();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -40,15 +47,17 @@ function enqueueCommand(fn) {
|
|||||||
function processNextCommand() {
|
function processNextCommand() {
|
||||||
if (isProcessingCommand || commandQueue.length === 0) return;
|
if (isProcessingCommand || commandQueue.length === 0) return;
|
||||||
isProcessingCommand = true;
|
isProcessingCommand = true;
|
||||||
const { fn, resolve } = commandQueue.shift();
|
const { fn, resolve, timeoutId } = commandQueue.shift();
|
||||||
try {
|
try {
|
||||||
fn(() => {
|
fn(() => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
isProcessingCommand = false;
|
isProcessingCommand = false;
|
||||||
resolve();
|
resolve();
|
||||||
processNextCommand();
|
processNextCommand();
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 防止队列因未捕获异常永久阻塞
|
// 防止队列因未捕获异常永久阻塞
|
||||||
|
clearTimeout(timeoutId);
|
||||||
addLog("error", `[CommandQueue] 指令执行异常: ${e.message}`);
|
addLog("error", `[CommandQueue] 指令执行异常: ${e.message}`);
|
||||||
isProcessingCommand = false;
|
isProcessingCommand = false;
|
||||||
resolve();
|
resolve();
|
||||||
@@ -112,6 +121,19 @@ function getLogFilePath() {
|
|||||||
fs.mkdirSync(settingsDir, { recursive: true });
|
fs.mkdirSync(settingsDir, { recursive: true });
|
||||||
}
|
}
|
||||||
_logFilePath = pathModule.join(settingsDir, "mcp-bridge.log");
|
_logFilePath = pathModule.join(settingsDir, "mcp-bridge.log");
|
||||||
|
// 日志轮转: 超过 2MB 时备份旧日志并创建新文件
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(_logFilePath)) {
|
||||||
|
const stats = fs.statSync(_logFilePath);
|
||||||
|
if (stats.size > 2 * 1024 * 1024) {
|
||||||
|
const backupPath = _logFilePath + ".old";
|
||||||
|
if (fs.existsSync(backupPath)) fs.unlinkSync(backupPath);
|
||||||
|
fs.renameSync(_logFilePath, backupPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
/* 轮转失败不影响主流程 */
|
||||||
|
}
|
||||||
return _logFilePath;
|
return _logFilePath;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -832,11 +854,21 @@ module.exports = {
|
|||||||
res.setHeader("Content-Type", "application/json");
|
res.setHeader("Content-Type", "application/json");
|
||||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
|
||||||
|
const MAX_BODY_SIZE = 5 * 1024 * 1024; // 5MB 请求体上限
|
||||||
let body = "";
|
let body = "";
|
||||||
|
let aborted = false;
|
||||||
req.on("data", (chunk) => {
|
req.on("data", (chunk) => {
|
||||||
body += chunk;
|
body += chunk;
|
||||||
|
if (body.length > MAX_BODY_SIZE) {
|
||||||
|
aborted = true;
|
||||||
|
addLog("error", `[HTTP] 请求体超过 ${MAX_BODY_SIZE} 字节上限,已拒绝`);
|
||||||
|
res.writeHead(413);
|
||||||
|
res.end(JSON.stringify({ error: "请求体过大" }));
|
||||||
|
req.destroy();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
req.on("end", () => {
|
req.on("end", () => {
|
||||||
|
if (aborted) return;
|
||||||
const url = req.url;
|
const url = req.url;
|
||||||
if (url === "/list-tools") {
|
if (url === "/list-tools") {
|
||||||
const tools = getToolsList();
|
const tools = getToolsList();
|
||||||
|
|||||||
2323
scene-script.js
2323
scene-script.js
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user