From 9ae28c68cc613a8e0e8e1cd9f45c9d1023ff5e19 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Thu, 17 Sep 2026 12:42:48 +0800 Subject: [PATCH] =?UTF-8?q?fix(07-10):=20CR-01=20ReplayArray.set=20?= =?UTF-8?q?=E7=B4=A2=E5=BC=95=E5=9B=9E=E9=80=80=E8=B5=B7=E7=82=B9=E4=B8=8E?= =?UTF-8?q?=E6=9C=AB=E6=AD=A5=E7=BB=88=E7=82=B9=E5=AF=B9=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - set() 改用 getParamRange(index) 取 [start, end),末步终点取 paramUsed,不再直读未初始化的 indexArray[index + 1] - 索引回退循环起点由参数字节偏移 paramStart + 1 改为命令索引 index + 1,与 delete() 同口径 - types.ts 的 IReplayArray.set jsDoc 写明允许参数编码长度变化及其后步骤索引平移 - array.test.ts 新增 6 条 CR-01 回归:中间步增长/缩短、末步增长/缩短、读流遍历(IN-03 场景) --- .../data-common/src/replay/array.test.ts | 112 ++++++++++++++++++ packages-user/data-common/src/replay/array.ts | 10 +- packages-user/data-common/src/replay/types.ts | 3 +- 3 files changed, 120 insertions(+), 5 deletions(-) diff --git a/packages-user/data-common/src/replay/array.test.ts b/packages-user/data-common/src/replay/array.test.ts index a55f35e..3ce3db9 100644 --- a/packages-user/data-common/src/replay/array.test.ts +++ b/packages-user/data-common/src/replay/array.test.ts @@ -245,6 +245,118 @@ describe('ReplayArray single operations', () => { }); }); +describe('ReplayArray set index maintenance', () => { + // 验证中间步 set 增长参数编码长度后其后步骤的索引同步平移,逐步读回命令与参数均正确(CR-01) + it('keeps later steps readable after a middle set grows the param length', () => { + const array = createArray(); + array.add(1, [1]); + array.add(2, [2]); + array.add(3, [30]); + + array.set(1, 5, [true, true, true]); + + expect(array.length).toBe(3); + expect(array.get(0)).toEqual({ command: 1, params: [1], index: 0 }); + expect(array.get(1)).toEqual({ + command: 5, + params: [true, true, true], + index: 1 + }); + expect(array.get(2)).toEqual({ command: 3, params: [30], index: 2 }); + }); + + // 验证中间步 set 缩短参数编码长度后后续参数字节前移,且末位残留字节被清零 + it('keeps later steps readable after a middle set shrinks the param length', () => { + const array = createArray(); + array.add(1, [1]); + array.add(2, [2]); + array.add(3, [30]); + + array.set(1, 5, []); + + expect(array.length).toBe(3); + expect(array.get(0)).toEqual({ command: 1, params: [1], index: 0 }); + expect(array.get(1)).toEqual({ command: 5, params: [], index: 1 }); + expect(array.get(2)).toEqual({ command: 3, params: [30], index: 2 }); + }); + + // 验证末步 set 增长参数时以 paramUsed 作为参数终点,不产生位移,且之后仍能正确追加 + it('sets the last step with more params and appends afterwards', () => { + const array = createArray(); + array.add(1, [1]); + array.add(2, [2]); + + array.set(1, 5, [true, true, true]); + + expect(array.length).toBe(2); + expect(array.get(0)).toEqual({ command: 1, params: [1], index: 0 }); + expect(array.get(1)).toEqual({ + command: 5, + params: [true, true, true], + index: 1 + }); + + array.add(3, [30]); + expect(array.get(2)).toEqual({ command: 3, params: [30], index: 2 }); + }); + + // 验证末步 set 缩短参数后 paramUsed 相应减少,后续追加的参数落在正确偏移上 + it('sets the last step with fewer params and appends afterwards', () => { + const array = createArray(); + array.add(1, [1]); + array.add(2, [300, 400, 500]); + + array.set(1, 5, [7]); + + expect(array.get(0)).toEqual({ command: 1, params: [1], index: 0 }); + expect(array.get(1)).toEqual({ command: 5, params: [7], index: 1 }); + + array.add(3, [30]); + expect(array.get(2)).toEqual({ command: 3, params: [30], index: 2 }); + }); + + // 验证中间步 set 增长参数后,读取流逐条读回与按索引读回结果一致 + it('reads every step through a read stream after a growing set', () => { + const array = createArray(); + array.add(1, [10]); + array.add(2, [20]); + array.add(3, [30]); + array.set(1, 5, [true, 'ab', 300]); + + const expected: ReadonlyArray = [ + [1, [10]], + [5, [true, 'ab', 300]], + [3, [30]] + ]; + const stream = array.createReadStream(0); + expected.forEach(([command, params], i) => { + expectStepTyped(stream.read()!, command, params, i + 1); + const step = array.get(i); + expect(step.command).toBe(command); + params.forEach((param, j) => + expectParamTyped(step.params[j], param) + ); + }); + expect(stream.read()).toBeNull(); + expect(stream.index).toBe(3); + }); + + // 验证末步 set 改变参数长度后,读取流仍能读完两步并在末尾返回 null + it('reads every step through a read stream after setting the last step', () => { + const array = createArray(); + array.add(1, [10]); + array.add(2, [20]); + + array.set(1, 5, [true, true, true, true, true]); + + const stream = array.createReadStream(0); + expectStepTyped(stream.read()!, 1, [10], 1); + expectStepTyped(stream.read()!, 5, [true, true, true, true, true], 2); + expect(stream.read()).toBeNull(); + expect(stream.index).toBe(2); + }); +}); + describe('ReplayArray param codec', () => { // 验证 boolean 参数按 type 0 写入并读回布尔值 it('round-trips boolean parameters', () => { diff --git a/packages-user/data-common/src/replay/array.ts b/packages-user/data-common/src/replay/array.ts index c31ff90..5ade216 100644 --- a/packages-user/data-common/src/replay/array.ts +++ b/packages-user/data-common/src/replay/array.ts @@ -503,8 +503,10 @@ export class ReplayArray implements IReplayArray { if (this.disabled > 0) return; const normalized = this.normalizeParamList(params); const length = this.calculateParamsLength(normalized); - const paramStart = this.indexArray[index]; - const nextParam = this.indexArray[index + 1]; + // 末步的参数终点取 paramUsed,与 delete 同口径,避免读到未初始化的 indexArray[index + 1] + const range = this.getParamRange(index); + const paramStart = range.start; + const nextParam = range.end; const paramLength = nextParam - paramStart; const deltaLength = length - paramLength; if (deltaLength > 0) { @@ -530,8 +532,8 @@ export class ReplayArray implements IReplayArray { // 然后设置参数数组 this.setParamArray(paramStart, normalized); - // 最后调整索引数组 - for (let i = paramStart + 1; i < this.length; i++) { + // 最后调整索引数组,从本命令的下一条起平移:本命令的起始索引自身不变 + for (let i = index + 1; i < this.length; i++) { this.indexArray[i] += deltaLength; } diff --git a/packages-user/data-common/src/replay/types.ts b/packages-user/data-common/src/replay/types.ts index 328d79e..7aba39e 100644 --- a/packages-user/data-common/src/replay/types.ts +++ b/packages-user/data-common/src/replay/types.ts @@ -211,7 +211,8 @@ export interface IReplayArray { delete(index: number): void; /** - * 修改指定索引处的录像步。 + * 修改指定索引处的录像步。新的参数列表允许与原有参数编码长度不同, + * 此情形下其后所有录像步的参数起始索引会同步平移。 * 由于此操作会涉及大量的内存迁移,耗时较长,因此不建议频繁调用。 * @param index 要修改的步索引 * @param command 新的指令标识