Merge pull request #72 from 0MirageTank0/master
优化掩码数据结构,新增BitMaskHashMap类型,支持无限数量原型
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import {Entity} from '../Entity';
|
||||
import {ComponentType} from './ComponentStorage';
|
||||
import {BitMask64Data, BitMask64Utils, ComponentTypeManager} from "../Utils";
|
||||
import { Entity } from '../Entity';
|
||||
import { ComponentType } from './ComponentStorage';
|
||||
import { BitMask64Data, BitMask64Utils, ComponentTypeManager } from "../Utils";
|
||||
import { BitMaskHashMap } from "../Utils/BitMaskHashMap";
|
||||
|
||||
/**
|
||||
* 原型标识符
|
||||
@@ -36,7 +37,7 @@ export interface ArchetypeQueryResult {
|
||||
*/
|
||||
export class ArchetypeSystem {
|
||||
/** 所有原型的映射表 */
|
||||
private _archetypes = new Map<number, Map<number, Archetype>>();
|
||||
private _archetypes = new BitMaskHashMap<Archetype>();
|
||||
|
||||
/** 实体到原型的映射 */
|
||||
private _entityToArchetype = new Map<Entity, Archetype>();
|
||||
@@ -57,7 +58,7 @@ export class ArchetypeSystem {
|
||||
const componentTypes = this.getEntityComponentTypes(entity);
|
||||
const archetypeId = this.generateArchetypeId(componentTypes);
|
||||
|
||||
let archetype = this.getArchetype(archetypeId);
|
||||
let archetype = this._archetypes.get(archetypeId);
|
||||
if (!archetype) {
|
||||
archetype = this.createArchetype(componentTypes);
|
||||
}
|
||||
@@ -109,7 +110,7 @@ export class ArchetypeSystem {
|
||||
}
|
||||
|
||||
// 获取或创建新原型
|
||||
let newArchetype = this.getArchetype(newArchetypeId);
|
||||
let newArchetype = this._archetypes.get(newArchetypeId);
|
||||
if (!newArchetype) {
|
||||
newArchetype = this.createArchetype(newComponentTypes);
|
||||
}
|
||||
@@ -215,25 +216,14 @@ export class ArchetypeSystem {
|
||||
this._entityComponentTypesCache.clear();
|
||||
this._allArchetypes = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据原型ID获取原型
|
||||
* @param archetypeId
|
||||
* @private
|
||||
*/
|
||||
private getArchetype(archetypeId: ArchetypeId): Archetype | undefined {
|
||||
return this._archetypes.get(archetypeId.hi)?.get(archetypeId.lo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新所有原型数组
|
||||
*/
|
||||
private updateAllArchetypeArrays(): void {
|
||||
this._allArchetypes = [];
|
||||
for (const [, innerMap] of this._archetypes) {
|
||||
for (const [, archetype] of innerMap) {
|
||||
this._allArchetypes.push(archetype);
|
||||
}
|
||||
for (let archetype of this._archetypes.values()) {
|
||||
this._allArchetypes.push(archetype);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,12 +259,7 @@ export class ArchetypeSystem {
|
||||
entities: new Set<Entity>()
|
||||
};
|
||||
// 存储原型ID - 原型
|
||||
let archetypeGroup = this._archetypes.get(id.hi);
|
||||
if (!archetypeGroup) {
|
||||
archetypeGroup = new Map<number, Archetype>();
|
||||
this._archetypes.set(id.hi, archetypeGroup);
|
||||
}
|
||||
archetypeGroup.set(id.lo, archetype);
|
||||
this._archetypes.set(id,archetype);
|
||||
// 更新数组
|
||||
this.updateAllArchetypeArrays();
|
||||
return archetype;
|
||||
|
||||
@@ -54,10 +54,7 @@ export class ComponentRegistry {
|
||||
const typeName = getComponentTypeName(componentType);
|
||||
throw new Error(`Component type ${typeName} is not registered`);
|
||||
}
|
||||
|
||||
const mask: BitMask64Data = { lo: 0, hi: 0 };
|
||||
BitMask64Utils.setBitExtended(mask, bitIndex);
|
||||
return mask;
|
||||
return BitMask64Utils.create(bitIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,9 +4,8 @@ import { ComponentRegistry, ComponentType } from './ComponentStorage';
|
||||
import { BitMask64Utils, BitMask64Data } from '../Utils/BigIntCompatibility';
|
||||
import { createLogger } from '../../Utils/Logger';
|
||||
import { getComponentTypeName } from '../Decorators';
|
||||
|
||||
import { ComponentPoolManager } from './ComponentPool';
|
||||
import { ArchetypeSystem, Archetype, ArchetypeQueryResult } from './ArchetypeSystem';
|
||||
import { Archetype, ArchetypeSystem } from './ArchetypeSystem';
|
||||
import { ComponentTypeManager } from "../Utils";
|
||||
|
||||
/**
|
||||
* 查询条件类型
|
||||
@@ -787,8 +786,7 @@ export class QuerySystem {
|
||||
private createComponentMask(componentTypes: ComponentType[]): BitMask64Data {
|
||||
// 生成缓存键
|
||||
const cacheKey = componentTypes.map(t => {
|
||||
const name = getComponentTypeName(t);
|
||||
return name;
|
||||
return getComponentTypeName(t);
|
||||
}).sort().join(',');
|
||||
|
||||
// 检查缓存
|
||||
@@ -797,27 +795,10 @@ export class QuerySystem {
|
||||
return cached;
|
||||
}
|
||||
|
||||
let mask = BitMask64Utils.clone(BitMask64Utils.ZERO);
|
||||
let hasValidComponents = false;
|
||||
|
||||
for (const type of componentTypes) {
|
||||
try {
|
||||
const bitMask = ComponentRegistry.getBitMask(type);
|
||||
BitMask64Utils.orInPlace(mask, bitMask);
|
||||
hasValidComponents = true;
|
||||
} catch (error) {
|
||||
this._logger.warn(`组件类型 ${getComponentTypeName(type)} 未注册,跳过`);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有有效的组件类型,返回一个不可能匹配的掩码
|
||||
if (!hasValidComponents) {
|
||||
mask = { lo: 0xFFFFFFFF, hi: 0xFFFFFFFF };
|
||||
}
|
||||
|
||||
let mask = ComponentTypeManager.instance.getEntityBits(componentTypes);
|
||||
// 缓存结果
|
||||
this.componentMaskCache.set(cacheKey, mask);
|
||||
return mask;
|
||||
this.componentMaskCache.set(cacheKey, mask.getValue());
|
||||
return mask.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -191,7 +191,7 @@ export class Entity {
|
||||
const maxBitIndex = ComponentRegistry.getRegisteredCount();
|
||||
|
||||
for (let bitIndex = 0; bitIndex < maxBitIndex; bitIndex++) {
|
||||
if (BitMask64Utils.getBitExtended(mask, bitIndex)) {
|
||||
if (BitMask64Utils.getBit(mask, bitIndex)) {
|
||||
const componentType = ComponentRegistry.getTypeByBitIndex(bitIndex);
|
||||
if (componentType) {
|
||||
let component: Component | null = null;
|
||||
@@ -504,7 +504,7 @@ export class Entity {
|
||||
this._localComponents.delete(componentType);
|
||||
|
||||
// 更新位掩码
|
||||
BitMask64Utils.clearBitExtended(this._componentMask, bitIndex);
|
||||
BitMask64Utils.clearBit(this._componentMask, bitIndex);
|
||||
|
||||
// 使缓存失效
|
||||
this._componentCache = null;
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
/**
|
||||
* 位枚举
|
||||
*/
|
||||
export enum SegmentPart {
|
||||
LOW = 0,
|
||||
HIGH = 1,
|
||||
}
|
||||
/**
|
||||
* 基础 64 位段结构
|
||||
* [低32位,高32位]
|
||||
*/
|
||||
export interface BitMask64Segment {
|
||||
/** 低32位(bit 0-31) */
|
||||
lo: number;
|
||||
/** 高32位(bit 32-63) */
|
||||
hi: number;
|
||||
}
|
||||
export type BitMask64Segment = [number,number]
|
||||
|
||||
/**
|
||||
* 位掩码数据结构
|
||||
* 基础模式(64位):使用 lo + hi 存储 64 位,segments 为空
|
||||
* 扩展模式(128+位):lo + hi 作为第一段,segments 存储额外的 64 位段
|
||||
* 基础模式(64位):使用 base[lo , hi] 存储 64 位,segments 为空
|
||||
* 扩展模式(128+位):base[lo , hi] 作为第一段,segments 存储额外的 64 位段
|
||||
* segments[0] 对应 bit 64-127,segments[1] 对应 bit 128-191,以此类推
|
||||
*/
|
||||
export interface BitMask64Data {
|
||||
/** 低32位(bit 0-31) */
|
||||
lo: number;
|
||||
/** 高32位(bit 32-63) */
|
||||
hi: number;
|
||||
base: BitMask64Segment;
|
||||
/** 扩展段数组,每个元素是一个 64 位段,用于超过 64 位的场景 */
|
||||
segments?: BitMask64Segment[];
|
||||
}
|
||||
|
||||
export class BitMask64Utils {
|
||||
/** 零掩码常量,所有位都为0 */
|
||||
public static readonly ZERO: BitMask64Data = { lo: 0, hi: 0 };
|
||||
public static readonly ZERO: Readonly<BitMask64Data> = { base: [0, 0], segments: undefined };
|
||||
|
||||
/**
|
||||
* 根据位索引创建64位掩码
|
||||
@@ -34,15 +34,12 @@ export class BitMask64Utils {
|
||||
* @throws 当位索引超出范围时抛出错误
|
||||
*/
|
||||
public static create(bitIndex: number): BitMask64Data {
|
||||
if (bitIndex < 0 || bitIndex >= 64) {
|
||||
throw new Error(`Bit index ${bitIndex} out of range [0, 63]`);
|
||||
}
|
||||
|
||||
if (bitIndex < 32) {
|
||||
return { lo: 1 << bitIndex, hi: 0 };
|
||||
} else {
|
||||
return { lo: 0, hi: 1 << (bitIndex - 32) };
|
||||
if (bitIndex < 0) {
|
||||
throw new Error(`Bit index ${bitIndex} out of range [0, ∞)`);
|
||||
}
|
||||
const mask: BitMask64Data = { base: [0, 0], segments: undefined };
|
||||
BitMask64Utils.setBit(mask, bitIndex);
|
||||
return mask;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,7 +48,7 @@ export class BitMask64Utils {
|
||||
* @returns 低32位为输入值、高32位为0的掩码
|
||||
*/
|
||||
public static fromNumber(value: number): BitMask64Data {
|
||||
return { lo: value >>> 0, hi: 0 };
|
||||
return { base: [value >>> 0, 0], segments: undefined};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,75 +58,50 @@ export class BitMask64Utils {
|
||||
* @returns 如果掩码包含bits中的任意位则返回true
|
||||
*/
|
||||
public static hasAny(mask: BitMask64Data, bits: BitMask64Data): boolean {
|
||||
// 检查第一个 64 位段
|
||||
if ((mask.lo & bits.lo) !== 0 || (mask.hi & bits.hi) !== 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果 bits 没有扩展段,检查完成
|
||||
if (!bits.segments || bits.segments.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果 bits 有扩展段但 mask 没有,返回 false
|
||||
if (!mask.segments || mask.segments.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查每个扩展段
|
||||
const minSegments = Math.min(mask.segments.length, bits.segments.length);
|
||||
for (let i = 0; i < minSegments; i++) {
|
||||
const maskSeg = mask.segments[i];
|
||||
const bitsSeg = bits.segments[i];
|
||||
if ((maskSeg.lo & bitsSeg.lo) !== 0 || (maskSeg.hi & bitsSeg.hi) !== 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
const bitsBase = bits.base;
|
||||
const maskBase = mask.base;
|
||||
const bitsSegments = bits.segments;
|
||||
const maskSegments = mask.segments;
|
||||
const baseHasAny = (maskBase[SegmentPart.LOW] & bitsBase[SegmentPart.LOW]) !== 0 || (maskBase[SegmentPart.HIGH] & bitsBase[SegmentPart.HIGH]) !== 0;
|
||||
// 基础区段就包含指定的位,或任意一个参数不含扩展区段,直接短路
|
||||
if(baseHasAny || !bitsSegments || !maskSegments) return baseHasAny;
|
||||
// 额外检查扩展区域是否包含指定的位 - 如果bitsSegments[index]不存在,会被转为NaN,NaN的位运算始终返回0
|
||||
return maskSegments.some((seg, index) => (seg[SegmentPart.LOW] & bitsSegments[index][SegmentPart.LOW]) !== 0 || (seg[SegmentPart.HIGH] & bitsSegments[index][SegmentPart.HIGH]) !== 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查掩码是否包含所有指定的位
|
||||
* 支持扩展模式,自动处理超过 64 位的掩码
|
||||
* @param mask 要检查的掩码
|
||||
* @param bits 指定的位模式
|
||||
* @returns 如果掩码包含bits中的所有位则返回true
|
||||
*/
|
||||
public static hasAll(mask: BitMask64Data, bits: BitMask64Data): boolean {
|
||||
// 检查第一个 64 位段
|
||||
if ((mask.lo & bits.lo) !== bits.lo || (mask.hi & bits.hi) !== bits.hi) {
|
||||
return false;
|
||||
}
|
||||
const maskBase = mask.base;
|
||||
const bitsBase = bits.base;
|
||||
const maskSegments = mask.segments;
|
||||
const bitsSegments = bits.segments;
|
||||
const baseHasAll = (maskBase[SegmentPart.LOW] & bitsBase[SegmentPart.LOW]) === bitsBase[SegmentPart.LOW] && (maskBase[SegmentPart.HIGH] & bitsBase[SegmentPart.HIGH]) === bitsBase[SegmentPart.HIGH];
|
||||
// 基础区域就不包含指定的位,或bits没有扩展区段,直接短路。
|
||||
if(!baseHasAll || !bitsSegments) return baseHasAll;
|
||||
|
||||
// 如果 bits 没有扩展段,检查完成
|
||||
if (!bits.segments || bits.segments.length === 0) {
|
||||
return true;
|
||||
// 扩展区段的hasAll匹配逻辑
|
||||
const maskSegmentsLength = maskSegments?.length ?? 0;
|
||||
// 对mask/bits中都存在的区段,进行hasAll判断
|
||||
if(maskSegments){
|
||||
for (let i = 0; i < Math.min(maskSegmentsLength,bitsSegments.length); i++) {
|
||||
if((maskSegments[i][SegmentPart.LOW] & bitsSegments[i][SegmentPart.LOW]) !== bitsSegments[i][SegmentPart.LOW] || (maskSegments[i][SegmentPart.HIGH] & bitsSegments[i][SegmentPart.HIGH]) !== bitsSegments[i][SegmentPart.HIGH]){
|
||||
// 存在不匹配的位,直接短路
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果 bits 有扩展段但 mask 没有,返回 false
|
||||
if (!mask.segments || mask.segments.length === 0) {
|
||||
// 检查 bits 的扩展段是否全为 0
|
||||
return bits.segments.every(seg => BitMask64Utils.isZero(seg));
|
||||
}
|
||||
|
||||
// 检查每个扩展段
|
||||
const minSegments = Math.min(mask.segments.length, bits.segments.length);
|
||||
for (let i = 0; i < minSegments; i++) {
|
||||
const maskSeg = mask.segments[i];
|
||||
const bitsSeg = bits.segments[i];
|
||||
if ((maskSeg.lo & bitsSeg.lo) !== bitsSeg.lo || (maskSeg.hi & bitsSeg.hi) !== bitsSeg.hi) {
|
||||
// 对mask中不存在,但bits中存在的区段,进行isZero判断
|
||||
for (let i = maskSegmentsLength; i < bitsSegments.length; i++) {
|
||||
if(bitsSegments[i][SegmentPart.LOW] !== 0 || bitsSegments[i][SegmentPart.HIGH] !== 0){
|
||||
// 存在不为0的区段,直接短路
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果 bits 有更多段,检查这些段是否为空
|
||||
for (let i = minSegments; i < bits.segments.length; i++) {
|
||||
if (!BitMask64Utils.isZero(bits.segments[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -140,7 +112,15 @@ export class BitMask64Utils {
|
||||
* @returns 如果掩码不包含bits中的任何位则返回true
|
||||
*/
|
||||
public static hasNone(mask: BitMask64Data, bits: BitMask64Data): boolean {
|
||||
return (mask.lo & bits.lo) === 0 && (mask.hi & bits.hi) === 0;
|
||||
const maskBase = mask.base;
|
||||
const bitsBase = bits.base;
|
||||
const maskSegments = mask.segments;
|
||||
const bitsSegments = bits.segments;
|
||||
const baseHasNone = (maskBase[SegmentPart.LOW] & bitsBase[SegmentPart.LOW]) === 0 && (maskBase[SegmentPart.HIGH] & bitsBase[SegmentPart.HIGH]) === 0;
|
||||
//不含扩展区域,或基础区域就包含指定的位,或bits不含拓展区段,直接短路。
|
||||
if(!maskSegments || !baseHasNone || !bitsSegments) return baseHasNone;
|
||||
// 额外检查扩展区域是否都包含指定的位 - 此时bitsSegments存在,如果bitsSegments[index]不存在,会被转为NaN,NaN的位运算始终返回0
|
||||
return maskSegments.every((seg, index) => (seg[SegmentPart.LOW] & bitsSegments[index][SegmentPart.LOW]) === 0 && (seg[SegmentPart.HIGH] & bitsSegments[index][SegmentPart.HIGH]) === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,8 +128,14 @@ export class BitMask64Utils {
|
||||
* @param mask 要检查的掩码
|
||||
* @returns 如果掩码所有位都为0则返回true
|
||||
*/
|
||||
public static isZero(mask: BitMask64Data | BitMask64Segment): boolean {
|
||||
return mask.lo === 0 && mask.hi === 0;
|
||||
public static isZero(mask: BitMask64Data): boolean {
|
||||
const baseIsZero = mask.base[SegmentPart.LOW] === 0 && mask.base[SegmentPart.HIGH] === 0;
|
||||
if(!mask.segments || !baseIsZero){
|
||||
// 不含扩展区域,或基础区域值就不为0,直接短路
|
||||
return baseIsZero;
|
||||
}
|
||||
// 额外检查扩展区域是否都为0
|
||||
return mask.segments.every(seg => seg[SegmentPart.LOW] === 0 && seg[SegmentPart.HIGH] === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,42 +145,82 @@ export class BitMask64Utils {
|
||||
* @returns 如果两个掩码完全相等则返回true
|
||||
*/
|
||||
public static equals(a: BitMask64Data, b: BitMask64Data): boolean {
|
||||
return a.lo === b.lo && a.hi === b.hi;
|
||||
let baseEquals = a.base[SegmentPart.LOW] === b.base[SegmentPart.LOW] && a.base[SegmentPart.HIGH] === b.base[SegmentPart.HIGH];
|
||||
// base不相等,或ab都没有扩展区域位,直接返回base比较结果
|
||||
if(!baseEquals || (!a.segments && !b.segments)) return baseEquals;
|
||||
// 不能假设a,b的segments都存在或长度相同.
|
||||
const aSegments = a.segments ?? [];
|
||||
const bSegments = b.segments ?? [];
|
||||
for (let i = 0; i < Math.max(aSegments.length, bSegments.length); i++) {
|
||||
const aSeg: BitMask64Segment | undefined = aSegments[i]; // 可能为undefined
|
||||
const bSeg: BitMask64Segment | undefined = bSegments[i]; // 可能为undefined
|
||||
if(aSeg && !bSeg){
|
||||
//bSeg不存在,则必须要求aSeg全为0
|
||||
if(aSeg[SegmentPart.LOW] !== 0 || aSeg[SegmentPart.HIGH] !== 0) return false;
|
||||
}else if(!aSeg && bSeg){
|
||||
//aSeg不存在,则必须要求bSeg全为0
|
||||
if(bSeg[SegmentPart.LOW] !== 0 || bSeg[SegmentPart.HIGH] !== 0) return false;
|
||||
}else{
|
||||
//理想状态:aSeg/bSeg都存在
|
||||
if(aSeg[SegmentPart.LOW] !== bSeg[SegmentPart.LOW] || aSeg[SegmentPart.HIGH] !== bSeg[SegmentPart.HIGH]) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置掩码中指定位为1
|
||||
* 设置掩码中指定位为1,必要时自动扩展
|
||||
* @param mask 要修改的掩码(原地修改)
|
||||
* @param bitIndex 位索引,范围 [0, 63]
|
||||
* @param bitIndex 位索引,不小于零
|
||||
* @throws 当位索引超出范围时抛出错误
|
||||
*/
|
||||
public static setBit(mask: BitMask64Data, bitIndex: number): void {
|
||||
if (bitIndex < 0 || bitIndex >= 64) {
|
||||
if (bitIndex < 0) {
|
||||
throw new Error(`Bit index ${bitIndex} out of range [0, 63]`);
|
||||
}
|
||||
|
||||
if (bitIndex < 32) {
|
||||
mask.lo |= (1 << bitIndex);
|
||||
const targetSeg = BitMask64Utils.getSegmentByBitIndex(mask, bitIndex)!;
|
||||
const mod = bitIndex & 63; // bitIndex % 64 优化方案
|
||||
if(mod < 32){
|
||||
targetSeg[SegmentPart.LOW] |= (1 << mod);
|
||||
} else {
|
||||
mask.hi |= (1 << (bitIndex - 32));
|
||||
targetSeg[SegmentPart.HIGH] |= (1 << (mod - 32));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除掩码中指定位为0
|
||||
* 获取掩码中指定位,如果位超出当前掩码的区段长度,则直接返回0
|
||||
* @param mask 掩码
|
||||
* @param bitIndex 位索引,不小于零
|
||||
*/
|
||||
public static getBit(mask: BitMask64Data, bitIndex: number): boolean {
|
||||
if (bitIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
const targetSeg = BitMask64Utils.getSegmentByBitIndex(mask, bitIndex, false);
|
||||
if(!targetSeg) return false;
|
||||
const mod = bitIndex & 63; // bitIndex % 64 优化方案
|
||||
if(mod < 32){
|
||||
return (targetSeg[SegmentPart.LOW] & (1 << mod)) !== 0;
|
||||
} else {
|
||||
return (targetSeg[SegmentPart.HIGH] & (1 << (mod - 32))) !== 0;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 清除掩码中指定位为0,如果位超出当前掩码的区段长度,则什么也不做
|
||||
* @param mask 要修改的掩码(原地修改)
|
||||
* @param bitIndex 位索引,范围 [0, 63]
|
||||
* @throws 当位索引超出范围时抛出错误
|
||||
* @param bitIndex 位索引,不小于0
|
||||
*/
|
||||
public static clearBit(mask: BitMask64Data, bitIndex: number): void {
|
||||
if (bitIndex < 0 || bitIndex >= 64) {
|
||||
if (bitIndex < 0) {
|
||||
throw new Error(`Bit index ${bitIndex} out of range [0, 63]`);
|
||||
}
|
||||
|
||||
if (bitIndex < 32) {
|
||||
mask.lo &= ~(1 << bitIndex);
|
||||
const targetSeg = BitMask64Utils.getSegmentByBitIndex(mask, bitIndex, false);
|
||||
if(!targetSeg) return;
|
||||
const mod = bitIndex & 63; // bitIndex % 64 优化方案
|
||||
if(mod < 32){
|
||||
targetSeg[SegmentPart.LOW] &= ~(1 << mod);
|
||||
} else {
|
||||
mask.hi &= ~(1 << (bitIndex - 32));
|
||||
targetSeg[SegmentPart.HIGH] &= ~(1 << (mod - 32));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,24 +230,26 @@ export class BitMask64Utils {
|
||||
* @param other 用于按位或的掩码
|
||||
*/
|
||||
public static orInPlace(target: BitMask64Data, other: BitMask64Data): void {
|
||||
target.lo |= other.lo;
|
||||
target.hi |= other.hi;
|
||||
target.base[SegmentPart.LOW] |= other.base[SegmentPart.LOW];
|
||||
target.base[SegmentPart.HIGH] |= other.base[SegmentPart.HIGH];
|
||||
|
||||
// 处理扩展段
|
||||
if (other.segments && other.segments.length > 0) {
|
||||
const otherSegments = other.segments;
|
||||
if (otherSegments && otherSegments.length > 0) {
|
||||
if (!target.segments) {
|
||||
target.segments = [];
|
||||
}
|
||||
const targetSegments = target.segments;
|
||||
|
||||
// 确保 target 有足够的段
|
||||
while (target.segments.length < other.segments.length) {
|
||||
target.segments.push({ lo: 0, hi: 0 });
|
||||
while (targetSegments.length < otherSegments.length) {
|
||||
targetSegments.push([0, 0]);
|
||||
}
|
||||
|
||||
// 对每个段执行或操作
|
||||
for (let i = 0; i < other.segments.length; i++) {
|
||||
target.segments[i].lo |= other.segments[i].lo;
|
||||
target.segments[i].hi |= other.segments[i].hi;
|
||||
for (let i = 0; i < otherSegments.length; i++) {
|
||||
targetSegments[i][SegmentPart.LOW] |= otherSegments[i][SegmentPart.LOW];
|
||||
targetSegments[i][SegmentPart.HIGH] |= otherSegments[i][SegmentPart.HIGH];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,8 +260,27 @@ export class BitMask64Utils {
|
||||
* @param other 用于按位与的掩码
|
||||
*/
|
||||
public static andInPlace(target: BitMask64Data, other: BitMask64Data): void {
|
||||
target.lo &= other.lo;
|
||||
target.hi &= other.hi;
|
||||
target.base[SegmentPart.LOW] &= other.base[SegmentPart.LOW];
|
||||
target.base[SegmentPart.HIGH] &= other.base[SegmentPart.HIGH];
|
||||
|
||||
// 处理扩展段
|
||||
const otherSegments = other.segments;
|
||||
if (otherSegments && otherSegments.length > 0) {
|
||||
if (!target.segments) {
|
||||
target.segments = [];
|
||||
}
|
||||
const targetSegments = target.segments;
|
||||
// 确保 target 有足够的段
|
||||
while (targetSegments.length < otherSegments.length) {
|
||||
targetSegments.push([0, 0]);
|
||||
}
|
||||
|
||||
// 对每个段执行与操作
|
||||
for (let i = 0; i < otherSegments.length; i++) {
|
||||
targetSegments[i][SegmentPart.LOW] &= otherSegments[i][SegmentPart.LOW];
|
||||
targetSegments[i][SegmentPart.HIGH] &= otherSegments[i][SegmentPart.HIGH];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,8 +289,25 @@ export class BitMask64Utils {
|
||||
* @param other 用于按位异或的掩码
|
||||
*/
|
||||
public static xorInPlace(target: BitMask64Data, other: BitMask64Data): void {
|
||||
target.lo ^= other.lo;
|
||||
target.hi ^= other.hi;
|
||||
target.base[SegmentPart.LOW] ^= other.base[SegmentPart.LOW];
|
||||
target.base[SegmentPart.HIGH] ^= other.base[SegmentPart.HIGH];
|
||||
|
||||
// 处理扩展段
|
||||
const otherSegments = other.segments;
|
||||
if (!otherSegments || otherSegments.length == 0) return;
|
||||
if (!target.segments) target.segments = [];
|
||||
|
||||
const targetSegments = target.segments;
|
||||
// 确保 target 有足够的段
|
||||
while (targetSegments.length < otherSegments.length) {
|
||||
targetSegments.push([0, 0]);
|
||||
}
|
||||
|
||||
// 对每个段执行异或操作
|
||||
for (let i = 0; i < otherSegments.length; i++) {
|
||||
targetSegments[i][SegmentPart.LOW] ^= otherSegments[i][SegmentPart.LOW];
|
||||
targetSegments[i][SegmentPart.HIGH] ^= otherSegments[i][SegmentPart.HIGH];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,18 +315,43 @@ export class BitMask64Utils {
|
||||
* @param mask 要清除的掩码(原地修改)
|
||||
*/
|
||||
public static clear(mask: BitMask64Data): void {
|
||||
mask.lo = 0;
|
||||
mask.hi = 0;
|
||||
mask.base[SegmentPart.LOW] = 0;
|
||||
mask.base[SegmentPart.HIGH] = 0;
|
||||
for (let i = 0; i < (mask.segments?.length ?? 0); i++) {
|
||||
const seg = mask.segments![i];
|
||||
seg[SegmentPart.LOW] = 0;
|
||||
seg[SegmentPart.HIGH] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将源掩码的值复制到目标掩码
|
||||
* 将源掩码的值复制到目标掩码,如果source包含扩展段,则target也会至少扩展到source扩展段的长度
|
||||
* @param source 源掩码
|
||||
* @param target 目标掩码(原地修改)
|
||||
*/
|
||||
public static copy(source: BitMask64Data, target: BitMask64Data): void {
|
||||
target.lo = source.lo;
|
||||
target.hi = source.hi;
|
||||
BitMask64Utils.clear(target);
|
||||
target.base[SegmentPart.LOW] = source.base[SegmentPart.LOW];
|
||||
target.base[SegmentPart.HIGH] = source.base[SegmentPart.HIGH];
|
||||
// source没有扩展段,直接退出
|
||||
if(!source.segments || source.segments.length == 0) return;
|
||||
// 没有拓展段,则直接复制数组
|
||||
if(!target.segments){
|
||||
target.segments = source.segments.map(seg => [...seg]);
|
||||
return;
|
||||
}
|
||||
// source有扩展段,target扩展段不足,则补充长度
|
||||
const copyLength = source.segments.length - target.segments.length;
|
||||
for (let i = 0; i < copyLength; i++) {
|
||||
target.segments.push([0,0]);
|
||||
}
|
||||
// 逐个重写
|
||||
for (let i = 0; i < length; i++) {
|
||||
const targetSeg = target.segments![i];
|
||||
const sourSeg = source.segments![i];
|
||||
targetSeg[SegmentPart.LOW] = sourSeg[SegmentPart.LOW];
|
||||
targetSeg[SegmentPart.HIGH] = sourSeg[SegmentPart.HIGH];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,36 +360,65 @@ export class BitMask64Utils {
|
||||
* @returns 新的掩码对象,内容与源掩码相同
|
||||
*/
|
||||
public static clone(mask: BitMask64Data): BitMask64Data {
|
||||
return { lo: mask.lo, hi: mask.hi };
|
||||
return {
|
||||
base: mask.base.slice() as BitMask64Segment,
|
||||
segments: mask.segments ? mask.segments.map(seg => [...seg]) : undefined
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将掩码转换为字符串表示
|
||||
* 将掩码转换为字符串表示,每个区段之间将使用空格分割。
|
||||
* @param mask 要转换的掩码
|
||||
* @param radix 进制,支持2(二进制)或16(十六进制),默认为2
|
||||
* @param radix 进制,支持2(二进制)或16(十六进制),默认为2,其他的值被视为2
|
||||
* @param printHead 打印头
|
||||
* @returns 掩码的字符串表示,二进制不带前缀,十六进制带0x前缀
|
||||
* @throws 当进制不支持时抛出错误
|
||||
*/
|
||||
public static toString(mask: BitMask64Data, radix: number = 2): string {
|
||||
if (radix === 2) {
|
||||
if (mask.hi === 0) {
|
||||
return mask.lo.toString(2);
|
||||
} else {
|
||||
const hiBits = mask.hi.toString(2);
|
||||
const loBits = mask.lo.toString(2).padStart(32, '0');
|
||||
return hiBits + loBits;
|
||||
public static toString(mask: BitMask64Data, radix: 2 | 16 = 2, printHead: boolean = false): string {
|
||||
if(radix != 2 && radix != 16) radix = 2;
|
||||
const totalLength = mask.segments?.length ?? 0;
|
||||
let result: string = '';
|
||||
if(printHead){
|
||||
let paddingLength = 0;
|
||||
if(radix === 2){
|
||||
paddingLength = 64 + 1 + 1;
|
||||
}else{
|
||||
paddingLength = 16 + 2 + 1;
|
||||
}
|
||||
} else if (radix === 16) {
|
||||
if (mask.hi === 0) {
|
||||
return '0x' + mask.lo.toString(16).toUpperCase();
|
||||
} else {
|
||||
const hiBits = mask.hi.toString(16).toUpperCase();
|
||||
const loBits = mask.lo.toString(16).toUpperCase().padStart(8, '0');
|
||||
return '0x' + hiBits + loBits;
|
||||
for (let i = 0; i <= totalLength; i++) {
|
||||
const title = i === 0 ? '0 (Base):' : `${i} (${64 * i}):`;
|
||||
result += title.toString().padEnd(paddingLength);
|
||||
}
|
||||
} else {
|
||||
throw new Error('Only radix 2 and 16 are supported');
|
||||
result += '\n';
|
||||
}
|
||||
|
||||
for (let i = -1; i < totalLength; i++) {
|
||||
let segResult = '';
|
||||
const bitMaskData = i == -1 ? mask.base : mask.segments![i];
|
||||
let hi = bitMaskData[SegmentPart.HIGH];
|
||||
let lo = bitMaskData[SegmentPart.LOW];
|
||||
if(radix == 2){
|
||||
const hiBits = hi.toString(2).padStart(32, '0');
|
||||
const loBits = lo.toString(2).padStart(32, '0');
|
||||
segResult = hiBits + '_' + loBits; //高低位之间使用_隔离
|
||||
}else{
|
||||
let hiBits = hi ? hi.toString(16).toUpperCase() : '';
|
||||
if(printHead){
|
||||
// 存在标头,则输出高位之前需要补齐位数
|
||||
hiBits = hiBits.padStart(8, '0');
|
||||
}
|
||||
let loBits = lo.toString(16).toUpperCase();
|
||||
if(hiBits){
|
||||
// 存在高位 则输出低位之前需要补齐位数
|
||||
loBits = loBits.padStart(8, '0');
|
||||
}
|
||||
segResult = '0x' + hiBits + loBits;
|
||||
}
|
||||
if(i === -1)
|
||||
result += segResult;
|
||||
else
|
||||
result += ' ' + segResult; // 不同段之间使用空格隔离
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -310,126 +428,49 @@ export class BitMask64Utils {
|
||||
*/
|
||||
public static popCount(mask: BitMask64Data): number {
|
||||
let count = 0;
|
||||
let lo = mask.lo;
|
||||
let hi = mask.hi;
|
||||
|
||||
while (lo) {
|
||||
lo &= lo - 1;
|
||||
count++;
|
||||
for (let i = -1; i < (mask.segments?.length ?? 0); i++) {
|
||||
const bitMaskData = i == -1 ? mask.base : mask.segments![i];
|
||||
let lo = bitMaskData[SegmentPart.LOW];
|
||||
let hi = bitMaskData[SegmentPart.HIGH];
|
||||
while (lo) {
|
||||
lo &= lo - 1;
|
||||
count++;
|
||||
}
|
||||
while (hi) {
|
||||
hi &= hi - 1;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
while (hi) {
|
||||
hi &= hi - 1;
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置扩展位(支持超过 64 位的索引)
|
||||
* @param mask 要修改的掩码(原地修改)
|
||||
* @param bitIndex 位索引(可以超过 63)
|
||||
* 获取包含目标位的BitMask64Segment
|
||||
* @param mask 要操作的掩码
|
||||
* @param bitIndex 目标位
|
||||
* @param createNewSegment 如果bitIndex超过了当前范围,是否自动补充扩展区域,默认为真
|
||||
* @private
|
||||
*/
|
||||
public static setBitExtended(mask: BitMask64Data, bitIndex: number): void {
|
||||
if (bitIndex < 0) {
|
||||
throw new Error('Bit index cannot be negative');
|
||||
}
|
||||
|
||||
if (bitIndex < 64) {
|
||||
BitMask64Utils.setBit(mask, bitIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算段索引和段内位索引
|
||||
const segmentIndex = Math.floor(bitIndex / 64) - 1;
|
||||
const localBitIndex = bitIndex % 64;
|
||||
|
||||
// 确保 segments 数组存在
|
||||
if (!mask.segments) {
|
||||
mask.segments = [];
|
||||
}
|
||||
|
||||
// 扩展 segments 数组
|
||||
while (mask.segments.length <= segmentIndex) {
|
||||
mask.segments.push({ lo: 0, hi: 0 });
|
||||
}
|
||||
|
||||
// 设置对应段的位
|
||||
const segment = mask.segments[segmentIndex];
|
||||
if (localBitIndex < 32) {
|
||||
segment.lo |= (1 << localBitIndex);
|
||||
} else {
|
||||
segment.hi |= (1 << (localBitIndex - 32));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取扩展位(支持超过 64 位的索引)
|
||||
* @param mask 要检查的掩码
|
||||
* @param bitIndex 位索引(可以超过 63)
|
||||
* @returns 如果位被设置则返回 true
|
||||
*/
|
||||
public static getBitExtended(mask: BitMask64Data, bitIndex: number): boolean {
|
||||
if (bitIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bitIndex < 64) {
|
||||
const testMask = BitMask64Utils.create(bitIndex);
|
||||
return BitMask64Utils.hasAny(mask, testMask);
|
||||
}
|
||||
|
||||
if (!mask.segments) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const segmentIndex = Math.floor(bitIndex / 64) - 1;
|
||||
if (segmentIndex >= mask.segments.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const localBitIndex = bitIndex % 64;
|
||||
const segment = mask.segments[segmentIndex];
|
||||
|
||||
if (localBitIndex < 32) {
|
||||
return (segment.lo & (1 << localBitIndex)) !== 0;
|
||||
} else {
|
||||
return (segment.hi & (1 << (localBitIndex - 32))) !== 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除扩展位(支持超过 64 位的索引)
|
||||
* @param mask 要修改的掩码(原地修改)
|
||||
* @param bitIndex 位索引(可以超过 63)
|
||||
*/
|
||||
public static clearBitExtended(mask: BitMask64Data, bitIndex: number): void {
|
||||
if (bitIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bitIndex < 64) {
|
||||
BitMask64Utils.clearBit(mask, bitIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mask.segments) {
|
||||
return;
|
||||
}
|
||||
|
||||
const segmentIndex = Math.floor(bitIndex / 64) - 1;
|
||||
if (segmentIndex >= mask.segments.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const localBitIndex = bitIndex % 64;
|
||||
const segment = mask.segments[segmentIndex];
|
||||
|
||||
if (localBitIndex < 32) {
|
||||
segment.lo &= ~(1 << localBitIndex);
|
||||
} else {
|
||||
segment.hi &= ~(1 << (localBitIndex - 32));
|
||||
private static getSegmentByBitIndex(mask: BitMask64Data,bitIndex: number, createNewSegment: boolean = true): BitMask64Segment | null{
|
||||
if(bitIndex <= 63){
|
||||
// 基础位
|
||||
return mask.base;
|
||||
}else{
|
||||
// 扩展位
|
||||
let segments = mask.segments;
|
||||
if(!segments) {
|
||||
if(!createNewSegment) return null;
|
||||
segments = mask.segments = [];
|
||||
}
|
||||
const targetSegIndex = (bitIndex >> 6) - 1; // Math.floor(bitIndex / 64) - 1的位运算优化
|
||||
if(segments.length <= targetSegIndex){
|
||||
if(!createNewSegment) return null;
|
||||
const diff = targetSegIndex - segments.length + 1;
|
||||
for (let i = 0; i < diff; i++) {
|
||||
segments.push([0, 0]);
|
||||
}
|
||||
}
|
||||
return segments[targetSegIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
143
packages/core/src/ECS/Utils/BitMaskHashMap.ts
Normal file
143
packages/core/src/ECS/Utils/BitMaskHashMap.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { BitMask64Data } from "./BigIntCompatibility";
|
||||
|
||||
// FlatHashMapFast.ts
|
||||
|
||||
/**
|
||||
* 高性能 HashMap,使用BitMask64Data作为Key。内部计算两层哈希:
|
||||
* - primaryHash: MurmurHash3(seed1) => 定位 bucket
|
||||
* - secondaryHash: MurmurHash3(seed2) => 处理 bucket 内碰撞判定
|
||||
*
|
||||
* 理论上,在1e5数量数据规模下碰撞概率在数学意义上的可忽略。
|
||||
* 在本地测试中,一千万次连续/随机BitMask64Data生成未发生一级哈希冲突,考虑到使用场景(原型系统、组件系统等)远达不到此数量级,因此可安全用于生产环境。
|
||||
*/
|
||||
export class BitMaskHashMap<T> {
|
||||
private buckets: Map<number, [number, T][]> = new Map();
|
||||
private _size = 0;
|
||||
|
||||
constructor() {}
|
||||
|
||||
get size(): number {
|
||||
return this._size;
|
||||
}
|
||||
|
||||
get innerBuckets(): Map<number, [number, T][]> {
|
||||
return this.buckets;
|
||||
}
|
||||
/** MurmurHash3 (32bit) 简化实现 */
|
||||
private murmur32(key: BitMask64Data, seed: number): number {
|
||||
let h = seed >>> 0;
|
||||
const mix = (k: number) => {
|
||||
k = Math.imul(k, 0xcc9e2d51) >>> 0; // 第一个 32 位魔术常数
|
||||
k = (k << 15) | (k >>> 17);
|
||||
k = Math.imul(k, 0x1b873593) >>> 0; // 第二个 32 位魔术常数
|
||||
h ^= k;
|
||||
h = (h << 13) | (h >>> 19);
|
||||
h = (Math.imul(h, 5) + 0xe6546b64) >>> 0;
|
||||
};
|
||||
|
||||
// base
|
||||
mix(key.base[0] >>> 0);
|
||||
mix(key.base[1] >>> 0);
|
||||
|
||||
// segments
|
||||
if (key.segments) {
|
||||
for (const seg of key.segments) {
|
||||
mix(seg[0] >>> 0);
|
||||
mix(seg[1] >>> 0);
|
||||
}
|
||||
}
|
||||
|
||||
h ^= (key.segments ? key.segments.length * 8 : 8);
|
||||
h ^= h >>> 16;
|
||||
h = Math.imul(h, 0x85ebca6b) >>> 0;
|
||||
h ^= h >>> 13;
|
||||
h = Math.imul(h, 0xc2b2ae35) >>> 0;
|
||||
h ^= h >>> 16;
|
||||
return h >>> 0;
|
||||
}
|
||||
|
||||
/** primaryHash + secondaryHash 计算 */
|
||||
private getHashes(key: BitMask64Data): [number, number] {
|
||||
const primary = this.murmur32(key, 0x9747b28c); // seed1
|
||||
const secondary = this.murmur32(key, 0x12345678); // seed2
|
||||
return [primary, secondary];
|
||||
}
|
||||
|
||||
set(key: BitMask64Data, value: T): this {
|
||||
const [primary, secondary] = this.getHashes(key);
|
||||
let bucket = this.buckets.get(primary);
|
||||
if (!bucket) {
|
||||
bucket = [];
|
||||
this.buckets.set(primary, bucket);
|
||||
}
|
||||
|
||||
// 查找是否存在 secondaryHash
|
||||
for (let i = 0; i < bucket.length; i++) {
|
||||
if (bucket[i][0] === secondary) {
|
||||
bucket[i][1] = value;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// 新增
|
||||
bucket.push([secondary, value]);
|
||||
this._size++;
|
||||
return this;
|
||||
}
|
||||
|
||||
get(key: BitMask64Data): T | undefined {
|
||||
const [primary, secondary] = this.getHashes(key);
|
||||
const bucket = this.buckets.get(primary);
|
||||
if (!bucket) return undefined;
|
||||
for (let i = 0; i < bucket.length; i++) {
|
||||
if (bucket[i][0] === secondary) {
|
||||
return bucket[i][1];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
has(key: BitMask64Data): boolean {
|
||||
return this.get(key) !== undefined;
|
||||
}
|
||||
|
||||
delete(key: BitMask64Data): boolean {
|
||||
const [primary, secondary] = this.getHashes(key);
|
||||
const bucket = this.buckets.get(primary);
|
||||
if (!bucket) return false;
|
||||
for (let i = 0; i < bucket.length; i++) {
|
||||
if (bucket[i][0] === secondary) {
|
||||
bucket.splice(i, 1);
|
||||
this._size--;
|
||||
if (bucket.length === 0) {
|
||||
this.buckets.delete(primary);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.buckets.clear();
|
||||
this._size = 0;
|
||||
}
|
||||
|
||||
*entries(): IterableIterator<[BitMask64Data, T]> {
|
||||
for (const [_, bucket] of this.buckets) {
|
||||
for (const [secondary, value] of bucket) {
|
||||
// 无法还原原始 key(只存二级 hash),所以 entries 返回不了 key
|
||||
yield [undefined as any, value];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*values(): IterableIterator<T> {
|
||||
for (const bucket of this.buckets.values()) {
|
||||
for (const [_, value] of bucket) {
|
||||
yield value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { BitMask64Data, BitMask64Utils } from './BigIntCompatibility';
|
||||
import { SegmentPart, BitMask64Data, BitMask64Utils } from './BigIntCompatibility';
|
||||
|
||||
/**
|
||||
* 位集合类,用于高效的位操作
|
||||
* 自动扩展支持:默认 64 位,超过时自动扩展到 128/256 位
|
||||
* 扩展模式性能略有下降,但仍然比数组遍历快得多
|
||||
* 支持任意位的位运算操作.
|
||||
*/
|
||||
export class Bits {
|
||||
/** 存储位数据的掩码,支持扩展 */
|
||||
@@ -37,7 +36,7 @@ export class Bits {
|
||||
throw new Error('Bit index cannot be negative');
|
||||
}
|
||||
|
||||
BitMask64Utils.setBitExtended(this._value, index);
|
||||
BitMask64Utils.setBit(this._value, index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +49,7 @@ export class Bits {
|
||||
throw new Error('Bit index cannot be negative');
|
||||
}
|
||||
|
||||
BitMask64Utils.clearBitExtended(this._value, index);
|
||||
BitMask64Utils.clearBit(this._value, index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +58,7 @@ export class Bits {
|
||||
* @returns 如果位被设置为1则返回true,否则返回false
|
||||
*/
|
||||
public get(index: number): boolean {
|
||||
return BitMask64Utils.getBitExtended(this._value, index);
|
||||
return BitMask64Utils.getBit(this._value, index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,16 +162,16 @@ export class Bits {
|
||||
|
||||
if (maxBits <= 32) {
|
||||
const mask = (1 << maxBits) - 1;
|
||||
result._value.lo = (~result._value.lo) & mask;
|
||||
result._value.hi = 0;
|
||||
result._value.base[SegmentPart.LOW] = (~result._value.base[SegmentPart.LOW]) & mask;
|
||||
result._value.base[SegmentPart.HIGH] = 0;
|
||||
} else {
|
||||
result._value.lo = ~result._value.lo;
|
||||
result._value.base[SegmentPart.LOW] = ~result._value.base[SegmentPart.LOW];
|
||||
if (maxBits < 64) {
|
||||
const remainingBits = maxBits - 32;
|
||||
const mask = (1 << remainingBits) - 1;
|
||||
result._value.hi = (~result._value.hi) & mask;
|
||||
result._value.base[SegmentPart.HIGH] = (~result._value.base[SegmentPart.HIGH]) & mask;
|
||||
} else {
|
||||
result._value.hi = ~result._value.hi;
|
||||
result._value.base[SegmentPart.HIGH] = ~result._value.base[SegmentPart.HIGH];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,9 +236,10 @@ export class Bits {
|
||||
* @param maxBits 最大位数,默认为64
|
||||
* @returns 二进制字符串表示,每8位用空格分隔
|
||||
*/
|
||||
public toBinaryString(maxBits: number = 64): string {
|
||||
if (maxBits > 64) maxBits = 64;
|
||||
|
||||
public toBinaryString(maxBits: number = 0): string {
|
||||
if(maxBits == 0){
|
||||
maxBits = 64 + (this._value.segments ? this._value.segments.length * 64 : 0);
|
||||
}
|
||||
let result = '';
|
||||
for (let i = maxBits - 1; i >= 0; i--) {
|
||||
result += this.get(i) ? '1' : '0';
|
||||
@@ -265,16 +265,16 @@ export class Bits {
|
||||
*/
|
||||
public static fromBinaryString(binaryString: string): Bits {
|
||||
const cleanString = binaryString.replace(/\s/g, '');
|
||||
let data: BitMask64Data;
|
||||
let data: BitMask64Data = { base: undefined!, segments: undefined};
|
||||
if (cleanString.length <= 32) {
|
||||
const num = parseInt(cleanString, 2);
|
||||
data = { lo: num >>> 0, hi: 0 };
|
||||
data.base = [num >>> 0, 0];
|
||||
} else {
|
||||
const loBits = cleanString.substring(cleanString.length - 32);
|
||||
const hiBits = cleanString.substring(0, cleanString.length - 32);
|
||||
const lo = parseInt(loBits, 2);
|
||||
const hi = parseInt(hiBits, 2);
|
||||
data = { lo: lo >>> 0, hi: hi >>> 0 };
|
||||
data.base = [lo >>> 0, hi >>> 0];
|
||||
}
|
||||
return new Bits(data);
|
||||
}
|
||||
@@ -286,16 +286,16 @@ export class Bits {
|
||||
*/
|
||||
public static fromHexString(hexString: string): Bits {
|
||||
const cleanString = hexString.replace(/^0x/i, '');
|
||||
let data: BitMask64Data;
|
||||
let data: BitMask64Data = { base: undefined!, segments: undefined};
|
||||
if (cleanString.length <= 8) {
|
||||
const num = parseInt(cleanString, 16);
|
||||
data = { lo: num >>> 0, hi: 0 };
|
||||
data.base = [num >>> 0, 0];
|
||||
} else {
|
||||
const loBits = cleanString.substring(cleanString.length - 8);
|
||||
const hiBits = cleanString.substring(0, cleanString.length - 8);
|
||||
const lo = parseInt(loBits, 16);
|
||||
const hi = parseInt(hiBits, 16);
|
||||
data = { lo: lo >>> 0, hi: hi >>> 0 };
|
||||
data.base = [lo >>> 0, hi >>> 0];
|
||||
}
|
||||
return new Bits(data);
|
||||
}
|
||||
@@ -318,16 +318,16 @@ export class Bits {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (this._value.hi !== 0) {
|
||||
if (this._value.base[SegmentPart.HIGH] !== 0) {
|
||||
for (let i = 31; i >= 0; i--) {
|
||||
if ((this._value.hi & (1 << i)) !== 0) {
|
||||
if ((this._value.base[SegmentPart.HIGH] & (1 << i)) !== 0) {
|
||||
return i + 32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 31; i >= 0; i--) {
|
||||
if ((this._value.lo & (1 << i)) !== 0) {
|
||||
if ((this._value.base[SegmentPart.LOW] & (1 << i)) !== 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -345,13 +345,13 @@ export class Bits {
|
||||
}
|
||||
|
||||
for (let i = 0; i < 32; i++) {
|
||||
if ((this._value.lo & (1 << i)) !== 0) {
|
||||
if ((this._value.base[SegmentPart.LOW] & (1 << i)) !== 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < 32; i++) {
|
||||
if ((this._value.hi & (1 << i)) !== 0) {
|
||||
if ((this._value.base[SegmentPart.HIGH] & (1 << i)) !== 0) {
|
||||
return i + 32;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
ComponentRegistry,
|
||||
ComponentStorage,
|
||||
ComponentStorageManager,
|
||||
ComponentType
|
||||
} from '../../../src/ECS/Core/ComponentStorage';
|
||||
import { ComponentRegistry, ComponentStorage, ComponentStorageManager } from '../../../src/ECS/Core/ComponentStorage';
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { BitMask64Utils } from '../../../src/ECS/Utils/BigIntCompatibility';
|
||||
|
||||
@@ -104,8 +99,8 @@ describe('ComponentRegistry - 组件注册表测试', () => {
|
||||
const mask1 = ComponentRegistry.getBitMask(TestComponent);
|
||||
const mask2 = ComponentRegistry.getBitMask(PositionComponent);
|
||||
|
||||
expect(mask1.lo).toBe(1); // 2^0
|
||||
expect(mask2.lo).toBe(2); // 2^1
|
||||
expect(BitMask64Utils.getBit(mask1,0)).toBe(true); // 2^0
|
||||
expect(BitMask64Utils.getBit(mask2,1)).toBe(true); // 2^1
|
||||
});
|
||||
|
||||
test('应该能够获取组件的位索引', () => {
|
||||
@@ -471,7 +466,7 @@ describe('ComponentStorageManager - 组件存储管理器测试', () => {
|
||||
const mask = manager.getComponentMask(1);
|
||||
|
||||
// 应该包含TestComponent(位0)和PositionComponent(位1)的掩码
|
||||
expect(mask.lo).toBe(3); // 1 | 2 = 3
|
||||
expect(BitMask64Utils.getBit(mask,0) && BitMask64Utils.getBit(mask,1)).toBe(true);
|
||||
});
|
||||
|
||||
test('没有组件的实体应该有零掩码', () => {
|
||||
@@ -485,15 +480,15 @@ describe('ComponentStorageManager - 组件存储管理器测试', () => {
|
||||
|
||||
manager.addComponent(1, new TestComponent(100));
|
||||
let mask = manager.getComponentMask(1);
|
||||
expect(mask.lo).toBe(1);
|
||||
expect(BitMask64Utils.getBit(mask,0)).toBe(true);
|
||||
|
||||
manager.addComponent(1, new PositionComponent(10, 20));
|
||||
mask = manager.getComponentMask(1);
|
||||
expect(mask.lo).toBe(3); // 0b11
|
||||
expect(BitMask64Utils.getBit(mask,1)).toBe(true); // 0b11
|
||||
|
||||
manager.removeComponent(1, TestComponent);
|
||||
mask = manager.getComponentMask(1);
|
||||
expect(mask.lo).toBe(2); // 0b10
|
||||
expect(BitMask64Utils.getBit(mask,0)).toBe(false); // 0b10
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,247 +1,183 @@
|
||||
import {
|
||||
BitMask64Data,
|
||||
BitMask64Utils
|
||||
} from '../../../src/ECS/Utils/BigIntCompatibility';
|
||||
import { BitMask64Data, BitMask64Utils } from "../../../src";
|
||||
|
||||
describe('64位掩码兼容性测试', () => {
|
||||
describe('基本功能', () => {
|
||||
it('应该能够创建和检查掩码', () => {
|
||||
const zero = BitMask64Utils.ZERO;
|
||||
const mask1 = BitMask64Utils.create(0);
|
||||
const mask2 = BitMask64Utils.create(5);
|
||||
|
||||
expect(BitMask64Utils.isZero(zero)).toBe(true);
|
||||
expect(BitMask64Utils.isZero(mask1)).toBe(false);
|
||||
expect(BitMask64Utils.isZero(mask2)).toBe(false);
|
||||
});
|
||||
describe("BitMask64Utils 位掩码工具测试", () => {
|
||||
test("create() 应该在指定索引位置设置位", () => {
|
||||
const mask = BitMask64Utils.create(0);
|
||||
expect(mask.base[0]).toBe(1);
|
||||
expect(mask.base[1]).toBe(0);
|
||||
|
||||
it('应该支持数字创建', () => {
|
||||
const mask = BitMask64Utils.fromNumber(42);
|
||||
expect(mask.lo).toBe(42);
|
||||
expect(mask.hi).toBe(0);
|
||||
});
|
||||
const mask2 = BitMask64Utils.create(33);
|
||||
expect(mask2.base[0]).toBe(0);
|
||||
expect(mask2.base[1]).toBe(0b10);
|
||||
});
|
||||
|
||||
describe('位运算', () => {
|
||||
let mask1: BitMask64Data;
|
||||
let mask2: BitMask64Data;
|
||||
|
||||
beforeEach(() => {
|
||||
mask1 = BitMask64Utils.create(2); // 位2
|
||||
mask2 = BitMask64Utils.create(3); // 位3
|
||||
});
|
||||
|
||||
it('hasAny运算', () => {
|
||||
const combined = BitMask64Utils.clone(BitMask64Utils.ZERO);
|
||||
BitMask64Utils.orInPlace(combined, mask1);
|
||||
BitMask64Utils.orInPlace(combined, mask2);
|
||||
|
||||
expect(BitMask64Utils.hasAny(combined, mask1)).toBe(true);
|
||||
expect(BitMask64Utils.hasAny(combined, mask2)).toBe(true);
|
||||
|
||||
const mask4 = BitMask64Utils.create(4);
|
||||
expect(BitMask64Utils.hasAny(combined, mask4)).toBe(false);
|
||||
});
|
||||
|
||||
it('hasAll运算', () => {
|
||||
const combined = BitMask64Utils.clone(BitMask64Utils.ZERO);
|
||||
BitMask64Utils.orInPlace(combined, mask1);
|
||||
BitMask64Utils.orInPlace(combined, mask2);
|
||||
|
||||
expect(BitMask64Utils.hasAll(combined, mask1)).toBe(true);
|
||||
expect(BitMask64Utils.hasAll(combined, mask2)).toBe(true);
|
||||
|
||||
const both = BitMask64Utils.clone(BitMask64Utils.ZERO);
|
||||
BitMask64Utils.orInPlace(both, mask1);
|
||||
BitMask64Utils.orInPlace(both, mask2);
|
||||
expect(BitMask64Utils.hasAll(combined, both)).toBe(true);
|
||||
});
|
||||
|
||||
it('hasNone运算', () => {
|
||||
const mask4 = BitMask64Utils.create(4);
|
||||
const mask5 = BitMask64Utils.create(5);
|
||||
|
||||
expect(BitMask64Utils.hasNone(mask1, mask2)).toBe(true);
|
||||
expect(BitMask64Utils.hasNone(mask1, mask4)).toBe(true);
|
||||
expect(BitMask64Utils.hasNone(mask1, mask1)).toBe(false);
|
||||
});
|
||||
|
||||
it('原地位运算', () => {
|
||||
const target = BitMask64Utils.clone(mask1);
|
||||
|
||||
// OR操作
|
||||
BitMask64Utils.orInPlace(target, mask2);
|
||||
expect(BitMask64Utils.hasAll(target, mask1)).toBe(true);
|
||||
expect(BitMask64Utils.hasAll(target, mask2)).toBe(true);
|
||||
|
||||
// AND操作
|
||||
const andTarget = BitMask64Utils.clone(target);
|
||||
BitMask64Utils.andInPlace(andTarget, mask1);
|
||||
expect(BitMask64Utils.hasAll(andTarget, mask1)).toBe(true);
|
||||
expect(BitMask64Utils.hasAny(andTarget, mask2)).toBe(false);
|
||||
|
||||
// XOR操作
|
||||
const xorTarget = BitMask64Utils.clone(target);
|
||||
BitMask64Utils.xorInPlace(xorTarget, mask1);
|
||||
expect(BitMask64Utils.hasAny(xorTarget, mask1)).toBe(false);
|
||||
expect(BitMask64Utils.hasAll(xorTarget, mask2)).toBe(true);
|
||||
});
|
||||
|
||||
it('设置和清除位', () => {
|
||||
const mask = BitMask64Utils.clone(BitMask64Utils.ZERO);
|
||||
|
||||
BitMask64Utils.setBit(mask, 5);
|
||||
expect(BitMask64Utils.hasAny(mask, BitMask64Utils.create(5))).toBe(true);
|
||||
|
||||
BitMask64Utils.clearBit(mask, 5);
|
||||
expect(BitMask64Utils.isZero(mask)).toBe(true);
|
||||
});
|
||||
test("fromNumber() 应该把数值放入低32位", () => {
|
||||
const mask = BitMask64Utils.fromNumber(123456);
|
||||
expect(mask.base[0]).toBe(123456);
|
||||
expect(mask.base[1]).toBe(0);
|
||||
});
|
||||
|
||||
describe('字符串表示', () => {
|
||||
it('二进制字符串', () => {
|
||||
const mask = BitMask64Utils.create(5); // 位5设置为1
|
||||
const binaryStr = BitMask64Utils.toString(mask, 2);
|
||||
expect(binaryStr).toBe('100000'); // 位5为1
|
||||
});
|
||||
test("setBit/getBit/clearBit 应该正确设置、读取和清除位", () => {
|
||||
const mask: BitMask64Data = { base: [0, 0] };
|
||||
|
||||
it('十六进制字符串', () => {
|
||||
const mask = BitMask64Utils.fromNumber(255);
|
||||
const hexStr = BitMask64Utils.toString(mask, 16);
|
||||
expect(hexStr).toBe('0xFF');
|
||||
});
|
||||
BitMask64Utils.setBit(mask, 5);
|
||||
expect(BitMask64Utils.getBit(mask, 5)).toBe(true);
|
||||
|
||||
it('大数字的十六进制表示', () => {
|
||||
const mask: BitMask64Data = { lo: 0xFFFFFFFF, hi: 0x12345678 };
|
||||
const hexStr = BitMask64Utils.toString(mask, 16);
|
||||
expect(hexStr).toBe('0x12345678FFFFFFFF');
|
||||
});
|
||||
BitMask64Utils.clearBit(mask, 5);
|
||||
expect(BitMask64Utils.getBit(mask, 5)).toBe(false);
|
||||
|
||||
// 测试扩展段
|
||||
BitMask64Utils.setBit(mask, 70);
|
||||
expect(mask.segments).toBeDefined();
|
||||
expect(BitMask64Utils.getBit(mask, 70)).toBe(true);
|
||||
});
|
||||
|
||||
describe('位计数', () => {
|
||||
it('popCount应该正确计算设置位的数量', () => {
|
||||
const mask = BitMask64Utils.clone(BitMask64Utils.ZERO);
|
||||
expect(BitMask64Utils.popCount(mask)).toBe(0);
|
||||
|
||||
BitMask64Utils.setBit(mask, 0);
|
||||
BitMask64Utils.setBit(mask, 2);
|
||||
BitMask64Utils.setBit(mask, 4);
|
||||
expect(BitMask64Utils.popCount(mask)).toBe(3);
|
||||
});
|
||||
test("hasAny/hasAll/hasNone 判断应正确", () => {
|
||||
const maskA = BitMask64Utils.create(1);
|
||||
const maskB = BitMask64Utils.create(1);
|
||||
const maskC = BitMask64Utils.create(2);
|
||||
|
||||
it('大数的popCount', () => {
|
||||
const mask = BitMask64Utils.fromNumber(0xFF); // 8个1
|
||||
expect(BitMask64Utils.popCount(mask)).toBe(8);
|
||||
});
|
||||
expect(BitMask64Utils.hasAny(maskA, maskB)).toBe(true);
|
||||
expect(BitMask64Utils.hasAll(maskA, maskB)).toBe(true);
|
||||
expect(BitMask64Utils.hasNone(maskA, maskC)).toBe(true);
|
||||
});
|
||||
|
||||
describe('ECS组件掩码操作', () => {
|
||||
it('多组件掩码组合', () => {
|
||||
const componentMasks: BitMask64Data[] = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
componentMasks.push(BitMask64Utils.create(i));
|
||||
}
|
||||
|
||||
let combinedMask = BitMask64Utils.clone(BitMask64Utils.ZERO);
|
||||
for (const mask of componentMasks) {
|
||||
BitMask64Utils.orInPlace(combinedMask, mask);
|
||||
}
|
||||
|
||||
expect(BitMask64Utils.popCount(combinedMask)).toBe(10);
|
||||
|
||||
// 检查所有位都设置了
|
||||
for (let i = 0; i < 10; i++) {
|
||||
expect(BitMask64Utils.hasAny(combinedMask, BitMask64Utils.create(i))).toBe(true);
|
||||
}
|
||||
});
|
||||
test("isZero 应正确判断", () => {
|
||||
const mask = BitMask64Utils.create(3);
|
||||
expect(BitMask64Utils.isZero(mask)).toBe(false);
|
||||
|
||||
it('实体匹配模拟', () => {
|
||||
// 模拟实体具有组件0, 2, 4
|
||||
const entityMask = BitMask64Utils.clone(BitMask64Utils.ZERO);
|
||||
BitMask64Utils.setBit(entityMask, 0);
|
||||
BitMask64Utils.setBit(entityMask, 2);
|
||||
BitMask64Utils.setBit(entityMask, 4);
|
||||
|
||||
// 查询需要组件0和2
|
||||
const queryMask = BitMask64Utils.clone(BitMask64Utils.ZERO);
|
||||
BitMask64Utils.setBit(queryMask, 0);
|
||||
BitMask64Utils.setBit(queryMask, 2);
|
||||
|
||||
expect(BitMask64Utils.hasAll(entityMask, queryMask)).toBe(true);
|
||||
|
||||
// 查询需要组件1和3
|
||||
const queryMask2 = BitMask64Utils.clone(BitMask64Utils.ZERO);
|
||||
BitMask64Utils.setBit(queryMask2, 1);
|
||||
BitMask64Utils.setBit(queryMask2, 3);
|
||||
|
||||
expect(BitMask64Utils.hasAll(entityMask, queryMask2)).toBe(false);
|
||||
});
|
||||
BitMask64Utils.clear(mask);
|
||||
expect(BitMask64Utils.isZero(mask)).toBe(true);
|
||||
});
|
||||
|
||||
describe('边界测试', () => {
|
||||
it('应该处理64位边界', () => {
|
||||
expect(() => BitMask64Utils.create(63)).not.toThrow();
|
||||
expect(() => BitMask64Utils.create(64)).toThrow();
|
||||
expect(() => BitMask64Utils.create(-1)).toThrow();
|
||||
});
|
||||
test("equals 应正确判断两个掩码是否相等", () => {
|
||||
const mask1 = BitMask64Utils.create(10);
|
||||
const mask2 = BitMask64Utils.create(10);
|
||||
const mask3 = BitMask64Utils.create(11);
|
||||
|
||||
it('设置和清除边界位', () => {
|
||||
const mask = BitMask64Utils.clone(BitMask64Utils.ZERO);
|
||||
|
||||
BitMask64Utils.setBit(mask, 63);
|
||||
expect(BitMask64Utils.hasAny(mask, BitMask64Utils.create(63))).toBe(true);
|
||||
expect(mask.hi).not.toBe(0);
|
||||
expect(mask.lo).toBe(0);
|
||||
|
||||
BitMask64Utils.clearBit(mask, 63);
|
||||
expect(BitMask64Utils.isZero(mask)).toBe(true);
|
||||
});
|
||||
|
||||
it('高32位和低32位操作', () => {
|
||||
const lowMask = BitMask64Utils.create(15); // 低32位
|
||||
const highMask = BitMask64Utils.create(47); // 高32位
|
||||
|
||||
expect(lowMask.hi).toBe(0);
|
||||
expect(lowMask.lo).not.toBe(0);
|
||||
|
||||
expect(highMask.hi).not.toBe(0);
|
||||
expect(highMask.lo).toBe(0);
|
||||
|
||||
const combined = BitMask64Utils.clone(BitMask64Utils.ZERO);
|
||||
BitMask64Utils.orInPlace(combined, lowMask);
|
||||
BitMask64Utils.orInPlace(combined, highMask);
|
||||
|
||||
expect(combined.hi).not.toBe(0);
|
||||
expect(combined.lo).not.toBe(0);
|
||||
expect(BitMask64Utils.popCount(combined)).toBe(2);
|
||||
});
|
||||
expect(BitMask64Utils.equals(mask1, mask2)).toBe(true);
|
||||
expect(BitMask64Utils.equals(mask1, mask3)).toBe(false);
|
||||
});
|
||||
|
||||
describe('复制和相等性', () => {
|
||||
it('clone应该创建独立副本', () => {
|
||||
const original = BitMask64Utils.create(5);
|
||||
const cloned = BitMask64Utils.clone(original);
|
||||
|
||||
expect(BitMask64Utils.equals(original, cloned)).toBe(true);
|
||||
|
||||
BitMask64Utils.setBit(cloned, 6);
|
||||
expect(BitMask64Utils.equals(original, cloned)).toBe(false);
|
||||
});
|
||||
test("orInPlace/andInPlace/xorInPlace 运算应正确", () => {
|
||||
const mask1 = BitMask64Utils.create(1);
|
||||
const mask2 = BitMask64Utils.create(2);
|
||||
|
||||
it('copy应该正确复制', () => {
|
||||
const source = BitMask64Utils.create(10);
|
||||
const target = BitMask64Utils.clone(BitMask64Utils.ZERO);
|
||||
|
||||
BitMask64Utils.copy(source, target);
|
||||
expect(BitMask64Utils.equals(source, target)).toBe(true);
|
||||
});
|
||||
BitMask64Utils.orInPlace(mask1, mask2);
|
||||
expect(BitMask64Utils.getBit(mask1, 1)).toBe(true);
|
||||
expect(BitMask64Utils.getBit(mask1, 2)).toBe(true);
|
||||
|
||||
it('clear应该清除所有位', () => {
|
||||
const mask = BitMask64Utils.create(20);
|
||||
expect(BitMask64Utils.isZero(mask)).toBe(false);
|
||||
|
||||
BitMask64Utils.clear(mask);
|
||||
expect(BitMask64Utils.isZero(mask)).toBe(true);
|
||||
});
|
||||
BitMask64Utils.andInPlace(mask1, mask2);
|
||||
expect(BitMask64Utils.getBit(mask1, 1)).toBe(false);
|
||||
expect(BitMask64Utils.getBit(mask1, 2)).toBe(true);
|
||||
|
||||
BitMask64Utils.xorInPlace(mask1, mask2);
|
||||
expect(BitMask64Utils.getBit(mask1, 2)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test("copy/clone 应正确复制数据", () => {
|
||||
const source = BitMask64Utils.create(15);
|
||||
const target: BitMask64Data = { base: [0, 0] };
|
||||
|
||||
BitMask64Utils.copy(source, target);
|
||||
expect(BitMask64Utils.equals(source, target)).toBe(true);
|
||||
|
||||
const clone = BitMask64Utils.clone(source);
|
||||
expect(BitMask64Utils.equals(source, clone)).toBe(true);
|
||||
expect(clone).not.toBe(source); // 深拷贝
|
||||
});
|
||||
|
||||
test("越界与非法输入处理", () => {
|
||||
expect(() => BitMask64Utils.create(-1)).toThrow();
|
||||
expect(BitMask64Utils.getBit({ base: [0,0] }, -5)).toBe(false);
|
||||
expect(() => BitMask64Utils.clearBit({ base: [0,0] }, -2)).toThrow();
|
||||
});
|
||||
|
||||
test("大于64位的扩展段逻辑 - hasAny/hasAll/hasNone/equals", () => {
|
||||
// 掩码 A: 只在 bit 150 位置为 1
|
||||
const maskA = BitMask64Utils.create(150);
|
||||
// 掩码 B: 只在 bit 200 位置为 1
|
||||
const maskB = BitMask64Utils.create(200);
|
||||
|
||||
// A 与 B 在不同扩展段,不存在重叠位
|
||||
expect(BitMask64Utils.hasAny(maskA, maskB)).toBe(false);
|
||||
expect(BitMask64Utils.hasNone(maskA, maskB)).toBe(true);
|
||||
|
||||
// C: 在 150 与 200 都置位
|
||||
const maskC = BitMask64Utils.clone(maskA);
|
||||
BitMask64Utils.setBit(maskC, 200);
|
||||
|
||||
// A 是 C 的子集
|
||||
expect(BitMask64Utils.hasAll(maskC, maskA)).toBe(true);
|
||||
// B 是 C 的子集
|
||||
expect(BitMask64Utils.hasAll(maskC, maskB)).toBe(true);
|
||||
|
||||
// A 和 C 不相等
|
||||
expect(BitMask64Utils.equals(maskA, maskC)).toBe(false);
|
||||
|
||||
// C 与自身相等
|
||||
expect(BitMask64Utils.equals(maskC, maskC)).toBe(true);
|
||||
|
||||
//copy
|
||||
const copyMask = BitMask64Utils.create(0);
|
||||
BitMask64Utils.copy(maskA,copyMask);
|
||||
expect(BitMask64Utils.equals(copyMask,maskA)).toBe(true);
|
||||
|
||||
// hasAll短路测试,对第一个if的测试覆盖
|
||||
BitMask64Utils.setBit(copyMask,64);
|
||||
expect(BitMask64Utils.hasAll(maskA, copyMask)).toBe(false);
|
||||
BitMask64Utils.clearBit(copyMask, 64);
|
||||
|
||||
// 扩展到350位,对最后一个短路if的测试覆盖
|
||||
BitMask64Utils.setBit(copyMask,350);
|
||||
expect(BitMask64Utils.hasAll(maskA, copyMask)).toBe(false);
|
||||
});
|
||||
|
||||
test("大于64位的逻辑运算 - or/and/xor 跨段处理", () => {
|
||||
const maskA = BitMask64Utils.create(128); // 第一扩展段
|
||||
const maskB = BitMask64Utils.create(190); // 同一扩展段但不同位置
|
||||
const maskC = BitMask64Utils.create(300); // 不同扩展段
|
||||
|
||||
// OR: 合并不同扩展段
|
||||
const orMask = BitMask64Utils.clone(maskA);
|
||||
BitMask64Utils.orInPlace(orMask, maskC);
|
||||
expect(BitMask64Utils.getBit(orMask, 128)).toBe(true);
|
||||
expect(BitMask64Utils.getBit(orMask, 300)).toBe(true);
|
||||
|
||||
// AND: 交集为空
|
||||
const andMask = BitMask64Utils.clone(maskA);
|
||||
BitMask64Utils.andInPlace(andMask, maskB);
|
||||
expect(BitMask64Utils.isZero(andMask)).toBe(true);
|
||||
|
||||
// XOR: 不同扩展段应该都保留
|
||||
const xorMask = BitMask64Utils.clone(maskA);
|
||||
BitMask64Utils.xorInPlace(xorMask, maskC);
|
||||
expect(BitMask64Utils.getBit(xorMask, 128)).toBe(true);
|
||||
expect(BitMask64Utils.getBit(xorMask, 300)).toBe(true);
|
||||
});
|
||||
|
||||
test("toString 与 popCount 应该在扩展段正常工作", () => {
|
||||
const mask = BitMask64Utils.create(0);
|
||||
BitMask64Utils.setBit(mask, 130); // 扩展段,此时扩展段长度延长到2
|
||||
BitMask64Utils.setBit(mask, 260); // 再设置另一个超出当前最高段范围更高位,此时扩展段长度延长到3
|
||||
// 现在应该有三个置位
|
||||
expect(BitMask64Utils.popCount(mask)).toBe(3);
|
||||
|
||||
|
||||
const strBin = BitMask64Utils.toString(mask, 2);
|
||||
const strHex = BitMask64Utils.toString(mask, 16);
|
||||
// 第三个区段应该以100结尾(130位为1)
|
||||
expect(strBin.split(' ')[2].endsWith('100')).toBe(true);
|
||||
// 不存在高位的第三个区段字符串应为0x4
|
||||
expect(strHex.split(' ')[2]).toBe('0x4');
|
||||
|
||||
// 设置第244位为1 这是第四个区段的第(256 - 244 =)12位
|
||||
BitMask64Utils.setBit(mask, 244);
|
||||
// 四个区段的在二进制下第12位的字符串应为'1'
|
||||
expect(BitMask64Utils.toString(mask, 2).split(' ')[3][11]).toBe('1');
|
||||
// 第四个区段的十六进制下所有字符串应为'0x10000000000000',即二进制的'10000 00000000 00000000 00000000 00000000 00000000 00000000'
|
||||
expect(BitMask64Utils.toString(mask, 16).split(' ')[3]).toBe('0x10000000000000');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
75
packages/core/tests/ECS/Utils/BitMaskHashMap.test.ts
Normal file
75
packages/core/tests/ECS/Utils/BitMaskHashMap.test.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
// FlatHashMap.test.ts
|
||||
|
||||
import { BitMaskHashMap } from "../../../src/ECS/Utils/BitMaskHashMap";
|
||||
import { BitMask64Data, BitMask64Utils } from "../../../src";
|
||||
|
||||
describe("FlatHashMap 基础功能", () => {
|
||||
test("set/get/has/delete 基本操作", () => {
|
||||
const map = new BitMaskHashMap<number>();
|
||||
const keyA = BitMask64Utils.create(5);
|
||||
const keyB = BitMask64Utils.create(63);
|
||||
|
||||
map.set(keyA, 100);
|
||||
map.set(keyB, 200);
|
||||
|
||||
expect(map.size).toBe(2);
|
||||
expect(map.get(keyA)).toBe(100);
|
||||
expect(map.get(keyB)).toBe(200);
|
||||
expect(map.has(keyA)).toBe(true);
|
||||
|
||||
map.delete(keyA);
|
||||
expect(map.has(keyA)).toBe(false);
|
||||
expect(map.size).toBe(1);
|
||||
|
||||
map.clear();
|
||||
expect(map.size).toBe(0);
|
||||
});
|
||||
|
||||
test("覆盖 set 应该更新 value 而不是新增", () => {
|
||||
const map = new BitMaskHashMap<string>();
|
||||
const key = BitMask64Utils.create(10);
|
||||
|
||||
map.set(key, "foo");
|
||||
map.set(key, "bar");
|
||||
|
||||
expect(map.size).toBe(1);
|
||||
expect(map.get(key)).toBe("bar");
|
||||
});
|
||||
|
||||
test("不同 key 产生相同 primaryHash 时应正确区分", () => {
|
||||
const map = new BitMaskHashMap<number>();
|
||||
|
||||
// 伪造两个不同 key,理论上可能 hash 冲突
|
||||
// 为了测试,我们直接用两个高位 bit(分段不同)
|
||||
const keyA = BitMask64Utils.create(150);
|
||||
const keyB = BitMask64Utils.create(300);
|
||||
|
||||
map.set(keyA, 111);
|
||||
map.set(keyB, 222);
|
||||
|
||||
expect(map.get(keyA)).toBe(111);
|
||||
expect(map.get(keyB)).toBe(222);
|
||||
expect(map.size).toBe(2);
|
||||
});
|
||||
test("100000 个掩码连续的 key 不应存在冲突", () => {
|
||||
const map = new BitMaskHashMap<number>();
|
||||
const count = 100000;
|
||||
const mask: BitMask64Data = { base: [0,0] };
|
||||
for (let i = 0; i < count; i++) {
|
||||
let temp = i;
|
||||
// 遍历 i 的二进制表示的每一位
|
||||
let bitIndex = 0;
|
||||
while (temp > 0) {
|
||||
if (temp & 1) {
|
||||
BitMask64Utils.setBit(mask, bitIndex);
|
||||
}
|
||||
temp = temp >>> 1; // 无符号右移一位,检查下一位
|
||||
bitIndex++;
|
||||
}
|
||||
map.set(mask,1);
|
||||
}
|
||||
// 预计没有任何冲突,每一个元素都在单独的桶中。
|
||||
expect(map.innerBuckets.size).toBe(map.size);
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user