HumanBreak/packages/render-elements/src/block.ts

312 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
}
}