mirror of
https://github.com/motajs/template.git
synced 2026-10-08 13:30:18 +08:00
refactor: 被动录像步应当在主动录像步执行期间完成
This commit is contained in:
parent
e25ef3fa93
commit
75a39ad573
@ -24,10 +24,6 @@ interface INormalizedParam {
|
||||
* - 9: string,带 int32 长度前缀
|
||||
* - 10 ~ 31: 留空,以备后续新增类型
|
||||
* - 32 ~ 255: n - 32 长度的字符串
|
||||
*
|
||||
* 该类型码表属于录像参数编解码格式的一部分,遵循只增不改的约定:新语义必须分配
|
||||
* 新的类型码,禁止复用既有类型码。历史上 5/6/7/8 与短字符串基址(长度 + 9)曾
|
||||
* 发生一次性重新分配,项目未发布,旧录像不在兼容范围内。
|
||||
*/
|
||||
readonly paramType: number;
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@ import {
|
||||
} from '@motajs/common';
|
||||
import {
|
||||
IReplayArray,
|
||||
IReplayCommand,
|
||||
IReplayPassiveHandler,
|
||||
IReplayReadStream,
|
||||
IReplaySandbox,
|
||||
@ -16,7 +15,16 @@ import {
|
||||
ReplayCommandResult,
|
||||
ReplayCommandType
|
||||
} from './types';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
interface IReplayStepResult {
|
||||
/** 录像是否播放成功 */
|
||||
readonly success: boolean;
|
||||
/**
|
||||
* 当前正在播放的录像步,注意如果是 finalize 失败,
|
||||
* 那么这里也应是还未播放的那个本来应该播放的录像步,而不是需要 finalize 的录像步
|
||||
*/
|
||||
readonly step: IReplayStepHandler | null;
|
||||
}
|
||||
|
||||
export class ReplaySandbox
|
||||
extends Hookable<IReplaySandboxHooks>
|
||||
@ -35,17 +43,15 @@ export class ReplaySandbox
|
||||
/** 暂停 `Promise` 的 `resolve` 函数 */
|
||||
private pauseResolve: () => void = () => {};
|
||||
|
||||
/** 被动录像步兑现函数 */
|
||||
private passiveResolve: (status: ReplayCommandResult) => void = () => {};
|
||||
/** 下一步被动录像步的 `StepHandler`,注意不是 `PassiveHandler` */
|
||||
private passiveHandler: IReplayStepHandler | null = null;
|
||||
|
||||
/** 录像的流式读取器 */
|
||||
private reader: Readonly<IReplayReadStream>;
|
||||
|
||||
/** 上一步播放的指令 */
|
||||
/** 上一个主动录像步指令 */
|
||||
private last: number = -1;
|
||||
|
||||
/** 等待下一步播放的录像步,包括主动和被动 */
|
||||
private appendingStep: IReplayStepHandler | null = null;
|
||||
|
||||
constructor(
|
||||
readonly route: IReplayArray,
|
||||
readonly system: IReplaySystem,
|
||||
@ -61,20 +67,6 @@ export class ReplaySandbox
|
||||
return new HookController(this, hook);
|
||||
}
|
||||
|
||||
getPassive(): IReplayPassiveHandler | null {
|
||||
if (!this.passiveHandler) {
|
||||
logger.error(73);
|
||||
return null;
|
||||
}
|
||||
const handler: IReplayPassiveHandler = {
|
||||
step: this.passiveHandler,
|
||||
next: status => {
|
||||
this.passiveResolve(status);
|
||||
}
|
||||
};
|
||||
return handler;
|
||||
}
|
||||
|
||||
setSpeed(speed: number): void {
|
||||
this.speed = speed;
|
||||
this.forEachHook(hook => hook.onSpeedSet?.(speed));
|
||||
@ -84,11 +76,162 @@ export class ReplaySandbox
|
||||
return this.reader.index;
|
||||
}
|
||||
|
||||
//#region 录像步
|
||||
|
||||
getPassive(): IReplayPassiveHandler | null {
|
||||
const curr = this.appendingStep;
|
||||
if (!curr) {
|
||||
logger.error(73);
|
||||
return null;
|
||||
}
|
||||
const command = this.system.getCommand(curr.command);
|
||||
if (!command) {
|
||||
logger.warn(157, curr.command.toString());
|
||||
return null;
|
||||
}
|
||||
if (command.type === ReplayCommandType.Active) {
|
||||
logger.error(73);
|
||||
return null;
|
||||
}
|
||||
|
||||
const handler: IReplayPassiveHandler = {
|
||||
step: curr,
|
||||
next: async status => {
|
||||
const result = this.checkReplayStatus(status, 'passive');
|
||||
if (result) {
|
||||
this.appendingStep = this.reader.read();
|
||||
await Promise.all(
|
||||
this.forEachHook(hook => hook.onStep?.(curr))
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发上一步指令的连续步骤后处理
|
||||
*/
|
||||
private async finalizeLast(): Promise<ReplayCommandResult> {
|
||||
if (this.last === -1) return ReplayCommandResult.Success;
|
||||
const last = this.system.getCommand(this.last);
|
||||
if (!last) {
|
||||
logger.warn(157, this.last.toString());
|
||||
return ReplayCommandResult.Failed;
|
||||
}
|
||||
return last.finalize?.() ?? ReplayCommandResult.Success;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查录像执行状态,进行适当的控制台输出
|
||||
* @param status 录像执行状态
|
||||
* @param method 录像步执行方法,可填 `active` `passive` `finalize`
|
||||
*/
|
||||
private checkReplayStatus(
|
||||
status: ReplayCommandResult,
|
||||
method: string
|
||||
): boolean {
|
||||
if (status === ReplayCommandResult.Success) return true;
|
||||
else if (status === ReplayCommandResult.Failed) {
|
||||
logger.error(72, method);
|
||||
return false;
|
||||
} else {
|
||||
logger.log(
|
||||
`Ignored replay error with a Ignored status returned by ${method} command.`
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private async replayStep(): Promise<IReplayStepResult> {
|
||||
if (!this.playing || this.ended) {
|
||||
return {
|
||||
step: null,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
if (this.reader.expired) {
|
||||
logger.warn(156);
|
||||
return {
|
||||
step: null,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
const curr = this.appendingStep;
|
||||
|
||||
// 当录像步已经放完时,对最后一个录像步进行 finalize 处理
|
||||
if (!curr) {
|
||||
const ne = await this.finalizeLast();
|
||||
const success = this.checkReplayStatus(ne, 'finalize');
|
||||
if (success) {
|
||||
this.last = -1;
|
||||
this.ending = true;
|
||||
}
|
||||
return {
|
||||
step: null,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
|
||||
const command = this.system.getCommand(curr.command);
|
||||
if (!command) {
|
||||
logger.warn(157, curr.command.toString());
|
||||
return {
|
||||
step: null,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
|
||||
// 如果当前录像步是被动录像步,说明它没有在上一个主动录像步期间完成,则忽略并进入后续播放
|
||||
if (command.type !== ReplayCommandType.Active) {
|
||||
logger.warn(194, curr.command.toString(), this.last.toString());
|
||||
this.appendingStep = this.reader.read();
|
||||
return {
|
||||
step: curr,
|
||||
success: true
|
||||
};
|
||||
}
|
||||
|
||||
// 如果当前录像步指令与上一个主动录像步不同,那么进行 finalize 后处理
|
||||
if (curr.command !== this.last) {
|
||||
const ne = await this.finalizeLast();
|
||||
const success = this.checkReplayStatus(ne, 'finalize');
|
||||
if (!success) {
|
||||
return {
|
||||
step: curr,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
}
|
||||
this.last = curr.command;
|
||||
|
||||
// 在执行前获取下一个录像步,因为被动录像步一定会在其紧跟着的前面的主动录像步中进行读取,
|
||||
// 所以在此时必须确定下一个录像步是什么,这样 `getPassive` 才能判断是否真的是被动录像步。
|
||||
this.appendingStep = this.reader.read();
|
||||
|
||||
// 然后执行当前录像步,此时一定是主动录像步,可以直接调
|
||||
const status = await command.execute(curr);
|
||||
|
||||
const success = this.checkReplayStatus(
|
||||
status,
|
||||
command.type === ReplayCommandType.Active ? 'execute' : 'passive'
|
||||
);
|
||||
|
||||
return {
|
||||
step: curr,
|
||||
success
|
||||
};
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 录像控制
|
||||
|
||||
/**
|
||||
* 开始录像重播循环
|
||||
*/
|
||||
private async startReplayLoop() {
|
||||
if (this.ended) return;
|
||||
if (this.ended || this.pausing) return;
|
||||
if (this.reader.expired) {
|
||||
logger.warn(156);
|
||||
return;
|
||||
@ -105,11 +248,15 @@ export class ReplaySandbox
|
||||
logger.warn(156);
|
||||
break;
|
||||
}
|
||||
const success = await this.step();
|
||||
if (!success) break;
|
||||
const res = await this.replayStep();
|
||||
if (!res.success) break;
|
||||
await Promise.all(
|
||||
this.forEachHook(hook => hook.onStep?.(res.step!))
|
||||
);
|
||||
}
|
||||
if (this.ending) {
|
||||
this.ended = true;
|
||||
this.forEachHook(hook => hook.onStopReplay?.());
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,109 +291,15 @@ export class ReplaySandbox
|
||||
this.forEachHook(hook => hook.onStopReplay?.());
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发上一步指令的连续步骤后处理
|
||||
*/
|
||||
private async finalizeLast(): Promise<ReplayCommandResult> {
|
||||
if (this.last === -1) return ReplayCommandResult.Success;
|
||||
const last = this.system.getCommand(this.last);
|
||||
if (!last) {
|
||||
logger.warn(157, this.last.toString());
|
||||
return ReplayCommandResult.Failed;
|
||||
}
|
||||
return last.finalize?.() ?? ReplayCommandResult.Success;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行下一个录像步
|
||||
* @param command 录像步对象
|
||||
* @param next 录像步参数
|
||||
*/
|
||||
private async executeCommand(
|
||||
command: IReplayCommand,
|
||||
next: IReplayStepHandler
|
||||
): Promise<ReplayCommandResult> {
|
||||
if (command.type === ReplayCommandType.Passive) {
|
||||
// 被动录像步
|
||||
const { promise, resolve } =
|
||||
Promise.withResolvers<ReplayCommandResult>();
|
||||
this.passiveResolve = resolve;
|
||||
const status = await promise;
|
||||
if (isNil(status)) {
|
||||
logger.error(71);
|
||||
return ReplayCommandResult.Failed;
|
||||
} else {
|
||||
return status;
|
||||
}
|
||||
} else {
|
||||
// 主动录像步
|
||||
const status = command.execute(next);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查录像执行状态,进行适当的控制台输出
|
||||
* @param status 录像执行状态
|
||||
* @param method 录像步执行方法,可填 `active` `passive` `finalize`
|
||||
*/
|
||||
private checkReplayStatus(
|
||||
status: ReplayCommandResult,
|
||||
method: string
|
||||
): boolean {
|
||||
if (status === ReplayCommandResult.Success) return true;
|
||||
else if (status === ReplayCommandResult.Failed) {
|
||||
logger.error(72, method);
|
||||
return false;
|
||||
} else {
|
||||
logger.log(
|
||||
`Ignored replay error with a Ignored status returned by ${method} command.`
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
async step(): Promise<boolean> {
|
||||
if (!this.playing || this.ended) return false;
|
||||
if (this.reader.expired) {
|
||||
logger.warn(156);
|
||||
return false;
|
||||
}
|
||||
const next = this.reader.read();
|
||||
|
||||
// finalize 执行
|
||||
if (!next || next.command !== this.last) {
|
||||
const ne = await this.finalizeLast();
|
||||
const success = this.checkReplayStatus(ne, 'finalize');
|
||||
if (success) {
|
||||
if (!next) {
|
||||
this.last = -1;
|
||||
this.ending = true;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.last = next.command;
|
||||
|
||||
// 获取指令本身
|
||||
const command = this.system.getCommand(next.command);
|
||||
if (!command) {
|
||||
logger.warn(157, next.command.toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
const status = await this.executeCommand(command, next);
|
||||
const success = this.checkReplayStatus(
|
||||
status,
|
||||
command.type === ReplayCommandType.Active ? 'execute' : 'passive'
|
||||
);
|
||||
if (success) {
|
||||
await Promise.all(this.forEachHook(hook => hook.onStep?.(next)));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
const res = await this.replayStep();
|
||||
if (res.success) {
|
||||
await Promise.all(
|
||||
this.forEachHook(hook => hook.onStep?.(res.step!))
|
||||
);
|
||||
}
|
||||
return res.success;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ export interface IReplayPassiveHandler {
|
||||
* 本被动录像步执行完毕,进入下一个录像步
|
||||
* @param status 被动录像步执行情况
|
||||
*/
|
||||
next(status: ReplayCommandResult): void;
|
||||
next(status: ReplayCommandResult): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IReplayCommand {
|
||||
@ -122,7 +122,8 @@ export interface IReplaySandbox extends IHookable<IReplaySandboxHooks> {
|
||||
readonly playing: boolean;
|
||||
|
||||
/**
|
||||
* 获取被动录像参数。如果下一步是被动录像,那么获取其参数,如果不是,返回 `null`
|
||||
* 获取被动录像参数。如果下一步是被动录像,那么获取其参数,如果不是,返回 `null`。
|
||||
* 当调用 `IReplayPassiveHandler` 后,才会进入下一个录像步,否则多次调用 `getPassive`,会得到相同的结果。
|
||||
*/
|
||||
getPassive(): IReplayPassiveHandler | null;
|
||||
|
||||
@ -159,7 +160,7 @@ export interface IReplaySandbox extends IHookable<IReplaySandboxHooks> {
|
||||
|
||||
/**
|
||||
* 单步播放一步,返回的 Promise 在该步渲染完成后兑现
|
||||
* @returns 当前步是否正常播放完成
|
||||
* @returns 当前步的播放结果
|
||||
*/
|
||||
step(): Promise<boolean>;
|
||||
}
|
||||
@ -188,8 +189,7 @@ export interface IReplayReadStream {
|
||||
expired: boolean;
|
||||
|
||||
/**
|
||||
* 读取当前指针位置的录像步并将指针推进到下一步
|
||||
* 超出录像长度时返回 null
|
||||
* 读取当前指针位置的录像步,并将录像指针推进到下一步,超出录像长度时返回 `null`
|
||||
*/
|
||||
read(): IReplayStepHandler | null;
|
||||
|
||||
|
||||
@ -276,6 +276,7 @@
|
||||
"191": "Cannot cache autotile since no its frame or type data stored.",
|
||||
"192": "A FaceGroup.Dir8 face handler registration is expected.",
|
||||
"193": "Cannot load item state since no item id registration for item num $1.",
|
||||
"194": "Expected passive replay command executed during the last active replay command executing, but a passive command $1 was got after active replay command $2 executing. Replay will continue and ignore this issue.",
|
||||
"1001": "Event(setBlock): Unknown tile '$1' when setting block."
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user