使用Lerna 和 monorepo管理项目结构
This commit is contained in:
338
packages/core/tests/ECS/Utils/BigIntCompatibility.test.ts
Normal file
338
packages/core/tests/ECS/Utils/BigIntCompatibility.test.ts
Normal file
@@ -0,0 +1,338 @@
|
||||
import {
|
||||
BigIntFactory,
|
||||
IBigIntLike,
|
||||
EnvironmentInfo
|
||||
} from '../../../src/ECS/Utils/BigIntCompatibility';
|
||||
|
||||
describe('BigInt兼容性测试', () => {
|
||||
describe('BigIntFactory环境检测', () => {
|
||||
it('应该能够检测BigInt支持情况', () => {
|
||||
const isSupported = BigIntFactory.isNativeSupported();
|
||||
expect(typeof isSupported).toBe('boolean');
|
||||
});
|
||||
|
||||
it('应该返回环境信息', () => {
|
||||
const envInfo = BigIntFactory.getEnvironmentInfo();
|
||||
expect(envInfo).toBeDefined();
|
||||
expect(typeof envInfo.supportsBigInt).toBe('boolean');
|
||||
expect(typeof envInfo.environment).toBe('string');
|
||||
expect(typeof envInfo.jsEngine).toBe('string');
|
||||
});
|
||||
|
||||
it('环境信息应该包含合理的字段', () => {
|
||||
const envInfo = BigIntFactory.getEnvironmentInfo();
|
||||
expect(envInfo.environment).not.toBe('');
|
||||
expect(envInfo.jsEngine).not.toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('BigIntFactory基本创建', () => {
|
||||
it('应该能够创建零值', () => {
|
||||
const zero = BigIntFactory.zero();
|
||||
expect(zero.isZero()).toBe(true);
|
||||
expect(zero.toString()).toBe('0');
|
||||
});
|
||||
|
||||
it('应该能够创建1值', () => {
|
||||
const one = BigIntFactory.one();
|
||||
expect(one.isZero()).toBe(false);
|
||||
expect(one.toString()).toBe('1');
|
||||
});
|
||||
|
||||
it('应该能够从数值创建', () => {
|
||||
const value = BigIntFactory.create(42);
|
||||
expect(value.toString()).toBe('42');
|
||||
expect(value.valueOf()).toBe(42);
|
||||
});
|
||||
|
||||
it('应该能够从字符串创建', () => {
|
||||
const value = BigIntFactory.create('123');
|
||||
expect(value.toString()).toBe('123');
|
||||
});
|
||||
|
||||
it('应该能够从原生BigInt创建(如果支持)', () => {
|
||||
if (BigIntFactory.isNativeSupported()) {
|
||||
const value = BigIntFactory.create(BigInt(456));
|
||||
expect(value.toString()).toBe('456');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('IBigIntLike基本操作', () => {
|
||||
let value1: IBigIntLike;
|
||||
let value2: IBigIntLike;
|
||||
|
||||
beforeEach(() => {
|
||||
value1 = BigIntFactory.create(5); // 101 in binary
|
||||
value2 = BigIntFactory.create(3); // 011 in binary
|
||||
});
|
||||
|
||||
it('should支持字符串转换', () => {
|
||||
expect(value1.toString()).toBe('5');
|
||||
expect(value2.toString()).toBe('3');
|
||||
});
|
||||
|
||||
it('应该支持十六进制转换', () => {
|
||||
const value = BigIntFactory.create(255);
|
||||
expect(value.toString(16)).toBe('FF');
|
||||
});
|
||||
|
||||
it('应该支持二进制转换', () => {
|
||||
expect(value1.toString(2)).toBe('101');
|
||||
expect(value2.toString(2)).toBe('11');
|
||||
});
|
||||
|
||||
it('应该支持相等比较', () => {
|
||||
const value1Copy = BigIntFactory.create(5);
|
||||
expect(value1.equals(value1Copy)).toBe(true);
|
||||
expect(value1.equals(value2)).toBe(false);
|
||||
});
|
||||
|
||||
it('应该支持零值检查', () => {
|
||||
const zero = BigIntFactory.zero();
|
||||
expect(zero.isZero()).toBe(true);
|
||||
expect(value1.isZero()).toBe(false);
|
||||
});
|
||||
|
||||
it('应该支持克隆操作', () => {
|
||||
const cloned = value1.clone();
|
||||
expect(cloned.equals(value1)).toBe(true);
|
||||
expect(cloned).not.toBe(value1); // 不同的对象引用
|
||||
});
|
||||
});
|
||||
|
||||
describe('位运算操作', () => {
|
||||
let value1: IBigIntLike; // 5 = 101
|
||||
let value2: IBigIntLike; // 3 = 011
|
||||
|
||||
beforeEach(() => {
|
||||
value1 = BigIntFactory.create(5);
|
||||
value2 = BigIntFactory.create(3);
|
||||
});
|
||||
|
||||
it('AND运算应该正确', () => {
|
||||
const result = value1.and(value2);
|
||||
expect(result.toString()).toBe('1'); // 101 & 011 = 001
|
||||
});
|
||||
|
||||
it('OR运算应该正确', () => {
|
||||
const result = value1.or(value2);
|
||||
expect(result.toString()).toBe('7'); // 101 | 011 = 111
|
||||
});
|
||||
|
||||
it('XOR运算应该正确', () => {
|
||||
const result = value1.xor(value2);
|
||||
expect(result.toString()).toBe('6'); // 101 ^ 011 = 110
|
||||
});
|
||||
|
||||
it('NOT运算应该正确(8位限制)', () => {
|
||||
const value = BigIntFactory.create(5); // 00000101
|
||||
const result = value.not(8);
|
||||
expect(result.toString()).toBe('250'); // 11111010 = 250
|
||||
});
|
||||
|
||||
it('左移位运算应该正确', () => {
|
||||
const result = value1.shiftLeft(2);
|
||||
expect(result.toString()).toBe('20'); // 101 << 2 = 10100 = 20
|
||||
});
|
||||
|
||||
it('右移位运算应该正确', () => {
|
||||
const result = value1.shiftRight(1);
|
||||
expect(result.toString()).toBe('2'); // 101 >> 1 = 10 = 2
|
||||
});
|
||||
|
||||
it('移位0位应该返回相同值', () => {
|
||||
const result = value1.shiftLeft(0);
|
||||
expect(result.equals(value1)).toBe(true);
|
||||
});
|
||||
|
||||
it('右移超过位数应该返回0', () => {
|
||||
const result = value1.shiftRight(10);
|
||||
expect(result.isZero()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('复杂位运算场景', () => {
|
||||
it('应该正确处理大数值位运算', () => {
|
||||
const large1 = BigIntFactory.create(0xFFFFFFFF); // 32位全1
|
||||
const large2 = BigIntFactory.create(0x12345678);
|
||||
|
||||
const andResult = large1.and(large2);
|
||||
expect(andResult.toString(16)).toBe('12345678');
|
||||
|
||||
const orResult = large1.or(large2);
|
||||
expect(orResult.toString(16)).toBe('FFFFFFFF');
|
||||
});
|
||||
|
||||
it('应该正确处理连续位运算', () => {
|
||||
let result = BigIntFactory.create(1);
|
||||
|
||||
// 构建 111111 (6个1)
|
||||
for (let i = 1; i < 6; i++) {
|
||||
const shifted = BigIntFactory.one().shiftLeft(i);
|
||||
result = result.or(shifted);
|
||||
}
|
||||
|
||||
expect(result.toString()).toBe('63'); // 111111 = 63
|
||||
expect(result.toString(2)).toBe('111111');
|
||||
});
|
||||
|
||||
it('应该正确处理掩码操作', () => {
|
||||
const value = BigIntFactory.create(0b10110101); // 181
|
||||
const mask = BigIntFactory.create(0b00001111); // 15, 低4位掩码
|
||||
|
||||
const masked = value.and(mask);
|
||||
expect(masked.toString()).toBe('5'); // 0101 = 5
|
||||
});
|
||||
});
|
||||
|
||||
describe('字符串解析功能', () => {
|
||||
it('应该支持从二进制字符串创建', () => {
|
||||
const value = BigIntFactory.fromBinaryString('10101');
|
||||
expect(value.toString()).toBe('21');
|
||||
});
|
||||
|
||||
it('应该支持从十六进制字符串创建', () => {
|
||||
const value1 = BigIntFactory.fromHexString('0xFF');
|
||||
const value2 = BigIntFactory.fromHexString('FF');
|
||||
|
||||
expect(value1.toString()).toBe('255');
|
||||
expect(value2.toString()).toBe('255');
|
||||
expect(value1.equals(value2)).toBe(true);
|
||||
});
|
||||
|
||||
it('应该正确处理大的十六进制值', () => {
|
||||
const value = BigIntFactory.fromHexString('0x12345678');
|
||||
expect(value.toString()).toBe('305419896');
|
||||
});
|
||||
|
||||
it('应该正确处理长二进制字符串', () => {
|
||||
const binaryStr = '11111111111111111111111111111111'; // 32个1
|
||||
const value = BigIntFactory.fromBinaryString(binaryStr);
|
||||
expect(value.toString()).toBe('4294967295'); // 2^32 - 1
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界情况和错误处理', () => {
|
||||
it('应该正确处理零值的所有操作', () => {
|
||||
const zero = BigIntFactory.zero();
|
||||
const one = BigIntFactory.one();
|
||||
|
||||
expect(zero.and(one).isZero()).toBe(true);
|
||||
expect(zero.or(one).equals(one)).toBe(true);
|
||||
expect(zero.xor(one).equals(one)).toBe(true);
|
||||
expect(zero.shiftLeft(5).isZero()).toBe(true);
|
||||
expect(zero.shiftRight(5).isZero()).toBe(true);
|
||||
});
|
||||
|
||||
it('应该正确处理1值的位运算', () => {
|
||||
const one = BigIntFactory.one();
|
||||
const zero = BigIntFactory.zero();
|
||||
|
||||
expect(one.and(zero).isZero()).toBe(true);
|
||||
expect(one.or(zero).equals(one)).toBe(true);
|
||||
expect(one.xor(zero).equals(one)).toBe(true);
|
||||
});
|
||||
|
||||
it('应该处理不支持的字符串进制', () => {
|
||||
const value = BigIntFactory.create(255);
|
||||
expect(() => value.toString(8)).toThrow();
|
||||
});
|
||||
|
||||
it('NOT运算应该正确处理不同的位数限制', () => {
|
||||
const value = BigIntFactory.one(); // 1
|
||||
|
||||
const not8 = value.not(8);
|
||||
expect(not8.toString()).toBe('254'); // 11111110 = 254
|
||||
|
||||
const not16 = value.not(16);
|
||||
expect(not16.toString()).toBe('65534'); // 1111111111111110 = 65534
|
||||
});
|
||||
});
|
||||
|
||||
describe('性能和兼容性测试', () => {
|
||||
it('两种实现应该产生相同的运算结果', () => {
|
||||
// 测试各种运算在两种模式下的一致性
|
||||
const testCases = [
|
||||
{ a: 0, b: 0 },
|
||||
{ a: 1, b: 1 },
|
||||
{ a: 5, b: 3 },
|
||||
{ a: 255, b: 128 },
|
||||
{ a: 65535, b: 32768 }
|
||||
];
|
||||
|
||||
testCases.forEach(({ a, b }) => {
|
||||
const val1 = BigIntFactory.create(a);
|
||||
const val2 = BigIntFactory.create(b);
|
||||
|
||||
// 基本运算
|
||||
const and = val1.and(val2);
|
||||
const or = val1.or(val2);
|
||||
const xor = val1.xor(val2);
|
||||
|
||||
// 验证运算结果的一致性
|
||||
expect(and.toString()).toBe((a & b).toString());
|
||||
expect(or.toString()).toBe((a | b).toString());
|
||||
expect(xor.toString()).toBe((a ^ b).toString());
|
||||
});
|
||||
});
|
||||
|
||||
it('大量运算应该保持高性能', () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
let result = BigIntFactory.zero();
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
const value = BigIntFactory.create(i);
|
||||
result = result.or(value.shiftLeft(i % 32));
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
expect(endTime - startTime).toBeLessThan(1000); // 应该在1秒内完成
|
||||
expect(result.isZero()).toBe(false);
|
||||
});
|
||||
|
||||
it('应该支持ECS框架中常见的位掩码操作', () => {
|
||||
// 模拟组件位掩码
|
||||
const componentMasks: IBigIntLike[] = [];
|
||||
|
||||
// 创建64个组件的位掩码
|
||||
for (let i = 0; i < 64; i++) {
|
||||
componentMasks.push(BigIntFactory.one().shiftLeft(i));
|
||||
}
|
||||
|
||||
// 组合多个组件掩码
|
||||
let combinedMask = BigIntFactory.zero();
|
||||
for (let i = 0; i < 10; i++) {
|
||||
combinedMask = combinedMask.or(componentMasks[i * 2]);
|
||||
}
|
||||
|
||||
// 检查是否包含特定组件
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const hasComponent = !combinedMask.and(componentMasks[i * 2]).isZero();
|
||||
expect(hasComponent).toBe(true);
|
||||
|
||||
const hasOtherComponent = !combinedMask.and(componentMasks[i * 2 + 1]).isZero();
|
||||
expect(hasOtherComponent).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('与Core类集成测试', () => {
|
||||
// 这里我们不直接导入Core来避免循环依赖,而是测试工厂方法
|
||||
it('BigIntFactory应该能够为Core提供环境信息', () => {
|
||||
const envInfo = BigIntFactory.getEnvironmentInfo();
|
||||
|
||||
// 验证所有必需的字段都存在
|
||||
expect(envInfo.supportsBigInt).toBeDefined();
|
||||
expect(envInfo.environment).toBeDefined();
|
||||
expect(envInfo.jsEngine).toBeDefined();
|
||||
});
|
||||
|
||||
it('应该提供详细的环境检测信息', () => {
|
||||
const envInfo = BigIntFactory.getEnvironmentInfo();
|
||||
|
||||
// 环境信息应该有意义
|
||||
expect(envInfo.environment).not.toBe('Unknown');
|
||||
});
|
||||
});
|
||||
});
|
||||
553
packages/core/tests/ECS/Utils/Bits.test.ts
Normal file
553
packages/core/tests/ECS/Utils/Bits.test.ts
Normal file
@@ -0,0 +1,553 @@
|
||||
import { Bits } from '../../../src/ECS/Utils/Bits';
|
||||
import { BigIntFactory } from '../../../src/ECS/Utils/BigIntCompatibility';
|
||||
|
||||
describe('Bits - 高性能位操作类测试', () => {
|
||||
let bits: Bits;
|
||||
|
||||
beforeEach(() => {
|
||||
bits = new Bits();
|
||||
});
|
||||
|
||||
describe('基本构造和初始化', () => {
|
||||
it('应该能够创建空的Bits对象', () => {
|
||||
expect(bits).toBeDefined();
|
||||
expect(bits.isEmpty()).toBe(true);
|
||||
expect(bits.getValue().isZero()).toBe(true);
|
||||
});
|
||||
|
||||
it('应该能够使用初始值创建Bits对象', () => {
|
||||
const bitsWithValue = new Bits(BigIntFactory.create(5)); // 二进制: 101
|
||||
expect(bitsWithValue.getValue().toString()).toBe('5');
|
||||
expect(bitsWithValue.isEmpty()).toBe(false);
|
||||
expect(bitsWithValue.get(0)).toBe(true); // 第0位
|
||||
expect(bitsWithValue.get(1)).toBe(false); // 第1位
|
||||
expect(bitsWithValue.get(2)).toBe(true); // 第2位
|
||||
});
|
||||
|
||||
it('默认构造函数应该创建值为0的对象', () => {
|
||||
const defaultBits = new Bits();
|
||||
expect(defaultBits.getValue().isZero()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('位设置和清除操作', () => {
|
||||
it('应该能够设置指定位置的位', () => {
|
||||
bits.set(0);
|
||||
expect(bits.get(0)).toBe(true);
|
||||
expect(bits.getValue().toString()).toBe('1');
|
||||
|
||||
bits.set(3);
|
||||
expect(bits.get(3)).toBe(true);
|
||||
expect(bits.getValue().toString()).toBe('9'); // 1001 in binary
|
||||
});
|
||||
|
||||
it('应该能够清除指定位置的位', () => {
|
||||
bits.set(0);
|
||||
bits.set(1);
|
||||
bits.set(2);
|
||||
expect(bits.getValue().toString()).toBe('7'); // 111 in binary
|
||||
|
||||
bits.clear(1);
|
||||
expect(bits.get(1)).toBe(false);
|
||||
expect(bits.getValue().toString()).toBe('5'); // 101 in binary
|
||||
});
|
||||
|
||||
it('重复设置同一位应该保持不变', () => {
|
||||
bits.set(0);
|
||||
const value1 = bits.getValue();
|
||||
bits.set(0);
|
||||
const value2 = bits.getValue();
|
||||
expect(value1.equals(value2)).toBe(true);
|
||||
});
|
||||
|
||||
it('清除未设置的位应该安全', () => {
|
||||
bits.clear(5);
|
||||
expect(bits.getValue().isZero()).toBe(true);
|
||||
});
|
||||
|
||||
it('设置负索引应该抛出错误', () => {
|
||||
expect(() => {
|
||||
bits.set(-1);
|
||||
}).toThrow('Bit index cannot be negative');
|
||||
});
|
||||
|
||||
it('清除负索引应该抛出错误', () => {
|
||||
expect(() => {
|
||||
bits.clear(-1);
|
||||
}).toThrow('Bit index cannot be negative');
|
||||
});
|
||||
});
|
||||
|
||||
describe('位获取操作', () => {
|
||||
beforeEach(() => {
|
||||
bits.set(0);
|
||||
bits.set(2);
|
||||
bits.set(4);
|
||||
});
|
||||
|
||||
it('应该能够正确获取设置的位', () => {
|
||||
expect(bits.get(0)).toBe(true);
|
||||
expect(bits.get(2)).toBe(true);
|
||||
expect(bits.get(4)).toBe(true);
|
||||
});
|
||||
|
||||
it('应该能够正确获取未设置的位', () => {
|
||||
expect(bits.get(1)).toBe(false);
|
||||
expect(bits.get(3)).toBe(false);
|
||||
expect(bits.get(5)).toBe(false);
|
||||
});
|
||||
|
||||
it('获取负索引应该返回false', () => {
|
||||
expect(bits.get(-1)).toBe(false);
|
||||
expect(bits.get(-10)).toBe(false);
|
||||
});
|
||||
|
||||
it('获取超大索引应该正确处理', () => {
|
||||
expect(bits.get(1000)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('位运算操作', () => {
|
||||
let otherBits: Bits;
|
||||
|
||||
beforeEach(() => {
|
||||
bits.set(0);
|
||||
bits.set(2);
|
||||
bits.set(4); // 10101 in binary = 21
|
||||
|
||||
otherBits = new Bits();
|
||||
otherBits.set(1);
|
||||
otherBits.set(2);
|
||||
otherBits.set(3); // 1110 in binary = 14
|
||||
});
|
||||
|
||||
it('AND运算应该正确', () => {
|
||||
const result = bits.and(otherBits);
|
||||
expect(result.getValue().toString()).toBe('4'); // 10101 & 01110 = 00100 = 4
|
||||
expect(result.get(2)).toBe(true);
|
||||
expect(result.get(0)).toBe(false);
|
||||
expect(result.get(1)).toBe(false);
|
||||
});
|
||||
|
||||
it('OR运算应该正确', () => {
|
||||
const result = bits.or(otherBits);
|
||||
expect(result.getValue().toString()).toBe('31'); // 10101 | 01110 = 11111 = 31
|
||||
expect(result.get(0)).toBe(true);
|
||||
expect(result.get(1)).toBe(true);
|
||||
expect(result.get(2)).toBe(true);
|
||||
expect(result.get(3)).toBe(true);
|
||||
expect(result.get(4)).toBe(true);
|
||||
});
|
||||
|
||||
it('XOR运算应该正确', () => {
|
||||
const result = bits.xor(otherBits);
|
||||
expect(result.getValue().toString()).toBe('27'); // 10101 ^ 01110 = 11011 = 27
|
||||
expect(result.get(0)).toBe(true);
|
||||
expect(result.get(1)).toBe(true);
|
||||
expect(result.get(2)).toBe(false); // 相同位XOR为0
|
||||
expect(result.get(3)).toBe(true);
|
||||
expect(result.get(4)).toBe(true);
|
||||
});
|
||||
|
||||
it('NOT运算应该正确', () => {
|
||||
const simpleBits = new Bits(BigIntFactory.create(5)); // 101 in binary
|
||||
const result = simpleBits.not(8); // 限制为8位
|
||||
expect(result.getValue().toString()).toBe('250'); // ~00000101 = 11111010 = 250 (8位)
|
||||
});
|
||||
|
||||
it('NOT运算默认64位应该正确', () => {
|
||||
const simpleBits = new Bits(BigIntFactory.create(1));
|
||||
const result = simpleBits.not();
|
||||
const expected = BigIntFactory.one().shiftLeft(64).valueOf() - 2; // 64位全1减去最低位
|
||||
expect(result.getValue().valueOf()).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('包含性检查', () => {
|
||||
let otherBits: Bits;
|
||||
|
||||
beforeEach(() => {
|
||||
bits.set(0);
|
||||
bits.set(2);
|
||||
bits.set(4); // 10101
|
||||
|
||||
otherBits = new Bits();
|
||||
});
|
||||
|
||||
it('containsAll应该正确检查包含所有位', () => {
|
||||
otherBits.set(0);
|
||||
otherBits.set(2); // 101
|
||||
expect(bits.containsAll(otherBits)).toBe(true);
|
||||
|
||||
otherBits.set(1); // 111
|
||||
expect(bits.containsAll(otherBits)).toBe(false);
|
||||
});
|
||||
|
||||
it('intersects应该正确检查交集', () => {
|
||||
otherBits.set(1);
|
||||
otherBits.set(3); // 1010
|
||||
expect(bits.intersects(otherBits)).toBe(false);
|
||||
|
||||
otherBits.set(0); // 1011
|
||||
expect(bits.intersects(otherBits)).toBe(true);
|
||||
});
|
||||
|
||||
it('excludes应该正确检查互斥', () => {
|
||||
otherBits.set(1);
|
||||
otherBits.set(3); // 1010
|
||||
expect(bits.excludes(otherBits)).toBe(true);
|
||||
|
||||
otherBits.set(0); // 1011
|
||||
expect(bits.excludes(otherBits)).toBe(false);
|
||||
});
|
||||
|
||||
it('空Bits对象的包含性检查', () => {
|
||||
const emptyBits = new Bits();
|
||||
expect(bits.containsAll(emptyBits)).toBe(true);
|
||||
expect(bits.intersects(emptyBits)).toBe(false);
|
||||
expect(bits.excludes(emptyBits)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('状态检查和计数', () => {
|
||||
it('isEmpty应该正确检查空状态', () => {
|
||||
expect(bits.isEmpty()).toBe(true);
|
||||
|
||||
bits.set(0);
|
||||
expect(bits.isEmpty()).toBe(false);
|
||||
|
||||
bits.clear(0);
|
||||
expect(bits.isEmpty()).toBe(true);
|
||||
});
|
||||
|
||||
it('cardinality应该正确计算设置的位数量', () => {
|
||||
expect(bits.cardinality()).toBe(0);
|
||||
|
||||
bits.set(0);
|
||||
expect(bits.cardinality()).toBe(1);
|
||||
|
||||
bits.set(2);
|
||||
bits.set(4);
|
||||
expect(bits.cardinality()).toBe(3);
|
||||
|
||||
bits.clear(2);
|
||||
expect(bits.cardinality()).toBe(2);
|
||||
});
|
||||
|
||||
it('大数值的cardinality应该正确', () => {
|
||||
// 设置很多位
|
||||
for (let i = 0; i < 100; i += 2) {
|
||||
bits.set(i);
|
||||
}
|
||||
expect(bits.cardinality()).toBe(50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('清空和重置操作', () => {
|
||||
beforeEach(() => {
|
||||
bits.set(0);
|
||||
bits.set(1);
|
||||
bits.set(2);
|
||||
});
|
||||
|
||||
it('clearAll应该清空所有位', () => {
|
||||
expect(bits.isEmpty()).toBe(false);
|
||||
bits.clearAll();
|
||||
expect(bits.isEmpty()).toBe(true);
|
||||
expect(bits.getValue().isZero()).toBe(true);
|
||||
});
|
||||
|
||||
it('clearAll后应该能重新设置位', () => {
|
||||
bits.clearAll();
|
||||
bits.set(5);
|
||||
expect(bits.get(5)).toBe(true);
|
||||
expect(bits.cardinality()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('复制和克隆操作', () => {
|
||||
beforeEach(() => {
|
||||
bits.set(1);
|
||||
bits.set(3);
|
||||
bits.set(5);
|
||||
});
|
||||
|
||||
it('copyFrom应该正确复制另一个Bits对象', () => {
|
||||
const newBits = new Bits();
|
||||
newBits.copyFrom(bits);
|
||||
|
||||
expect(newBits.getValue().equals(bits.getValue())).toBe(true);
|
||||
expect(newBits.equals(bits)).toBe(true);
|
||||
});
|
||||
|
||||
it('clone应该创建相同的副本', () => {
|
||||
const clonedBits = bits.clone();
|
||||
|
||||
expect(clonedBits.getValue().equals(bits.getValue())).toBe(true);
|
||||
expect(clonedBits.equals(bits)).toBe(true);
|
||||
expect(clonedBits).not.toBe(bits); // 应该是不同的对象
|
||||
});
|
||||
|
||||
it('修改克隆对象不应该影响原对象', () => {
|
||||
const clonedBits = bits.clone();
|
||||
clonedBits.set(7);
|
||||
|
||||
expect(bits.get(7)).toBe(false);
|
||||
expect(clonedBits.get(7)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('值操作', () => {
|
||||
it('getValue和setValue应该正确工作', () => {
|
||||
bits.setValue(42);
|
||||
expect(bits.getValue().toString()).toBe('42');
|
||||
});
|
||||
|
||||
it('setValue应该正确反映在位操作中', () => {
|
||||
bits.setValue(5); // 101 in binary
|
||||
expect(bits.get(0)).toBe(true);
|
||||
expect(bits.get(1)).toBe(false);
|
||||
expect(bits.get(2)).toBe(true);
|
||||
});
|
||||
|
||||
it('setValue为0应该清空所有位', () => {
|
||||
bits.set(1);
|
||||
bits.set(2);
|
||||
bits.setValue(0);
|
||||
expect(bits.isEmpty()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('字符串表示和解析', () => {
|
||||
beforeEach(() => {
|
||||
bits.set(0);
|
||||
bits.set(2);
|
||||
bits.set(4); // 10101 = 21
|
||||
});
|
||||
|
||||
it('toString应该返回可读的位表示', () => {
|
||||
const str = bits.toString();
|
||||
expect(str).toBe('Bits[0, 2, 4]');
|
||||
});
|
||||
|
||||
it('空Bits的toString应该正确', () => {
|
||||
const emptyBits = new Bits();
|
||||
expect(emptyBits.toString()).toBe('Bits[]');
|
||||
});
|
||||
|
||||
it('toBinaryString应该返回正确的二进制表示', () => {
|
||||
const binaryStr = bits.toBinaryString(8);
|
||||
expect(binaryStr).toBe('00010101');
|
||||
});
|
||||
|
||||
it('toBinaryString应该正确处理空格分隔', () => {
|
||||
const binaryStr = bits.toBinaryString(16);
|
||||
expect(binaryStr).toBe('00000000 00010101');
|
||||
});
|
||||
|
||||
it('toHexString应该返回正确的十六进制表示', () => {
|
||||
const hexStr = bits.toHexString();
|
||||
expect(hexStr).toBe('0x15'); // 21 in hex
|
||||
});
|
||||
|
||||
it('fromBinaryString应该正确解析', () => {
|
||||
const parsedBits = Bits.fromBinaryString('10101');
|
||||
expect(parsedBits.getValue().toString()).toBe('21');
|
||||
expect(parsedBits.equals(bits)).toBe(true);
|
||||
});
|
||||
|
||||
it('fromBinaryString应该处理带空格的字符串', () => {
|
||||
const parsedBits = Bits.fromBinaryString('0001 0101');
|
||||
expect(parsedBits.getValue().toString()).toBe('21');
|
||||
});
|
||||
|
||||
it('fromHexString应该正确解析', () => {
|
||||
const parsedBits = Bits.fromHexString('0x15');
|
||||
expect(parsedBits.getValue().toString()).toBe('21');
|
||||
expect(parsedBits.equals(bits)).toBe(true);
|
||||
});
|
||||
|
||||
it('fromHexString应该处理不带0x前缀的字符串', () => {
|
||||
const parsedBits = Bits.fromHexString('15');
|
||||
expect(parsedBits.getValue().toString()).toBe('21');
|
||||
});
|
||||
});
|
||||
|
||||
describe('比较操作', () => {
|
||||
let otherBits: Bits;
|
||||
|
||||
beforeEach(() => {
|
||||
bits.set(0);
|
||||
bits.set(2);
|
||||
|
||||
otherBits = new Bits();
|
||||
});
|
||||
|
||||
it('equals应该正确比较相等的Bits', () => {
|
||||
otherBits.set(0);
|
||||
otherBits.set(2);
|
||||
expect(bits.equals(otherBits)).toBe(true);
|
||||
});
|
||||
|
||||
it('equals应该正确比较不相等的Bits', () => {
|
||||
otherBits.set(0);
|
||||
otherBits.set(1);
|
||||
expect(bits.equals(otherBits)).toBe(false);
|
||||
});
|
||||
|
||||
it('空Bits对象应该相等', () => {
|
||||
const emptyBits1 = new Bits();
|
||||
const emptyBits2 = new Bits();
|
||||
expect(emptyBits1.equals(emptyBits2)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('索引查找操作', () => {
|
||||
it('getHighestBitIndex应该返回最高设置位的索引', () => {
|
||||
bits.set(0);
|
||||
bits.set(5);
|
||||
bits.set(10);
|
||||
expect(bits.getHighestBitIndex()).toBe(10);
|
||||
});
|
||||
|
||||
it('getLowestBitIndex应该返回最低设置位的索引', () => {
|
||||
bits.set(3);
|
||||
bits.set(7);
|
||||
bits.set(1);
|
||||
expect(bits.getLowestBitIndex()).toBe(1);
|
||||
});
|
||||
|
||||
it('空Bits的索引查找应该返回-1', () => {
|
||||
expect(bits.getHighestBitIndex()).toBe(-1);
|
||||
expect(bits.getLowestBitIndex()).toBe(-1);
|
||||
});
|
||||
|
||||
it('只有一个位设置时索引查找应该正确', () => {
|
||||
bits.set(5);
|
||||
expect(bits.getHighestBitIndex()).toBe(5);
|
||||
expect(bits.getLowestBitIndex()).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('大数值处理', () => {
|
||||
it('应该能够处理超过64位的数值', () => {
|
||||
bits.set(100);
|
||||
expect(bits.get(100)).toBe(true);
|
||||
expect(bits.cardinality()).toBe(1);
|
||||
});
|
||||
|
||||
it('应该能够处理非常大的位索引', () => {
|
||||
bits.set(1000);
|
||||
bits.set(2000);
|
||||
expect(bits.get(1000)).toBe(true);
|
||||
expect(bits.get(2000)).toBe(true);
|
||||
expect(bits.cardinality()).toBe(2);
|
||||
});
|
||||
|
||||
it('大数值的位运算应该正确', () => {
|
||||
const largeBits1 = new Bits();
|
||||
const largeBits2 = new Bits();
|
||||
|
||||
largeBits1.set(100);
|
||||
largeBits1.set(200);
|
||||
|
||||
largeBits2.set(100);
|
||||
largeBits2.set(150);
|
||||
|
||||
const result = largeBits1.and(largeBits2);
|
||||
expect(result.get(100)).toBe(true);
|
||||
expect(result.get(150)).toBe(false);
|
||||
expect(result.get(200)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('性能测试', () => {
|
||||
it('大量位设置操作应该高效', () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
bits.set(i);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
expect(endTime - startTime).toBeLessThan(1000); // 应该在1秒内完成
|
||||
expect(bits.cardinality()).toBe(10000);
|
||||
});
|
||||
|
||||
it('大量位查询操作应该高效', () => {
|
||||
// 先设置一些位
|
||||
for (let i = 0; i < 1000; i += 2) {
|
||||
bits.set(i);
|
||||
}
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
let trueCount = 0;
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
if (bits.get(i)) {
|
||||
trueCount++;
|
||||
}
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
expect(endTime - startTime).toBeLessThan(100); // 应该在100ms内完成
|
||||
expect(trueCount).toBe(500); // 500个偶数位
|
||||
});
|
||||
|
||||
it('位运算操作应该高效', () => {
|
||||
const otherBits = new Bits();
|
||||
|
||||
// 设置一些位
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
bits.set(i * 2);
|
||||
otherBits.set(i * 2 + 1);
|
||||
}
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
const result = bits.or(otherBits);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
expect(endTime - startTime).toBeLessThan(100); // 应该在100ms内完成
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界情况和错误处理', () => {
|
||||
it('应该处理0值的各种操作', () => {
|
||||
const zeroBits = new Bits(BigIntFactory.zero());
|
||||
expect(zeroBits.isEmpty()).toBe(true);
|
||||
expect(zeroBits.cardinality()).toBe(0);
|
||||
expect(zeroBits.getHighestBitIndex()).toBe(-1);
|
||||
expect(zeroBits.getLowestBitIndex()).toBe(-1);
|
||||
});
|
||||
|
||||
it('应该处理最大BigInt值', () => {
|
||||
const maxBits = new Bits(BigIntFactory.create(Number.MAX_SAFE_INTEGER));
|
||||
expect(maxBits.isEmpty()).toBe(false);
|
||||
expect(maxBits.cardinality()).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('位操作的结果应该是新对象', () => {
|
||||
bits.set(0);
|
||||
const otherBits = new Bits();
|
||||
otherBits.set(1);
|
||||
|
||||
const result = bits.or(otherBits);
|
||||
expect(result).not.toBe(bits);
|
||||
expect(result).not.toBe(otherBits);
|
||||
});
|
||||
|
||||
it('连续的设置和清除操作应该正确', () => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
bits.set(i);
|
||||
expect(bits.get(i)).toBe(true);
|
||||
bits.clear(i);
|
||||
expect(bits.get(i)).toBe(false);
|
||||
}
|
||||
|
||||
expect(bits.isEmpty()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
414
packages/core/tests/ECS/Utils/IdentifierPool.test.ts
Normal file
414
packages/core/tests/ECS/Utils/IdentifierPool.test.ts
Normal file
@@ -0,0 +1,414 @@
|
||||
/**
|
||||
* IdentifierPool 世代式ID池测试
|
||||
*
|
||||
* 测试实体ID的分配、回收、验证和世代版本控制功能
|
||||
*/
|
||||
import { IdentifierPool } from '../../../src/ECS/Utils/IdentifierPool';
|
||||
import { TestUtils } from '../../setup';
|
||||
|
||||
describe('IdentifierPool 世代式ID池测试', () => {
|
||||
let pool: IdentifierPool;
|
||||
|
||||
beforeEach(() => {
|
||||
pool = new IdentifierPool();
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
// 测试基本功能
|
||||
describe('基本功能测试', () => {
|
||||
test('应该能创建IdentifierPool实例', () => {
|
||||
expect(pool).toBeDefined();
|
||||
expect(pool).toBeInstanceOf(IdentifierPool);
|
||||
});
|
||||
|
||||
test('应该能分配连续的ID', () => {
|
||||
const id1 = pool.checkOut();
|
||||
const id2 = pool.checkOut();
|
||||
const id3 = pool.checkOut();
|
||||
|
||||
expect(id1).toBe(65536); // 世代1,索引0
|
||||
expect(id2).toBe(65537); // 世代1,索引1
|
||||
expect(id3).toBe(65538); // 世代1,索引2
|
||||
});
|
||||
|
||||
test('应该能验证有效的ID', () => {
|
||||
const id = pool.checkOut();
|
||||
expect(pool.isValid(id)).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能获取统计信息', () => {
|
||||
const id1 = pool.checkOut();
|
||||
const id2 = pool.checkOut();
|
||||
|
||||
const stats = pool.getStats();
|
||||
expect(stats.totalAllocated).toBe(2);
|
||||
expect(stats.currentActive).toBe(2);
|
||||
expect(stats.currentlyFree).toBe(0);
|
||||
expect(stats.pendingRecycle).toBe(0);
|
||||
expect(stats.maxPossibleEntities).toBe(65536); // 2^16
|
||||
expect(stats.averageGeneration).toBe(1);
|
||||
expect(stats.memoryUsage).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试回收功能
|
||||
describe('ID回收功能测试', () => {
|
||||
test('应该能回收有效的ID', () => {
|
||||
const id = pool.checkOut();
|
||||
const result = pool.checkIn(id);
|
||||
|
||||
expect(result).toBe(true);
|
||||
|
||||
const stats = pool.getStats();
|
||||
expect(stats.pendingRecycle).toBe(1);
|
||||
expect(stats.currentActive).toBe(0);
|
||||
});
|
||||
|
||||
test('应该拒绝回收无效的ID', () => {
|
||||
const invalidId = 999999;
|
||||
const result = pool.checkIn(invalidId);
|
||||
|
||||
expect(result).toBe(false);
|
||||
|
||||
const stats = pool.getStats();
|
||||
expect(stats.pendingRecycle).toBe(0);
|
||||
});
|
||||
|
||||
test('应该拒绝重复回收同一个ID', () => {
|
||||
const id = pool.checkOut();
|
||||
|
||||
const firstResult = pool.checkIn(id);
|
||||
const secondResult = pool.checkIn(id);
|
||||
|
||||
expect(firstResult).toBe(true);
|
||||
expect(secondResult).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试延迟回收
|
||||
describe('延迟回收机制测试', () => {
|
||||
test('应该支持延迟回收', () => {
|
||||
const pool = new IdentifierPool(100); // 100ms延迟
|
||||
|
||||
const id = pool.checkOut();
|
||||
pool.checkIn(id);
|
||||
|
||||
// 立即检查,ID应该还在延迟队列中
|
||||
let stats = pool.getStats();
|
||||
expect(stats.pendingRecycle).toBe(1);
|
||||
expect(stats.currentlyFree).toBe(0);
|
||||
|
||||
// 模拟时间前进150ms
|
||||
jest.advanceTimersByTime(150);
|
||||
|
||||
// 触发延迟回收处理(通过分配新ID)
|
||||
pool.checkOut();
|
||||
|
||||
// 现在ID应该被真正回收了
|
||||
stats = pool.getStats();
|
||||
expect(stats.pendingRecycle).toBe(0);
|
||||
expect(stats.currentlyFree).toBe(0); // 因为被重新分配了
|
||||
});
|
||||
|
||||
test('延迟时间内ID应该仍然有效', () => {
|
||||
const pool = new IdentifierPool(100);
|
||||
|
||||
const id = pool.checkOut();
|
||||
pool.checkIn(id);
|
||||
|
||||
// 在延迟时间内,ID应该仍然有效
|
||||
expect(pool.isValid(id)).toBe(true);
|
||||
|
||||
// 模拟时间前进150ms并触发处理
|
||||
jest.advanceTimersByTime(150);
|
||||
pool.checkOut(); // 触发延迟回收处理
|
||||
|
||||
// 现在ID应该无效了(世代已递增)
|
||||
expect(pool.isValid(id)).toBe(false);
|
||||
});
|
||||
|
||||
test('应该支持强制延迟回收处理', () => {
|
||||
const id = pool.checkOut();
|
||||
pool.checkIn(id);
|
||||
|
||||
// 在延迟时间内强制处理
|
||||
pool.forceProcessDelayedRecycle();
|
||||
|
||||
// ID应该立即变为无效
|
||||
expect(pool.isValid(id)).toBe(false);
|
||||
|
||||
const stats = pool.getStats();
|
||||
expect(stats.pendingRecycle).toBe(0);
|
||||
expect(stats.currentlyFree).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试世代版本控制
|
||||
describe('世代版本控制测试', () => {
|
||||
test('回收后的ID应该增加世代版本', () => {
|
||||
const pool = new IdentifierPool(0); // 无延迟,立即回收
|
||||
|
||||
const originalId = pool.checkOut();
|
||||
pool.checkIn(originalId);
|
||||
|
||||
// 分配新ID触发回收处理
|
||||
const newId = pool.checkOut();
|
||||
|
||||
// 原ID应该无效
|
||||
expect(pool.isValid(originalId)).toBe(false);
|
||||
|
||||
// 新ID应该有不同的世代版本
|
||||
expect(newId).not.toBe(originalId);
|
||||
expect(newId).toBe(131072); // 世代2,索引0
|
||||
});
|
||||
|
||||
test('应该能重用回收的索引', () => {
|
||||
const pool = new IdentifierPool(0);
|
||||
|
||||
const id1 = pool.checkOut(); // 索引0
|
||||
const id2 = pool.checkOut(); // 索引1
|
||||
|
||||
pool.checkIn(id1);
|
||||
|
||||
const id3 = pool.checkOut(); // 应该重用索引0,但世代递增
|
||||
|
||||
expect(id3 & 0xFFFF).toBe(0); // 索引部分应该是0
|
||||
expect(id3 >> 16).toBe(2); // 世代应该是2
|
||||
});
|
||||
|
||||
test('世代版本溢出应该重置为1', () => {
|
||||
const pool = new IdentifierPool(0);
|
||||
|
||||
// 手动设置一个即将溢出的世代
|
||||
const id = pool.checkOut();
|
||||
|
||||
// 通过反射访问私有成员来模拟溢出情况
|
||||
const generations = (pool as any)._generations;
|
||||
generations.set(0, 65535); // 设置为最大值
|
||||
|
||||
pool.checkIn(id);
|
||||
const newId = pool.checkOut();
|
||||
|
||||
// 世代应该重置为1而不是0
|
||||
expect(newId >> 16).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试错误处理
|
||||
describe('错误处理测试', () => {
|
||||
test('超过最大索引数应该抛出错误', () => {
|
||||
// 创建一个模拟的池,直接设置到达到限制
|
||||
const pool = new IdentifierPool();
|
||||
|
||||
// 通过反射设置到达到限制(65536会触发错误)
|
||||
(pool as any)._nextAvailableIndex = 65536;
|
||||
|
||||
expect(() => {
|
||||
pool.checkOut();
|
||||
}).toThrow('实体索引已达到框架设计限制');
|
||||
});
|
||||
|
||||
test('应该能处理边界值', () => {
|
||||
const pool = new IdentifierPool();
|
||||
|
||||
const id = pool.checkOut();
|
||||
expect(id).toBe(65536); // 世代1,索引0
|
||||
|
||||
// 回收并重新分配
|
||||
pool.checkIn(id);
|
||||
|
||||
jest.advanceTimersByTime(200);
|
||||
const newId = pool.checkOut();
|
||||
|
||||
expect(newId).toBe(131072); // 世代2,索引0
|
||||
});
|
||||
});
|
||||
|
||||
// 测试动态扩展
|
||||
describe('动态内存扩展测试', () => {
|
||||
test('应该能动态扩展内存', () => {
|
||||
const pool = new IdentifierPool(0, 10); // 小的扩展块用于测试
|
||||
|
||||
// 分配超过初始块大小的ID
|
||||
const ids: number[] = [];
|
||||
for (let i = 0; i < 25; i++) {
|
||||
ids.push(pool.checkOut());
|
||||
}
|
||||
|
||||
expect(ids.length).toBe(25);
|
||||
|
||||
// 验证所有ID都是唯一的
|
||||
const uniqueIds = new Set(ids);
|
||||
expect(uniqueIds.size).toBe(25);
|
||||
|
||||
// 检查内存扩展统计
|
||||
const stats = pool.getStats();
|
||||
expect(stats.memoryExpansions).toBeGreaterThan(1);
|
||||
expect(stats.generationStorageSize).toBeGreaterThanOrEqual(25);
|
||||
});
|
||||
|
||||
test('内存扩展应该按块进行', () => {
|
||||
const blockSize = 5;
|
||||
const pool = new IdentifierPool(0, blockSize);
|
||||
|
||||
// 分配第一个块
|
||||
for (let i = 0; i < blockSize; i++) {
|
||||
pool.checkOut();
|
||||
}
|
||||
|
||||
let stats = pool.getStats();
|
||||
const initialExpansions = stats.memoryExpansions;
|
||||
|
||||
// 分配一个会触发新块的ID
|
||||
pool.checkOut();
|
||||
|
||||
stats = pool.getStats();
|
||||
expect(stats.memoryExpansions).toBe(initialExpansions + 1);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试性能和内存
|
||||
describe('性能和内存测试', () => {
|
||||
test('应该能处理大量ID分配', () => {
|
||||
const count = 10000; // 增加测试规模
|
||||
const ids: number[] = [];
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
ids.push(pool.checkOut());
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
expect(ids.length).toBe(count);
|
||||
expect(duration).toBeLessThan(1000); // 10k个ID应该在1秒内完成
|
||||
|
||||
// 验证所有ID都是唯一的
|
||||
const uniqueIds = new Set(ids);
|
||||
expect(uniqueIds.size).toBe(count);
|
||||
});
|
||||
|
||||
test('应该能处理大量回收操作', () => {
|
||||
const count = 5000; // 增加测试规模
|
||||
const ids: number[] = [];
|
||||
|
||||
// 分配ID
|
||||
for (let i = 0; i < count; i++) {
|
||||
ids.push(pool.checkOut());
|
||||
}
|
||||
|
||||
// 回收ID
|
||||
const startTime = performance.now();
|
||||
|
||||
for (const id of ids) {
|
||||
pool.checkIn(id);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
expect(duration).toBeLessThan(500); // 5k个回收应该在500ms内完成
|
||||
|
||||
const stats = pool.getStats();
|
||||
expect(stats.pendingRecycle).toBe(count);
|
||||
expect(stats.currentActive).toBe(0);
|
||||
});
|
||||
|
||||
test('内存使用应该是合理的', () => {
|
||||
const stats = pool.getStats();
|
||||
const initialMemory = stats.memoryUsage;
|
||||
|
||||
// 分配大量ID
|
||||
for (let i = 0; i < 5000; i++) {
|
||||
pool.checkOut();
|
||||
}
|
||||
|
||||
const newStats = pool.getStats();
|
||||
const memoryIncrease = newStats.memoryUsage - initialMemory;
|
||||
|
||||
// 内存增长应该是合理的(动态分配应该更高效)
|
||||
expect(memoryIncrease).toBeLessThan(5000 * 50); // 每个ID少于50字节
|
||||
});
|
||||
});
|
||||
|
||||
// 测试并发安全性(模拟)
|
||||
describe('并发安全性测试', () => {
|
||||
test('应该能处理并发分配', async () => {
|
||||
const promises: Promise<number>[] = [];
|
||||
|
||||
// 模拟并发分配
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
promises.push(Promise.resolve(pool.checkOut()));
|
||||
}
|
||||
|
||||
const ids = await Promise.all(promises);
|
||||
|
||||
// 所有ID应该是唯一的
|
||||
const uniqueIds = new Set(ids);
|
||||
expect(uniqueIds.size).toBe(1000);
|
||||
});
|
||||
|
||||
test('应该能处理并发回收', async () => {
|
||||
const ids: number[] = [];
|
||||
|
||||
// 先分配一些ID
|
||||
for (let i = 0; i < 500; i++) {
|
||||
ids.push(pool.checkOut());
|
||||
}
|
||||
|
||||
// 模拟并发回收
|
||||
const promises = ids.map(id => Promise.resolve(pool.checkIn(id)));
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
// 所有回收操作都应该成功
|
||||
expect(results.every(result => result === true)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试统计信息
|
||||
describe('统计信息测试', () => {
|
||||
test('统计信息应该准确反映池状态', () => {
|
||||
// 分配一些ID
|
||||
const ids = [pool.checkOut(), pool.checkOut(), pool.checkOut()];
|
||||
|
||||
let stats = pool.getStats();
|
||||
expect(stats.totalAllocated).toBe(3);
|
||||
expect(stats.currentActive).toBe(3);
|
||||
expect(stats.currentlyFree).toBe(0);
|
||||
expect(stats.pendingRecycle).toBe(0);
|
||||
|
||||
// 回收一个ID
|
||||
pool.checkIn(ids[0]);
|
||||
|
||||
stats = pool.getStats();
|
||||
expect(stats.totalRecycled).toBe(1);
|
||||
expect(stats.currentActive).toBe(2);
|
||||
expect(stats.pendingRecycle).toBe(1);
|
||||
|
||||
// 强制处理延迟回收
|
||||
pool.forceProcessDelayedRecycle();
|
||||
|
||||
stats = pool.getStats();
|
||||
expect(stats.pendingRecycle).toBe(0);
|
||||
expect(stats.currentlyFree).toBe(1);
|
||||
});
|
||||
|
||||
test('应该正确计算平均世代版本', () => {
|
||||
const pool = new IdentifierPool(0); // 无延迟
|
||||
|
||||
// 分配、回收、再分配来增加世代
|
||||
const id1 = pool.checkOut();
|
||||
pool.checkIn(id1);
|
||||
const id2 = pool.checkOut(); // 这会触发世代递增
|
||||
|
||||
const stats = pool.getStats();
|
||||
expect(stats.averageGeneration).toBeGreaterThan(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
288
packages/core/tests/ECS/Utils/Matcher.test.ts
Normal file
288
packages/core/tests/ECS/Utils/Matcher.test.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* Matcher完整测试套件
|
||||
* 测试新的Matcher条件构建功能和QuerySystem集成
|
||||
*/
|
||||
|
||||
import { Scene } from '../../../src/ECS/Scene';
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { Matcher } from '../../../src/ECS/Utils/Matcher';
|
||||
|
||||
// 测试组件
|
||||
class Position extends Component {
|
||||
public x: number = 0;
|
||||
public y: number = 0;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [x = 0, y = 0] = args as [number?, number?];
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
class Velocity extends Component {
|
||||
public vx: number = 0;
|
||||
public vy: number = 0;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [vx = 0, vy = 0] = args as [number?, number?];
|
||||
this.vx = vx;
|
||||
this.vy = vy;
|
||||
}
|
||||
}
|
||||
|
||||
class Health extends Component {
|
||||
public hp: number = 100;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [hp = 100] = args as [number?];
|
||||
this.hp = hp;
|
||||
}
|
||||
}
|
||||
|
||||
class Dead extends Component {}
|
||||
|
||||
describe('Matcher测试套件', () => {
|
||||
let scene: Scene;
|
||||
let entities: Entity[];
|
||||
|
||||
beforeEach(() => {
|
||||
scene = new Scene();
|
||||
scene.begin();
|
||||
|
||||
// 创建测试实体
|
||||
entities = [];
|
||||
|
||||
// 实体1: 移动的活体
|
||||
const entity1 = scene.createEntity('MovingAlive');
|
||||
entity1.addComponent(new Position(10, 20));
|
||||
entity1.addComponent(new Velocity(1, 0));
|
||||
entity1.addComponent(new Health(100));
|
||||
entities.push(entity1);
|
||||
|
||||
// 实体2: 静止的活体
|
||||
const entity2 = scene.createEntity('StillAlive');
|
||||
entity2.addComponent(new Position(30, 40));
|
||||
entity2.addComponent(new Health(50));
|
||||
entities.push(entity2);
|
||||
|
||||
// 实体3: 移动的死体
|
||||
const entity3 = scene.createEntity('MovingDead');
|
||||
entity3.addComponent(new Position(50, 60));
|
||||
entity3.addComponent(new Velocity(0, 1));
|
||||
entity3.addComponent(new Dead());
|
||||
entities.push(entity3);
|
||||
|
||||
// 实体4: 静止的死体
|
||||
const entity4 = scene.createEntity('StillDead');
|
||||
entity4.addComponent(new Position(70, 80));
|
||||
entity4.addComponent(new Dead());
|
||||
entities.push(entity4);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
scene.end();
|
||||
});
|
||||
|
||||
describe('Matcher条件构建测试', () => {
|
||||
test('Matcher.all()应该创建正确的查询条件', () => {
|
||||
const matcher = Matcher.all(Position, Health);
|
||||
const condition = matcher.getCondition();
|
||||
|
||||
expect(condition.all).toContain(Position);
|
||||
expect(condition.all).toContain(Health);
|
||||
expect(condition.all.length).toBe(2);
|
||||
});
|
||||
|
||||
test('Matcher.any()应该创建正确的查询条件', () => {
|
||||
const matcher = Matcher.any(Health, Dead);
|
||||
const condition = matcher.getCondition();
|
||||
|
||||
expect(condition.any).toContain(Health);
|
||||
expect(condition.any).toContain(Dead);
|
||||
expect(condition.any.length).toBe(2);
|
||||
});
|
||||
|
||||
test('Matcher.none()应该创建正确的查询条件', () => {
|
||||
const matcher = Matcher.none(Dead);
|
||||
const condition = matcher.getCondition();
|
||||
|
||||
expect(condition.none).toContain(Dead);
|
||||
expect(condition.none.length).toBe(1);
|
||||
});
|
||||
|
||||
test('链式调用应该正确工作', () => {
|
||||
const matcher = Matcher.all(Position)
|
||||
.any(Health, Velocity)
|
||||
.none(Dead);
|
||||
|
||||
const condition = matcher.getCondition();
|
||||
expect(condition.all).toContain(Position);
|
||||
expect(condition.any).toContain(Health);
|
||||
expect(condition.any).toContain(Velocity);
|
||||
expect(condition.none).toContain(Dead);
|
||||
});
|
||||
|
||||
test('byComponent()应该创建单组件查询条件', () => {
|
||||
const matcher = Matcher.byComponent(Position);
|
||||
const condition = matcher.getCondition();
|
||||
|
||||
expect(condition.component).toBe(Position);
|
||||
});
|
||||
|
||||
test('byTag()应该创建标签查询条件', () => {
|
||||
const matcher = Matcher.byTag(123);
|
||||
const condition = matcher.getCondition();
|
||||
|
||||
expect(condition.tag).toBe(123);
|
||||
});
|
||||
|
||||
test('byName()应该创建名称查询条件', () => {
|
||||
const matcher = Matcher.byName('TestEntity');
|
||||
const condition = matcher.getCondition();
|
||||
|
||||
expect(condition.name).toBe('TestEntity');
|
||||
});
|
||||
});
|
||||
|
||||
describe('QuerySystem集成测试', () => {
|
||||
test('使用QuerySystem的queryAll()查询所有匹配实体', () => {
|
||||
const result = scene.querySystem.queryAll(Position, Health);
|
||||
expect(result.entities.map(e => e.name).sort()).toEqual(['MovingAlive', 'StillAlive']);
|
||||
});
|
||||
|
||||
test('使用QuerySystem的queryAny()查询任一匹配实体', () => {
|
||||
const result = scene.querySystem.queryAny(Health, Dead);
|
||||
expect(result.entities.length).toBe(4); // 所有实体都有Health或Dead
|
||||
});
|
||||
|
||||
test('使用QuerySystem的queryNone()查询排除实体', () => {
|
||||
const result = scene.querySystem.queryNone(Dead);
|
||||
const aliveEntities = result.entities.filter(e => e.hasComponent(Position));
|
||||
expect(aliveEntities.map(e => e.name).sort()).toEqual(['MovingAlive', 'StillAlive']);
|
||||
});
|
||||
|
||||
test('QuerySystem查询性能统计', () => {
|
||||
scene.querySystem.queryAll(Position, Velocity);
|
||||
const stats = scene.querySystem.getStats();
|
||||
|
||||
expect(stats.queryStats.totalQueries).toBeGreaterThan(0);
|
||||
expect(stats.queryStats.cacheHits).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('实际使用场景测试', () => {
|
||||
test('游戏系统中的移动实体查询', () => {
|
||||
// 查询所有可移动的实体(有位置和速度的)
|
||||
const movableEntities = scene.querySystem.queryAll(Position, Velocity);
|
||||
expect(movableEntities.entities.length).toBe(2); // MovingAlive, MovingDead
|
||||
|
||||
movableEntities.entities.forEach(entity => {
|
||||
const pos = entity.getComponent(Position)!;
|
||||
const vel = entity.getComponent(Velocity)!;
|
||||
expect(pos).toBeDefined();
|
||||
expect(vel).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
test('游戏系统中的活体实体查询', () => {
|
||||
// 查询所有活体实体(有血量,没有死亡标记的)
|
||||
const aliveEntitiesAll = scene.querySystem.queryAll(Health);
|
||||
const deadEntitiesAll = scene.querySystem.queryAll(Dead);
|
||||
|
||||
expect(aliveEntitiesAll.entities.length).toBe(2); // MovingAlive, StillAlive
|
||||
expect(deadEntitiesAll.entities.length).toBe(2); // MovingDead, StillDead
|
||||
});
|
||||
|
||||
test('复杂查询:查找活着的移动实体', () => {
|
||||
// 首先获取所有有位置和速度的实体
|
||||
const movableEntities = scene.querySystem.queryAll(Position, Velocity);
|
||||
|
||||
// 然后过滤出活着的(有血量的)
|
||||
const aliveMovableEntities = movableEntities.entities.filter(entity =>
|
||||
entity.hasComponent(Health)
|
||||
);
|
||||
|
||||
expect(aliveMovableEntities.length).toBe(1); // 只有MovingAlive
|
||||
expect(aliveMovableEntities[0].name).toBe('MovingAlive');
|
||||
});
|
||||
|
||||
test('复合查询条件应用', () => {
|
||||
// 使用Matcher建立复杂条件,然后用QuerySystem执行
|
||||
const matcher = Matcher.all(Position).any(Health, Dead);
|
||||
const condition = matcher.getCondition();
|
||||
|
||||
// 这里演示如何用条件,实际执行需要QuerySystem支持复合条件
|
||||
expect(condition.all).toContain(Position);
|
||||
expect(condition.any).toContain(Health);
|
||||
expect(condition.any).toContain(Dead);
|
||||
});
|
||||
});
|
||||
|
||||
describe('性能测试', () => {
|
||||
test('大量简单查询的性能', () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
scene.querySystem.queryAll(Position);
|
||||
}
|
||||
|
||||
const executionTime = performance.now() - startTime;
|
||||
expect(executionTime).toBeLessThan(100); // 应该在100ms内完成
|
||||
});
|
||||
|
||||
test('复杂查询的性能', () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
scene.querySystem.queryAll(Position, Health);
|
||||
scene.querySystem.queryAny(Health, Dead);
|
||||
scene.querySystem.queryNone(Dead);
|
||||
}
|
||||
|
||||
const executionTime = performance.now() - startTime;
|
||||
expect(executionTime).toBeLessThan(50);
|
||||
});
|
||||
|
||||
test('不存在组件的查询性能', () => {
|
||||
class NonExistentComponent extends Component {
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
const result = scene.querySystem.queryAll(NonExistentComponent);
|
||||
expect(result.entities.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界情况测试', () => {
|
||||
test('空查询应该返回所有实体', () => {
|
||||
const result = scene.querySystem.queryAll();
|
||||
expect(result.entities.length).toBe(entities.length);
|
||||
});
|
||||
|
||||
test('查询不存在的组件应该返回空结果', () => {
|
||||
class NonExistentComponent extends Component {
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
const result = scene.querySystem.queryAll(NonExistentComponent);
|
||||
expect(result.entities.length).toBe(0);
|
||||
});
|
||||
|
||||
test('Matcher条件构建的边界情况', () => {
|
||||
const emptyMatcher = Matcher.complex();
|
||||
const condition = emptyMatcher.getCondition();
|
||||
|
||||
expect(condition.all.length).toBe(0);
|
||||
expect(condition.any.length).toBe(0);
|
||||
expect(condition.none.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user