fix: 给「寻路状态」进度日志加节流(1.11.1)
mineflayer-pathfinder 每用满一个 tick 的计算预算(tickTimeout,默认 40ms)就发一次 path_update,而原来的处理函数每次都记一条 —— 实测 4 分钟写了 3893 条(约 16 条/秒)。 既淹没了真正有用的日志(按 120 字节/行算一天能写上百 MB,而本服磁盘只剩 2.4G), 也让「寻路到底卡在哪」更难看清。 现在只在三种情况下记: 1. 状态发生变化(partial → success / noPath / timeout)—— 最要紧的信号; 2. 距上一条超过 2 秒; 3. 已探索节点数比上一条多出 1000 以上 —— 长规划里仍能看出推进速度, 不丢定位卡死所需的信息。 测试 480 → 481。
这个提交包含在:
+20
@@ -2,6 +2,26 @@
|
|||||||
|
|
||||||
本项目遵循 [约定式提交](https://www.conventionalcommits.org/zh-hans/) 与 [语义化版本](https://semver.org/lang/zh-CN/)。
|
本项目遵循 [约定式提交](https://www.conventionalcommits.org/zh-hans/) 与 [语义化版本](https://semver.org/lang/zh-CN/)。
|
||||||
|
|
||||||
|
## [1.11.1] - 2026-10-01
|
||||||
|
|
||||||
|
给「寻路状态」进度日志加节流 —— 它一度是日志体积的主要来源。
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
|
||||||
|
- **「寻路状态」进度日志刷屏**:mineflayer-pathfinder 每用满一个 tick 的计算预算
|
||||||
|
(`tickTimeout`,默认 40ms)就发一次 `path_update`,而原来的处理函数**每次都记一条**。
|
||||||
|
实测 4 分钟写了 **3893 条**(约 16 条/秒)—— 既淹没了真正有用的日志
|
||||||
|
(按 120 字节/行算一天能写上百 MB,而本服磁盘只剩 2.4G),
|
||||||
|
也让「寻路到底卡在哪」更难看清。
|
||||||
|
现在只在三种情况下记:**状态发生变化**(partial → success / noPath / timeout,
|
||||||
|
最要紧的信号)、**距上一条超过 2 秒**、**已探索节点数比上一条多出 1000 以上**
|
||||||
|
(长规划里仍能看出推进速度,不丢定位卡死所需的信息)。
|
||||||
|
|
||||||
|
### 测试
|
||||||
|
|
||||||
|
新增 1 个用例(480 → 481):300 次 `partial` 被节流到十几条以内且不为零,
|
||||||
|
状态变化时立刻记录。
|
||||||
|
|
||||||
## [1.11.0] - 2026-10-01
|
## [1.11.0] - 2026-10-01
|
||||||
|
|
||||||
**修掉「机器人原地走之字形、净位移恒为 0」的朝向 bug** —— 这是扩地跑不完
|
**修掉「机器人原地走之字形、净位移恒为 0」的朝向 bug** —— 这是扩地跑不完
|
||||||
|
|||||||
自动生成
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "autostore-bot",
|
"name": "autostore-bot",
|
||||||
"version": "1.11.0",
|
"version": "1.11.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "autostore-bot",
|
"name": "autostore-bot",
|
||||||
"version": "1.11.0",
|
"version": "1.11.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "autostore-bot",
|
"name": "autostore-bot",
|
||||||
"version": "1.11.0",
|
"version": "1.11.1",
|
||||||
"description": "AutoStore Bot —— Minecraft 自动扩地机器人(mineflayer 重写版,多机器人挂机,Web 控制台,开拓日志,大地图,Grim 反作弊适配)",
|
"description": "AutoStore Bot —— Minecraft 自动扩地机器人(mineflayer 重写版,多机器人挂机,Web 控制台,开拓日志,大地图,Grim 反作弊适配)",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+50
-8
@@ -25,9 +25,18 @@ const MAX_SEGMENT_SHRINK = 3;
|
|||||||
* - 大幅提高进入液体格的代价,优先绕开水域(mineflayer 液体物理与原版差异最大)
|
* - 大幅提高进入液体格的代价,优先绕开水域(mineflayer 液体物理与原版差异最大)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 「寻路状态」日志的最小间隔(毫秒):进度日志最多这么频繁地记一条。
|
||||||
|
*
|
||||||
|
* mineflayer-pathfinder 每用满一个 tick 的计算预算就发一次 `path_update`,
|
||||||
|
* 不节流的话实测 4 分钟能写 3893 条,把真正有用的日志全淹了。
|
||||||
|
*/
|
||||||
|
const PATH_LOG_MIN_INTERVAL_MS = 2000;
|
||||||
|
/** 「寻路状态」日志的节点数增量阈值:探索推进这么多节点才值得再记一条 */
|
||||||
|
const PATH_LOG_NODE_STEP = 1000;
|
||||||
|
|
||||||
/** 路径事件枚举,语义对齐 Baritone 的 PathEvent */
|
/** 路径事件枚举,语义对齐 Baritone 的 PathEvent */
|
||||||
const PathEvent = {
|
const PathEvent = { CALC_STARTED: 'CALC_STARTED',
|
||||||
CALC_STARTED: 'CALC_STARTED',
|
|
||||||
CALC_FINISHED_NOW_EXECUTING: 'CALC_FINISHED_NOW_EXECUTING',
|
CALC_FINISHED_NOW_EXECUTING: 'CALC_FINISHED_NOW_EXECUTING',
|
||||||
CALC_FAILED: 'CALC_FAILED',
|
CALC_FAILED: 'CALC_FAILED',
|
||||||
AT_GOAL: 'AT_GOAL',
|
AT_GOAL: 'AT_GOAL',
|
||||||
@@ -57,6 +66,10 @@ class PathingService {
|
|||||||
this._pathing = false;
|
this._pathing = false;
|
||||||
this._lastStatus = null;
|
this._lastStatus = null;
|
||||||
this._configured = false;
|
this._configured = false;
|
||||||
|
/** 「寻路状态」日志的节流状态:上次记的状态 / 时间 / 节点数 */
|
||||||
|
this._lastLoggedStatus = null;
|
||||||
|
this._lastPathLogAt = 0;
|
||||||
|
this._lastLoggedNodes = 0;
|
||||||
/** 事件监听是否已注册(configure 会被反复调用,不能重复注册) */
|
/** 事件监听是否已注册(configure 会被反复调用,不能重复注册) */
|
||||||
this._listenersBound = false;
|
this._listenersBound = false;
|
||||||
/**
|
/**
|
||||||
@@ -134,12 +147,7 @@ class PathingService {
|
|||||||
bot.on('goal_reached', () => this._onSegmentReached());
|
bot.on('goal_reached', () => this._onSegmentReached());
|
||||||
bot.on('path_update', (r) => {
|
bot.on('path_update', (r) => {
|
||||||
this._lastStatus = r.status;
|
this._lastStatus = r.status;
|
||||||
// 完整记录寻路器状态:长距离寻路卡住时,只看成功 / 失败是查不出原因的
|
this._logPathProgress(r);
|
||||||
this._log(`寻路状态:${r.status}`
|
|
||||||
+ (r.visitedNodes !== undefined ? `(已探索 ${r.visitedNodes} 节点)` : '')
|
|
||||||
+ (r.cost !== undefined ? `(代价 ${r.cost})` : '')
|
|
||||||
+ `,本段目标 ${this.currentWaypoint ? Math.round(this.currentWaypoint.x) + ',' + Math.round(this.currentWaypoint.z) : '无'}`
|
|
||||||
+ `,最终目标 ${this.finalTarget ? Math.round(this.finalTarget.x) + ',' + Math.round(this.finalTarget.z) : '无'}`);
|
|
||||||
if (r.status === 'success') {
|
if (r.status === 'success') {
|
||||||
this.calcFailed = false;
|
this.calcFailed = false;
|
||||||
this._emit(PathEvent.CALC_FINISHED_NOW_EXECUTING, { cost: r.cost, nodes: r.visitedNodes });
|
this._emit(PathEvent.CALC_FINISHED_NOW_EXECUTING, { cost: r.cost, nodes: r.visitedNodes });
|
||||||
@@ -183,6 +191,40 @@ class PathingService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录寻路进度(**带节流**)。
|
||||||
|
*
|
||||||
|
* mineflayer-pathfinder 每用满一个 tick 的计算预算(`tickTimeout`,默认 40ms)
|
||||||
|
* 就发一次 `path_update`。长距离规划时这会以**每秒十几到几十条**的频率刷屏 ——
|
||||||
|
* 实测 4 分钟写了 **3893 条**「寻路状态」,既淹没了真正有用的日志
|
||||||
|
* (按 120 字节/行算,一天能写上百 MB,本服磁盘本就只剩 2.4G),
|
||||||
|
* 也让「寻路到底卡在哪」更难看清。
|
||||||
|
*
|
||||||
|
* 所以只在下面三种情况下记一条:
|
||||||
|
* 1. 状态发生变化(partial → success / noPath / timeout)—— 这是最要紧的;
|
||||||
|
* 2. 距上一条超过 {@link PATH_LOG_MIN_INTERVAL_MS};
|
||||||
|
* 3. 已探索节点数比上一条多出 {@link PATH_LOG_NODE_STEP} 以上
|
||||||
|
* —— 长规划里仍能看出「探索推进得有多快」,不会丢掉定位卡死所需的信息。
|
||||||
|
*
|
||||||
|
* @param {{status: string, visitedNodes?: number, cost?: number}} r path_update 事件
|
||||||
|
*/
|
||||||
|
_logPathProgress(r) {
|
||||||
|
const now = Date.now();
|
||||||
|
const nodes = Number.isFinite(r.visitedNodes) ? r.visitedNodes : 0;
|
||||||
|
const statusChanged = r.status !== this._lastLoggedStatus;
|
||||||
|
const stale = now - this._lastPathLogAt >= PATH_LOG_MIN_INTERVAL_MS;
|
||||||
|
const progressed = nodes - this._lastLoggedNodes >= PATH_LOG_NODE_STEP;
|
||||||
|
if (!statusChanged && !stale && !progressed) return;
|
||||||
|
this._lastLoggedStatus = r.status;
|
||||||
|
this._lastPathLogAt = now;
|
||||||
|
this._lastLoggedNodes = nodes;
|
||||||
|
this._log(`寻路状态:${r.status}`
|
||||||
|
+ (r.visitedNodes !== undefined ? `(已探索 ${r.visitedNodes} 节点)` : '')
|
||||||
|
+ (r.cost !== undefined ? `(代价 ${r.cost})` : '')
|
||||||
|
+ `,本段目标 ${this.currentWaypoint ? Math.round(this.currentWaypoint.x) + ',' + Math.round(this.currentWaypoint.z) : '无'}`
|
||||||
|
+ `,最终目标 ${this.finalTarget ? Math.round(this.finalTarget.x) + ',' + Math.round(this.finalTarget.z) : '无'}`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置变更后重新应用(不重建事件监听)。
|
* 配置变更后重新应用(不重建事件监听)。
|
||||||
* @param {object} config 新配置
|
* @param {object} config 新配置
|
||||||
|
|||||||
@@ -50,6 +50,38 @@ test('高度偏好默认关闭:它的启发值会破坏 partial 路径的终
|
|||||||
`高处节点的启发值 ${farButHigh} 不大于低处近节点的 ${nearButLow},正是问题所在`);
|
`高处节点的启发值 ${farButHigh} 不大于低处近节点的 ${nearButLow},正是问题所在`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('寻路状态日志被节流:进度刷屏不会把日志写爆', () => {
|
||||||
|
// mineflayer-pathfinder 每用满一个 tick 的计算预算(tickTimeout,默认 40ms)
|
||||||
|
// 就发一次 path_update。不节流时实测 4 分钟写了 3893 条「寻路状态」,
|
||||||
|
// 既淹没真正有用的日志(一天上百 MB,本服磁盘本就只剩 2.4G),
|
||||||
|
// 也让「寻路卡在哪」更难看清。
|
||||||
|
const bot = new FakeBot({ x: 0.5, y: 64, z: 0.5 });
|
||||||
|
bot.pathfinder = {
|
||||||
|
setMovements: () => {},
|
||||||
|
setGoal: () => {},
|
||||||
|
thinkTimeout: 0,
|
||||||
|
tickTimeout: 0,
|
||||||
|
searchRadius: 0
|
||||||
|
};
|
||||||
|
const logs = [];
|
||||||
|
const logger = { log: (msg) => logs.push(String(msg)), error: () => {}, list: () => [], clear: () => {} };
|
||||||
|
const service = new PathingService(bot, defaultConfig(), logger);
|
||||||
|
service.configure();
|
||||||
|
|
||||||
|
// 模拟一次长规划:状态一直是 partial、节点数快速增长
|
||||||
|
for (let i = 0; i < 300; i++) {
|
||||||
|
bot.emit('path_update', { status: 'partial', visitedNodes: i * 20, cost: 90 });
|
||||||
|
}
|
||||||
|
const progressLogs = logs.filter((m) => m.includes('寻路状态'));
|
||||||
|
assert.ok(progressLogs.length >= 2, `不应把进度信息完全丢掉,实际 ${progressLogs.length} 条`);
|
||||||
|
assert.ok(progressLogs.length <= 12, `300 次 partial 应被节流到十几条以内,实际 ${progressLogs.length} 条`);
|
||||||
|
|
||||||
|
// 状态变化是最要紧的信号,必须立刻记一条
|
||||||
|
logs.length = 0;
|
||||||
|
bot.emit('path_update', { status: 'success', visitedNodes: 6100, cost: 90 });
|
||||||
|
assert.strictEqual(logs.filter((m) => m.includes('寻路状态')).length, 1, '状态变化要立刻记录');
|
||||||
|
});
|
||||||
|
|
||||||
// ==================== 长距离分段寻路 ====================
|
// ==================== 长距离分段寻路 ====================
|
||||||
//
|
//
|
||||||
// mineflayer-pathfinder 只能规划已加载区块范围(实测线上约 8 区块 ≈ 130 格),
|
// mineflayer-pathfinder 只能规划已加载区块范围(实测线上约 8 区块 ≈ 130 格),
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户