mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-06-08 16:27:58 +08:00
312 lines
9.0 KiB
TypeScript
312 lines
9.0 KiB
TypeScript
import { EventEmitter } from 'eventemitter3';
|
||
import { logger } from '@motajs/common';
|
||
import { MotaOffscreenCanvas2D } from '@motajs/render-core';
|
||
|
||
interface BlockCacherEvent {
|
||
split: [];
|
||
}
|
||
|
||
interface BlockData {
|
||
/** 横向宽度,包括rest的那一个块 */
|
||
width: number;
|
||
/** 纵向宽度,包括rest的那一个块 */
|
||
height: number;
|
||
/** 横向最后一个块的宽度 */
|
||
restWidth: number;
|
||
/** 纵向最后一个块的高度 */
|
||
restHeight: number;
|
||
}
|
||
|
||
export interface IBlockCacheable {
|
||
/**
|
||
* 摧毁这个缓存元素
|
||
*/
|
||
destroy(): void;
|
||
}
|
||
|
||
/**
|
||
* 简单分块缓存类,内容包含元素与分块两种,其中元素是最小单元,分块是缓存单元。
|
||
* 拿楼层举例,假如我将楼层按照13x13划分缓存,那么元素就是每个图块,而分块就是这13x13的缓存分块。
|
||
* 为方便区分,在相关函数的注释最后,都会有`xx -> yy`的说明,
|
||
* 其中xx说明传入的数据是元素还是分块的数据,而yy表示其返回值或转换为的值
|
||
*/
|
||
export class BlockCacher<
|
||
T extends IBlockCacheable
|
||
> extends EventEmitter<BlockCacherEvent> {
|
||
/** 区域宽度 */
|
||
width: number;
|
||
/** 区域高度 */
|
||
height: number;
|
||
/** 分块大小 */
|
||
blockSize: number;
|
||
/** 分块信息 */
|
||
blockData: BlockData = {
|
||
width: 0,
|
||
height: 0,
|
||
restWidth: 0,
|
||
restHeight: 0
|
||
};
|
||
/** 缓存深度,例如填4的时候表示每格包含4个缓存 */
|
||
cacheDepth: number = 1;
|
||
|
||
/** 缓存内容,计算公式为 (x + y * width) * depth + deep */
|
||
cache: Map<number, T> = new Map();
|
||
|
||
constructor(
|
||
width: number,
|
||
height: number,
|
||
size: number,
|
||
depth: number = 1
|
||
) {
|
||
super();
|
||
this.width = width;
|
||
this.height = height;
|
||
this.blockSize = size;
|
||
this.cacheDepth = depth;
|
||
this.split();
|
||
}
|
||
|
||
/**
|
||
* 设置区域大小
|
||
* @param width 区域宽度
|
||
* @param height 区域高度
|
||
*/
|
||
size(width: number, height: number) {
|
||
this.width = width;
|
||
this.height = height;
|
||
this.split();
|
||
}
|
||
|
||
/**
|
||
* 设置分块大小
|
||
*/
|
||
setBlockSize(size: number) {
|
||
this.blockSize = size;
|
||
this.split();
|
||
}
|
||
|
||
/**
|
||
* 设置缓存深度,设置后会自动将旧缓存移植到新缓存中,最大值为31
|
||
* @param depth 缓存深度
|
||
*/
|
||
setCacheDepth(depth: number) {
|
||
if (depth > 31) {
|
||
logger.error(11);
|
||
return;
|
||
}
|
||
const old = this.cache;
|
||
const before = this.cacheDepth;
|
||
this.cache = new Map();
|
||
old.forEach((v, k) => {
|
||
const index = Math.floor(k / before);
|
||
const deep = k % before;
|
||
this.cache.set(index * depth + deep, v);
|
||
});
|
||
old.clear();
|
||
this.cacheDepth = depth;
|
||
}
|
||
|
||
/**
|
||
* 执行分块
|
||
*/
|
||
split() {
|
||
this.blockData = {
|
||
width: Math.ceil(this.width / this.blockSize),
|
||
height: Math.ceil(this.height / this.blockSize),
|
||
restWidth: this.width % this.blockSize,
|
||
restHeight: this.height % this.blockSize
|
||
};
|
||
this.emit('split');
|
||
}
|
||
|
||
/**
|
||
* 清除指定块的索引(分块->void)
|
||
* @param index 要清除的缓存索引
|
||
* @param deep 清除哪些深度的缓存,至多31位二进制数,例如填0b111就是清除前三层的索引
|
||
*/
|
||
clearCache(index: number, deep: number) {
|
||
const depth = this.cacheDepth;
|
||
for (let i = 0; i < depth; i++) {
|
||
if (deep & (1 << i)) {
|
||
const nowIndex = index * this.cacheDepth + i;
|
||
const item = this.cache.get(nowIndex);
|
||
item?.destroy();
|
||
this.cache.delete(nowIndex);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 清空指定索引的缓存,与 {@link clearCache} 不同的是,这里会直接清空对应索引的缓存,而不是指定分块的缓存(元素->void)
|
||
*/
|
||
clearCacheByIndex(index: number) {
|
||
const item = this.cache.get(index);
|
||
item?.destroy();
|
||
this.cache.delete(index);
|
||
}
|
||
|
||
/**
|
||
* 清空所有缓存
|
||
*/
|
||
clearAllCache() {
|
||
this.cache.forEach(v => v.destroy());
|
||
this.cache.clear();
|
||
}
|
||
|
||
/**
|
||
* 根据分块的横纵坐标获取其索引(分块->分块)
|
||
*/
|
||
getIndex(x: number, y: number) {
|
||
return x + y * this.blockData.width;
|
||
}
|
||
|
||
/**
|
||
* 根据元素位置获取分块索引(注意是单个元素的位置,而不是分块的位置)(元素->分块)
|
||
*/
|
||
getIndexByLoc(x: number, y: number) {
|
||
return this.getIndex(
|
||
Math.floor(x / this.blockSize),
|
||
Math.floor(y / this.blockSize)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 根据块的索引获取其位置(分块->分块)
|
||
*/
|
||
getBlockXYByIndex(index: number): LocArr {
|
||
const width = this.blockData.width;
|
||
return [index % width, Math.floor(index / width)];
|
||
}
|
||
|
||
/**
|
||
* 获取一个元素位置所在的分块位置(即使它不在任何分块内)(元素->分块)
|
||
*/
|
||
getBlockXY(x: number, y: number): LocArr {
|
||
return [Math.floor(x / this.blockSize), Math.floor(y / this.blockSize)];
|
||
}
|
||
|
||
/**
|
||
* 根据分块坐标与deep获取一个分块的精确索引(分块->分块)
|
||
*/
|
||
getPreciseIndex(x: number, y: number, deep: number) {
|
||
return (x + y * this.blockSize) * this.cacheDepth + deep;
|
||
}
|
||
|
||
/**
|
||
* 根据元素坐标及deep获取元素所在块的精确索引(元素->分块)
|
||
*/
|
||
getPreciseIndexByLoc(x: number, y: number, deep: number) {
|
||
return this.getPreciseIndex(...this.getBlockXY(x, y), deep);
|
||
}
|
||
|
||
/**
|
||
* 更新指定元素区域内的缓存(注意坐标是元素坐标,而非分块坐标)(元素->分块)
|
||
* @param deep 缓存清除深度,默认全部清空
|
||
* @returns 更新区域内的所有有关分块索引
|
||
*/
|
||
updateElementArea(
|
||
x: number,
|
||
y: number,
|
||
width: number,
|
||
height: number,
|
||
deep: number = 2 ** 31 - 1
|
||
) {
|
||
const [bx, by] = this.getBlockXY(x, y);
|
||
const [ex, ey] = this.getBlockXY(x + width - 1, y + height - 1);
|
||
|
||
return this.updateArea(bx, by, ex - bx, ey - by, deep);
|
||
}
|
||
|
||
/**
|
||
* 更新指定分块区域内的缓存(注意坐标是分块坐标,而非元素坐标)(分块->分块)
|
||
* @param deep 缓存清除深度,默认全部清空
|
||
* @returns 更新区域内的所有分块索引
|
||
*/
|
||
updateArea(
|
||
x: number,
|
||
y: number,
|
||
width: number,
|
||
height: number,
|
||
deep: number = 2 ** 31 - 1
|
||
) {
|
||
const blocks = this.getIndexOf(x, y, width, height);
|
||
|
||
blocks.forEach(v => {
|
||
this.clearCache(v, deep);
|
||
});
|
||
return blocks;
|
||
}
|
||
|
||
/**
|
||
* 传入分块坐标与范围,获取该区域内包含的所有分块索引(分块->分块)
|
||
*/
|
||
getIndexOf(x: number, y: number, width: number, height: number) {
|
||
const res = new Set<number>();
|
||
const sx = Math.max(x, 0);
|
||
const sy = Math.max(y, 0);
|
||
const ex = Math.min(x + width, this.blockData.width);
|
||
const ey = Math.min(y + height, this.blockData.height);
|
||
|
||
for (let nx = sx; nx <= ex; nx++) {
|
||
for (let ny = sy; ny <= ey; ny++) {
|
||
const index = this.getIndex(nx, ny);
|
||
res.add(index);
|
||
}
|
||
}
|
||
|
||
return res;
|
||
}
|
||
|
||
/**
|
||
* 传入元素坐标与范围,获取该区域内包含的所有分块索引(元素->分块)
|
||
*/
|
||
getIndexOfElement(x: number, y: number, width: number, height: number) {
|
||
const [bx, by] = this.getBlockXY(x, y);
|
||
const [ex, ey] = this.getBlockXY(x + width, y + height);
|
||
return this.getIndexOf(bx, by, ex - bx, ey - by);
|
||
}
|
||
|
||
/**
|
||
* 根据分块索引,获取这个分块所在区域的元素矩形范围(左上角横纵坐标及右下角横纵坐标)(分块->元素)
|
||
* @param block 分块索引
|
||
*/
|
||
getRectOfIndex(block: number) {
|
||
const [x, y] = this.getBlockXYByIndex(block);
|
||
return this.getRectOfBlockXY(x, y);
|
||
}
|
||
|
||
/**
|
||
* 根据分块坐标,获取这个分块所在区域的元素矩形范围(左上角横纵坐标及右下角横纵坐标)(分块->元素)
|
||
* @param x 分块横坐标
|
||
* @param y 分块纵坐标
|
||
*/
|
||
getRectOfBlockXY(x: number, y: number) {
|
||
return [
|
||
x * this.blockSize,
|
||
y * this.blockSize,
|
||
(x + 1) * this.blockSize,
|
||
(y + 1) * this.blockSize
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 摧毁这个块缓存
|
||
*/
|
||
destroy() {
|
||
this.clearAllCache();
|
||
}
|
||
}
|
||
|
||
export interface ICanvasCacheItem extends IBlockCacheable {
|
||
readonly canvas: MotaOffscreenCanvas2D;
|
||
symbol: number;
|
||
}
|
||
|
||
export class CanvasCacheItem implements ICanvasCacheItem {
|
||
constructor(public canvas: MotaOffscreenCanvas2D, public symbol: number) {}
|
||
|
||
destroy(): void {
|
||
this.canvas.delete();
|
||
}
|
||
}
|