mirror of
https://github.com/motajs/template.git
synced 2026-11-02 15:04:30 +08:00
feat: 将 HumanBreak 新内容移植入该样板
This commit is contained in:
1 parent
6bf32174d8
commit
bea2725d6a
128 files changed
+11984
-573
No files matched your search
@@ -0,0 +1,133 @@
|
||||
import { isNil } from 'lodash-es';
|
||||
import { IDirtyMark, IDirtyTracker } from './types';
|
||||
|
||||
/**
|
||||
* 布尔类型的脏标记追踪器。当传入 `dirtySince` 的标记不属于当前的追踪器时,会返回 `true`
|
||||
*/
|
||||
export class PrivateBooleanDirtyTracker implements IDirtyTracker<boolean> {
|
||||
/** 标记映射 */
|
||||
private markMap: WeakMap<IDirtyMark, number> = new WeakMap();
|
||||
/** 脏标记 */
|
||||
private dirtyFlag: number = 0;
|
||||
|
||||
mark(): IDirtyMark {
|
||||
const symbol = {};
|
||||
this.markMap.set(symbol, this.dirtyFlag);
|
||||
return symbol;
|
||||
}
|
||||
|
||||
unmark(mark: IDirtyMark): void {
|
||||
this.markMap.delete(mark);
|
||||
}
|
||||
|
||||
dirtySince(mark: IDirtyMark): boolean {
|
||||
const num = this.markMap.get(mark);
|
||||
if (isNil(num)) return true;
|
||||
return num < this.dirtyFlag;
|
||||
}
|
||||
|
||||
hasMark(symbol: IDirtyMark): boolean {
|
||||
return this.markMap.has(symbol);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据标记为脏
|
||||
*/
|
||||
protected dirty() {
|
||||
this.dirtyFlag++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表的脏标记追踪器。当传入 `dirtySince` 的标记不属于当前的追踪器时,会返回空集合
|
||||
*/
|
||||
export class PrivateListDirtyTracker<T extends number>
|
||||
implements IDirtyTracker<Set<T>>
|
||||
{
|
||||
/** 标记映射,键表示在索引,值表示其对应的标记数字 */
|
||||
private readonly markMap: Map<T, number> = new Map();
|
||||
/** 标记 symbol 映射,值表示这个 symbol 对应的标记数字 */
|
||||
private readonly symbolMap: WeakMap<{}, number> = new WeakMap();
|
||||
|
||||
/** 脏标记数字 */
|
||||
private dirtyFlag: number = 0;
|
||||
|
||||
constructor(protected length: number) {}
|
||||
|
||||
mark(): IDirtyMark {
|
||||
const symbol = {};
|
||||
this.symbolMap.set(symbol, this.dirtyFlag);
|
||||
return symbol;
|
||||
}
|
||||
|
||||
unmark(mark: IDirtyMark): void {
|
||||
this.symbolMap.delete(mark);
|
||||
}
|
||||
|
||||
dirtySince(mark: IDirtyMark): Set<T> {
|
||||
const num = this.symbolMap.get(mark);
|
||||
const res = new Set<T>();
|
||||
if (isNil(num)) return res;
|
||||
this.markMap.forEach((v, k) => {
|
||||
if (v > num) res.add(k);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
hasMark(symbol: IDirtyMark): boolean {
|
||||
return this.symbolMap.has(symbol);
|
||||
}
|
||||
|
||||
protected dirty(data: T): void {
|
||||
if (data >= this.length) return;
|
||||
this.dirtyFlag++;
|
||||
this.markMap.set(data, this.dirtyFlag);
|
||||
}
|
||||
|
||||
protected updateLength(length: number) {
|
||||
this.length = length;
|
||||
}
|
||||
}
|
||||
|
||||
export class PrivateMapDirtyTracker<T extends string>
|
||||
implements IDirtyTracker<Record<T, boolean>>
|
||||
{
|
||||
/** 标记映射,键表示名称,值表示其对应的标记数字 */
|
||||
private readonly markMap: Map<T, number> = new Map();
|
||||
/** 标记 symbol 映射,值表示这个 symbol 对应的标记数字 */
|
||||
private readonly symbolMap: WeakMap<{}, number> = new WeakMap();
|
||||
|
||||
/** 脏标记数字 */
|
||||
private dirtyFlag: number = 0;
|
||||
|
||||
constructor(protected length: number) {}
|
||||
|
||||
mark(): IDirtyMark {
|
||||
const symbol = {};
|
||||
this.symbolMap.set(symbol, this.dirtyFlag);
|
||||
return symbol;
|
||||
}
|
||||
|
||||
unmark(mark: IDirtyMark): void {
|
||||
this.symbolMap.delete(mark);
|
||||
}
|
||||
|
||||
dirtySince(mark: IDirtyMark): Record<T, boolean> {
|
||||
const num = this.symbolMap.get(mark) ?? 0;
|
||||
const obj: Partial<Record<T, boolean>> = {};
|
||||
this.markMap.forEach((v, k) => {
|
||||
if (v > num) obj[k] = true;
|
||||
else obj[k] = false;
|
||||
});
|
||||
return obj as Record<T, boolean>;
|
||||
}
|
||||
|
||||
hasMark(symbol: IDirtyMark): boolean {
|
||||
return this.symbolMap.has(symbol);
|
||||
}
|
||||
|
||||
protected dirty(data: T): void {
|
||||
this.dirtyFlag++;
|
||||
this.markMap.set(data, this.dirtyFlag);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { logger } from './logger';
|
||||
import { IHookable, IHookBase, IHookController, IHookObject } from './types';
|
||||
|
||||
export abstract class Hookable<
|
||||
H extends IHookBase = IHookBase,
|
||||
C extends IHookController<H> = IHookController<H>
|
||||
> implements IHookable<H, C>
|
||||
{
|
||||
/** 加载完成的钩子列表 */
|
||||
protected readonly loadedList: Set<IHookObject<H, C>> = new Set();
|
||||
|
||||
/** 钩子列表 */
|
||||
private readonly hookList: Set<IHookObject<H, C>> = new Set();
|
||||
/** 钩子对象到钩子存储对象的映射 */
|
||||
private readonly hookMap: Map<Partial<H>, IHookObject<H, C>> = new Map();
|
||||
/** 钩子控制器到钩子存储对象的映射 */
|
||||
private readonly controllerMap: Map<C, IHookObject<H, C>> = new Map();
|
||||
|
||||
/**
|
||||
* 创建钩子对象的控制器
|
||||
* @param hook 钩子对象
|
||||
*/
|
||||
protected abstract createController(hook: Partial<H>): C;
|
||||
|
||||
addHook(hook: Partial<H>): C {
|
||||
const controller = this.createController(hook);
|
||||
const obj: IHookObject<H, C> = { hook, controller };
|
||||
this.hookMap.set(hook, obj);
|
||||
this.controllerMap.set(controller, obj);
|
||||
this.hookList.add(obj);
|
||||
return controller;
|
||||
}
|
||||
|
||||
loadHook(hook: Partial<H>): void {
|
||||
const obj = this.hookMap.get(hook);
|
||||
if (!obj) {
|
||||
logger.warn(85);
|
||||
return;
|
||||
}
|
||||
hook.awake?.(obj.controller);
|
||||
this.loadedList.add(obj);
|
||||
}
|
||||
|
||||
removeHook(hook: Partial<H>): void {
|
||||
const obj = this.hookMap.get(hook);
|
||||
if (!obj) return;
|
||||
obj.hook.destroy?.(obj.controller);
|
||||
this.hookList.delete(obj);
|
||||
this.loadedList.delete(obj);
|
||||
this.hookMap.delete(hook);
|
||||
this.controllerMap.delete(obj.controller);
|
||||
}
|
||||
|
||||
removeHookByController(hook: C): void {
|
||||
const obj = this.controllerMap.get(hook);
|
||||
if (!obj) return;
|
||||
obj.hook.destroy?.(obj.controller);
|
||||
this.hookList.delete(obj);
|
||||
this.loadedList.delete(obj);
|
||||
this.controllerMap.delete(hook);
|
||||
this.hookMap.delete(obj.hook);
|
||||
}
|
||||
|
||||
forEachHook<T>(fn: (hook: Partial<H>, controller: C) => T): T[] {
|
||||
const arr: T[] = [];
|
||||
this.loadedList.forEach(v => {
|
||||
arr.push(fn(v.hook, v.controller));
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
export class HookController<H extends IHookBase> implements IHookController<H> {
|
||||
constructor(
|
||||
readonly hookable: IHookable<H, IHookController<H>>,
|
||||
readonly hook: Partial<H>
|
||||
) {}
|
||||
|
||||
load(): void {
|
||||
this.hookable.loadHook(this.hook);
|
||||
}
|
||||
|
||||
unload(): void {
|
||||
this.hookable.removeHookByController(this);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,5 @@
|
||||
export * from './dirtyTracker';
|
||||
export * from './hook';
|
||||
export * from './logger';
|
||||
export * from './utils';
|
||||
export * from './types';
|
||||
@@ -26,6 +26,26 @@
|
||||
"24": "Cannot decode source type of '$1', since there is no registered decoder for that type.",
|
||||
"25": "Unknown audio type. Header: '$1'",
|
||||
"26": "Uncaught error when fetching stream data from '$1'. Error info: $2.",
|
||||
"27": "No autotile connection data, please ensure you have created autotile connection map.",
|
||||
"28": "Cannot compile map render shader.",
|
||||
"29": "Cannot get uniform location of map render shader.",
|
||||
"30": "",
|
||||
"31": "No asset data is specified when rending map.",
|
||||
"32": "Every layer added to map renderer must share the same size. Different layer: $1.",
|
||||
"33": "Map layer transfered to vertex generator must belong to the renderer that the generator use.",
|
||||
"34": "The texture of moving block must be a part of built asset.",
|
||||
"35": "Tile background transfered to map renderer does not exists.",
|
||||
"36": "Tile background transfered to map renderer has no frame data.",
|
||||
"37": "Frame of tile background transfered to map renderer must share the same size.",
|
||||
"38": "Cached texture cannot be convert to asset. This is likely an internal bug, please contact us.",
|
||||
"39": "Offset pool size exceeds WebGL2 limitation, ensure size type of your big image is less than $1.",
|
||||
"40": "Material used by map block $1 is not found in built asset. Please ensure you have pack it into asset.",
|
||||
"41": "You are trying to use a texture on moving block whose offset is not in the offset pool, please build it into asset after loading.",
|
||||
"42": "The '$1' property of map-render element is required.",
|
||||
"43": "Cannot bind face direction to main block $1, please call malloc in advance.",
|
||||
"44": "Cannot bind face direction to main block $1, since main direction cannot be override.",
|
||||
"45": "Cannot add $1 map renderer extension, since $1 already exists for the given state.",
|
||||
"46": "Cannot execute close door action on $1,$2, since the given position is not empty.",
|
||||
"1201": "Floor-damage extension needs 'floor-binder' extension as dependency."
|
||||
},
|
||||
"warn": {
|
||||
@@ -94,6 +114,34 @@
|
||||
"63": "Uncaught promise error in waiting box component. Error reason: $1",
|
||||
"64": "Text node type and block type mismatch: '$1' vs '$2'",
|
||||
"65": "Cannot bind a weather controller twice.",
|
||||
"66": "Texture identifier repeated: $1",
|
||||
"67": "Cannot set alias '$1' for texture $2, since there is already an alias '$3' for it.",
|
||||
"68": "Cannot set alias '$1' for texture $2, since '$1' is already an alias for $3.",
|
||||
"69": "Clip totally exceeds texture's dimensions, no operation will be alppied.",
|
||||
"70": "An TextureAnimater can only be created once.",
|
||||
"71": "Cannot start animate when animater not created on texture.",
|
||||
"72": "Frame count delivered to frame-based animater need to exactly divide texture's height.",
|
||||
"73": "Consistent size is needed when using WebGL2 composer.",
|
||||
"74": "Frame size not match texture's size, incomplete data will be skipped.",
|
||||
"75": "Material count splitted by grid does not match alias count, some material may have no alias or some alias may not map to a texture. Splitted: $1, alias: $2.",
|
||||
"76": "Cannot pipe texture store when asset builder has been started.",
|
||||
"77": "Texture is clipped to an area of 0. Ensure that the texture contains your clip rect and clip rect's area is not zero.",
|
||||
"78": "Adding tileset source must follow index order.",
|
||||
"79": "Assets can only be built once.",
|
||||
"80": "Parameter count of MapLayer.getMapData must be 0 or 4.",
|
||||
"81": "Map data to get is partially (or totally) out of range. Overflowed area will be filled with zero.",
|
||||
"82": "Big image offset size is larger than 64. Considier reduce big image offset bucket.",
|
||||
"83": "It seems that you call '$1' too frequently. This will extremely affect game's performance, so considering integrate them into one 'updateArea' or 'updateBlockList' call.",
|
||||
"84": "Cannot set alias '$1' for layer, since '$1' is already an alias for another layer.",
|
||||
"85": "Hook to load does not belong to current hookable object.",
|
||||
"86": "Cannot restore vertex data since delivered state does not belong to current vertex generator instance.",
|
||||
"87": "Map texture asset cache not hit. This has no effect on rendering, but may extremely affect performance, please see the following link to find solution.",
|
||||
"88": "No hero image is specified, please set image in advance.",
|
||||
"89": "Cannot set hero image, since there is no image named '$1'.",
|
||||
"90": "Cannot set hero image, since delivered image has incorrect format or size.",
|
||||
"91": "Cannot add follower, since specified follower number $1 does not exist.",
|
||||
"92": "Followers can only be added when the last follower is not moving.",
|
||||
"93": "Followers can only be removed when the last follower is not moving.",
|
||||
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency."
|
||||
}
|
||||
}
|
||||
@@ -72,8 +72,15 @@ export class Logger {
|
||||
let paramNum = '';
|
||||
while (++pointer < text.length) {
|
||||
const char = text[pointer];
|
||||
const next = text[pointer + 1];
|
||||
|
||||
if (char === '$' && text[pointer - 1] !== '\\') {
|
||||
if (char === '\\' && next === '$') {
|
||||
str += '$';
|
||||
pointer++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '$' && nums.has(next)) {
|
||||
inParam = true;
|
||||
continue;
|
||||
}
|
||||
@@ -82,7 +89,7 @@ export class Logger {
|
||||
if (nums.has(char)) {
|
||||
paramNum += char;
|
||||
}
|
||||
if (!nums.has(text[pointer + 1])) {
|
||||
if (!nums.has(next)) {
|
||||
inParam = false;
|
||||
const num = Number(paramNum);
|
||||
paramNum = '';
|
||||
@@ -125,8 +132,7 @@ export class Logger {
|
||||
hideTipText();
|
||||
}
|
||||
const n = Math.floor(code / 50) + 1;
|
||||
const n2 = code % 50;
|
||||
const url = `${location.origin}/_docs/logger/error/error${n}.html#error-code-${n2}`;
|
||||
const url = `${location.origin}/_docs/logger/error/error${n}.html#error-code-${code}`;
|
||||
console.error(`[ERROR Code ${code}] ${text} See ${url}`);
|
||||
}
|
||||
}
|
||||
@@ -159,8 +165,7 @@ export class Logger {
|
||||
hideTipText();
|
||||
}
|
||||
const n = Math.floor(code / 50) + 1;
|
||||
const n2 = code % 50;
|
||||
const url = `${location.origin}/_docs/logger/warn/warn${n}.html#warn-code-${n2}`;
|
||||
const url = `${location.origin}/_docs/logger/warn/warn${n}.html#warn-code-${code}`;
|
||||
console.warn(`[WARNING Code ${code}] ${text} See ${url}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
//#region 脏标记
|
||||
|
||||
export interface IDirtyMarker<T> {
|
||||
/**
|
||||
* 标记为脏,即进行了一次更新
|
||||
* @param data 传递给追踪器的数据
|
||||
*/
|
||||
dirty(data: T): void;
|
||||
}
|
||||
|
||||
export interface IDirtyMark {}
|
||||
|
||||
export interface IDirtyTracker<T> {
|
||||
/**
|
||||
* 对状态进行标记
|
||||
*/
|
||||
mark(): IDirtyMark;
|
||||
|
||||
/**
|
||||
* 取消指定标记符号
|
||||
* @param mark 标记符号
|
||||
*/
|
||||
unmark(mark: IDirtyMark): void;
|
||||
|
||||
/**
|
||||
* 从指定标记符号开始,数据是否发生了变动
|
||||
* @param mark 标记符号
|
||||
*/
|
||||
dirtySince(mark: IDirtyMark): T;
|
||||
|
||||
/**
|
||||
* 当前追踪器是否包含指定标记符号
|
||||
* @param symbol 标记符号
|
||||
*/
|
||||
hasMark(symbol: IDirtyMark): boolean;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 钩子
|
||||
|
||||
export interface IHookObject<
|
||||
H extends IHookBase,
|
||||
C extends IHookController<H>
|
||||
> {
|
||||
/** 钩子对象 */
|
||||
readonly hook: Partial<H>;
|
||||
/** 钩子控制器 */
|
||||
readonly controller: C;
|
||||
}
|
||||
|
||||
export interface IHookController<H extends IHookBase = IHookBase> {
|
||||
/** 控制器的钩子对象 */
|
||||
readonly hook: Partial<H>;
|
||||
|
||||
/**
|
||||
* 加载此控制器对应的钩子对象
|
||||
*/
|
||||
load(): void;
|
||||
|
||||
/**
|
||||
* 卸载此控制器对应的钩子对象,之后此钩子将不会再被触发
|
||||
*/
|
||||
unload(): void;
|
||||
}
|
||||
|
||||
export interface IHookBase {
|
||||
/**
|
||||
* 加载此钩子对象
|
||||
* @param controller 钩子控制器对象
|
||||
*/
|
||||
awake(controller: IHookController<this>): void;
|
||||
|
||||
/**
|
||||
* 摧毁此钩子对象
|
||||
* @param controller 钩子控制器对象
|
||||
*/
|
||||
destroy(controller: IHookController<this>): void;
|
||||
}
|
||||
|
||||
export interface IHookable<
|
||||
H extends IHookBase = IHookBase,
|
||||
C extends IHookController<H> = IHookController<H>
|
||||
> {
|
||||
/**
|
||||
* 添加钩子对象,返回控制该钩子对象的控制器
|
||||
* @param hook 钩子对象
|
||||
*/
|
||||
addHook(hook: Partial<H>): C;
|
||||
|
||||
/**
|
||||
* 加载指定的钩子对象
|
||||
* @param hook 钩子对象
|
||||
*/
|
||||
loadHook(hook: Partial<H>): void;
|
||||
|
||||
/**
|
||||
* 移除钩子对象,会调用钩子对象的 `destroy` 方法
|
||||
* @param hook 钩子对象
|
||||
*/
|
||||
removeHook(hook: Partial<H>): void;
|
||||
|
||||
/**
|
||||
* 传入钩子控制器,移除对应的钩子对象
|
||||
* @param hook 钩子控制器
|
||||
*/
|
||||
removeHookByController(hook: C): void;
|
||||
|
||||
/**
|
||||
* 遍历每个钩子,执行顺序不固定
|
||||
* @param fn 对每个钩子执行的函数
|
||||
* @returns 每个钩子的返回值组成的数组,顺序不固定
|
||||
*/
|
||||
forEachHook<T>(fn: (hook: Partial<H>, controller: C) => T): T[];
|
||||
}
|
||||
|
||||
//#endregion
|
||||
Reference in new issue
Block a user