feat(core): 启用 TypeScript 最严格的类型检查 (#199)

* feat(core):  启用 TypeScript 最严格的类型检查

* ci: 配置 Codecov 以适应类型安全改进

* fix(core): 修复 CodeQL 安全警告

* fix(core): eslint.config.mjs
This commit is contained in:
YHH
2025-10-31 16:14:23 +08:00
committed by GitHub
parent 011d795361
commit c58e3411fd
56 changed files with 622 additions and 495 deletions

View File

@@ -25,7 +25,7 @@ export interface BitMask64Data {
export class BitMask64Utils {
/** 零掩码常量所有位都为0 */
public static readonly ZERO: Readonly<BitMask64Data> = { base: [0, 0], segments: undefined };
public static readonly ZERO: Readonly<BitMask64Data> = { base: [0, 0] };
/**
* 根据位索引创建64位掩码
@@ -37,7 +37,7 @@ export class BitMask64Utils {
if (bitIndex < 0) {
throw new Error(`Bit index ${bitIndex} out of range [0, ∞)`);
}
const mask: BitMask64Data = { base: [0, 0], segments: undefined };
const mask: BitMask64Data = { base: [0, 0] };
BitMask64Utils.setBit(mask, bitIndex);
return mask;
}
@@ -48,7 +48,7 @@ export class BitMask64Utils {
* @returns 低32位为输入值、高32位为0的掩码
*/
public static fromNumber(value: number): BitMask64Data {
return { base: [value >>> 0, 0], segments: undefined};
return { base: [value >>> 0, 0] };
}
/**
@@ -66,7 +66,10 @@ export class BitMask64Utils {
// 基础区段就包含指定的位,或任意一个参数不含扩展区段,直接短路
if(baseHasAny || !bitsSegments || !maskSegments) return baseHasAny;
// 额外检查扩展区域是否包含指定的位 - 如果bitsSegments[index]不存在会被转为NaNNaN的位运算始终返回0
return maskSegments.some((seg, index) => (seg[SegmentPart.LOW] & bitsSegments[index][SegmentPart.LOW]) !== 0 || (seg[SegmentPart.HIGH] & bitsSegments[index][SegmentPart.HIGH]) !== 0)
return maskSegments.some((seg, index) => {
const bitsSeg = bitsSegments[index];
return bitsSeg && ((seg[SegmentPart.LOW] & bitsSeg[SegmentPart.LOW]) !== 0 || (seg[SegmentPart.HIGH] & bitsSeg[SegmentPart.HIGH]) !== 0);
})
}
/**
@@ -89,7 +92,9 @@ export class BitMask64Utils {
// 对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]){
const maskSeg = maskSegments[i]!;
const bitsSeg = bitsSegments[i]!;
if((maskSeg[SegmentPart.LOW] & bitsSeg[SegmentPart.LOW]) !== bitsSeg[SegmentPart.LOW] || (maskSeg[SegmentPart.HIGH] & bitsSeg[SegmentPart.HIGH]) !== bitsSeg[SegmentPart.HIGH]){
// 存在不匹配的位,直接短路
return false;
}
@@ -97,7 +102,8 @@ export class BitMask64Utils {
}
// 对mask中不存在但bits中存在的区段进行isZero判断
for (let i = maskSegmentsLength; i < bitsSegments.length; i++) {
if(bitsSegments[i][SegmentPart.LOW] !== 0 || bitsSegments[i][SegmentPart.HIGH] !== 0){
const bitsSeg = bitsSegments[i]!;
if(bitsSeg[SegmentPart.LOW] !== 0 || bitsSeg[SegmentPart.HIGH] !== 0){
// 存在不为0的区段直接短路
return false;
}
@@ -120,7 +126,11 @@ export class BitMask64Utils {
//不含扩展区域或基础区域就包含指定的位或bits不含拓展区段直接短路。
if(!maskSegments || !baseHasNone || !bitsSegments) return baseHasNone;
// 额外检查扩展区域是否都包含指定的位 - 此时bitsSegments存在,如果bitsSegments[index]不存在会被转为NaNNaN的位运算始终返回0
return maskSegments.every((seg, index) => (seg[SegmentPart.LOW] & bitsSegments[index][SegmentPart.LOW]) === 0 && (seg[SegmentPart.HIGH] & bitsSegments[index][SegmentPart.HIGH]) === 0);
return maskSegments.every((seg, index) => {
const bitsSeg = bitsSegments[index];
if (!bitsSeg) return true;
return (seg[SegmentPart.LOW] & bitsSeg[SegmentPart.LOW]) === 0 && (seg[SegmentPart.HIGH] & bitsSeg[SegmentPart.HIGH]) === 0;
});
}
/**
@@ -160,7 +170,7 @@ export class BitMask64Utils {
}else if(!aSeg && bSeg){
//aSeg不存在则必须要求bSeg全为0
if(bSeg[SegmentPart.LOW] !== 0 || bSeg[SegmentPart.HIGH] !== 0) return false;
}else{
}else if(aSeg && bSeg){
//理想状态aSeg/bSeg都存在
if(aSeg[SegmentPart.LOW] !== bSeg[SegmentPart.LOW] || aSeg[SegmentPart.HIGH] !== bSeg[SegmentPart.HIGH]) return false;
}
@@ -248,8 +258,10 @@ export class BitMask64Utils {
// 对每个段执行或操作
for (let i = 0; i < otherSegments.length; i++) {
targetSegments[i][SegmentPart.LOW] |= otherSegments[i][SegmentPart.LOW];
targetSegments[i][SegmentPart.HIGH] |= otherSegments[i][SegmentPart.HIGH];
const targetSeg = targetSegments[i]!;
const otherSeg = otherSegments[i]!;
targetSeg[SegmentPart.LOW] |= otherSeg[SegmentPart.LOW];
targetSeg[SegmentPart.HIGH] |= otherSeg[SegmentPart.HIGH];
}
}
}
@@ -277,8 +289,10 @@ export class BitMask64Utils {
// 对每个段执行与操作
for (let i = 0; i < otherSegments.length; i++) {
targetSegments[i][SegmentPart.LOW] &= otherSegments[i][SegmentPart.LOW];
targetSegments[i][SegmentPart.HIGH] &= otherSegments[i][SegmentPart.HIGH];
const targetSeg = targetSegments[i]!;
const otherSeg = otherSegments[i]!;
targetSeg[SegmentPart.LOW] &= otherSeg[SegmentPart.LOW];
targetSeg[SegmentPart.HIGH] &= otherSeg[SegmentPart.HIGH];
}
}
}
@@ -305,8 +319,10 @@ export class BitMask64Utils {
// 对每个段执行异或操作
for (let i = 0; i < otherSegments.length; i++) {
targetSegments[i][SegmentPart.LOW] ^= otherSegments[i][SegmentPart.LOW];
targetSegments[i][SegmentPart.HIGH] ^= otherSegments[i][SegmentPart.HIGH];
const targetSeg = targetSegments[i]!;
const otherSeg = otherSegments[i]!;
targetSeg[SegmentPart.LOW] ^= otherSeg[SegmentPart.LOW];
targetSeg[SegmentPart.HIGH] ^= otherSeg[SegmentPart.HIGH];
}
}
@@ -317,10 +333,12 @@ export class BitMask64Utils {
public static clear(mask: BitMask64Data): void {
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;
if (mask.segments) {
for (let i = 0; i < mask.segments.length; i++) {
const seg = mask.segments[i]!;
seg[SegmentPart.LOW] = 0;
seg[SegmentPart.HIGH] = 0;
}
}
}
@@ -346,9 +364,11 @@ export class BitMask64Utils {
target.segments.push([0,0]);
}
// 逐个重写
for (let i = 0; i < length; i++) {
const targetSeg = target.segments![i];
const sourSeg = source.segments![i];
const targetSegments = target.segments;
const sourceSegments = source.segments;
for (let i = 0; i < sourceSegments.length; i++) {
const targetSeg = targetSegments[i]!;
const sourSeg = sourceSegments[i]!;
targetSeg[SegmentPart.LOW] = sourSeg[SegmentPart.LOW];
targetSeg[SegmentPart.HIGH] = sourSeg[SegmentPart.HIGH];
}
@@ -362,7 +382,7 @@ export class BitMask64Utils {
public static clone(mask: BitMask64Data): BitMask64Data {
return {
base: mask.base.slice() as BitMask64Segment,
segments: mask.segments ? mask.segments.map(seg => [...seg]) : undefined
...(mask.segments && { segments: mask.segments.map(seg => [...seg] as BitMask64Segment) })
};
}
@@ -393,7 +413,7 @@ export class BitMask64Utils {
for (let i = -1; i < totalLength; i++) {
let segResult = '';
const bitMaskData = i == -1 ? mask.base : mask.segments![i];
const bitMaskData = i == -1 ? mask.base : mask.segments![i]!;
let hi = bitMaskData[SegmentPart.HIGH];
let lo = bitMaskData[SegmentPart.LOW];
if(radix == 2){
@@ -429,7 +449,7 @@ export class BitMask64Utils {
public static popCount(mask: BitMask64Data): number {
let count = 0;
for (let i = -1; i < (mask.segments?.length ?? 0); i++) {
const bitMaskData = i == -1 ? mask.base : mask.segments![i];
const bitMaskData = i == -1 ? mask.base : mask.segments![i]!;
let lo = bitMaskData[SegmentPart.LOW];
let hi = bitMaskData[SegmentPart.HIGH];
while (lo) {
@@ -470,7 +490,7 @@ export class BitMask64Utils {
segments.push([0, 0]);
}
}
return segments[targetSegIndex];
return segments[targetSegIndex] ?? null;
}
}
}

View File

@@ -73,8 +73,8 @@ export class BitMaskHashMap<T> {
// 查找是否存在 secondaryHash
for (let i = 0; i < bucket.length; i++) {
if (bucket[i][0] === secondary) {
bucket[i][1] = value;
if (bucket[i]![0] === secondary) {
bucket[i]![1] = value;
return this;
}
}
@@ -90,8 +90,8 @@ export class BitMaskHashMap<T> {
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];
if (bucket[i]![0] === secondary) {
return bucket[i]![1];
}
}
return undefined;
@@ -106,7 +106,7 @@ export class BitMaskHashMap<T> {
const bucket = this.buckets.get(primary);
if (!bucket) return false;
for (let i = 0; i < bucket.length; i++) {
if (bucket[i][0] === secondary) {
if (bucket[i]![0] === secondary) {
bucket.splice(i, 1);
this._size--;
if (bucket.length === 0) {
@@ -125,7 +125,7 @@ export class BitMaskHashMap<T> {
*entries(): IterableIterator<[BitMask64Data, T]> {
for (const [_, bucket] of this.buckets) {
for (const [secondary, value] of bucket) {
for (const [_secondary, value] of bucket) {
// 无法还原原始 key只存二级 hash所以 entries 返回不了 key
yield [undefined as any, value];
}

View File

@@ -265,16 +265,16 @@ export class Bits {
*/
public static fromBinaryString(binaryString: string): Bits {
const cleanString = binaryString.replace(/\s/g, '');
let data: BitMask64Data = { base: undefined!, segments: undefined};
let data: BitMask64Data;
if (cleanString.length <= 32) {
const num = parseInt(cleanString, 2);
data.base = [num >>> 0, 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.base = [lo >>> 0, 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 = { base: undefined!, segments: undefined};
let data: BitMask64Data;
if (cleanString.length <= 8) {
const num = parseInt(cleanString, 16);
data.base = [num >>> 0, 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.base = [lo >>> 0, hi >>> 0];
data = { base: [lo >>> 0, hi >>> 0] };
}
return new Bits(data);
}

View File

@@ -11,7 +11,7 @@ import { IPoolable } from '../../Utils/Pool/IPoolable';
* 实现IPoolable接口支持对象池复用以减少内存分配开销。
*/
class PoolableEntitySet extends Set<Entity> implements IPoolable {
constructor(...args: unknown[]) {
constructor(..._args: unknown[]) {
super();
}
@@ -135,7 +135,7 @@ export class ComponentSparseSet {
const lastIndex = this._componentMasks.length - 1;
if (entityIndex !== lastIndex) {
// 将最后一个位掩码移动到当前位置
this._componentMasks[entityIndex] = this._componentMasks[lastIndex];
this._componentMasks[entityIndex] = this._componentMasks[lastIndex]!;
}
this._componentMasks.pop();
}
@@ -165,7 +165,7 @@ export class ComponentSparseSet {
}
if (componentTypes.length === 1) {
return this.queryByComponent(componentTypes[0]);
return this.queryByComponent(componentTypes[0]!);
}
// 构建目标位掩码
@@ -182,7 +182,7 @@ export class ComponentSparseSet {
// 遍历所有实体,检查位掩码匹配
this._entities.forEach((entity, index) => {
const entityMask = this._componentMasks[index];
const entityMask = this._componentMasks[index]!;
if (BitMask64Utils.hasAll(entityMask, targetMask)) {
result.add(entity);
}
@@ -205,7 +205,7 @@ export class ComponentSparseSet {
}
if (componentTypes.length === 1) {
return this.queryByComponent(componentTypes[0]);
return this.queryByComponent(componentTypes[0]!);
}
// 构建目标位掩码
@@ -225,7 +225,7 @@ export class ComponentSparseSet {
// 遍历所有实体,检查位掩码匹配
this._entities.forEach((entity, index) => {
const entityMask = this._componentMasks[index];
const entityMask = this._componentMasks[index]!;
if (BitMask64Utils.hasAny(entityMask, targetMask)) {
result.add(entity);
}
@@ -251,9 +251,9 @@ export class ComponentSparseSet {
return false;
}
const entityMask = this._componentMasks[entityIndex];
const entityMask = this._componentMasks[entityIndex]!;
const componentMask = ComponentRegistry.getBitMask(componentType);
return BitMask64Utils.hasAny(entityMask, componentMask);
}
@@ -301,7 +301,7 @@ export class ComponentSparseSet {
*/
public forEach(callback: (entity: Entity, mask: BitMask64Data, index: number) => void): void {
this._entities.forEach((entity, index) => {
callback(entity, this._componentMasks[index], index);
callback(entity, this._componentMasks[index]!, index);
});
}

View File

@@ -86,8 +86,8 @@ export class EntityList {
const idsToRecycle: number[] = [];
for (let i = this.buffer.length - 1; i >= 0; i--) {
idsToRecycle.push(this.buffer[i].id);
this.buffer[i].destroy();
idsToRecycle.push(this.buffer[i]!.id);
this.buffer[i]!.destroy();
}
// 批量回收ID
@@ -142,7 +142,7 @@ export class EntityList {
*/
public findEntity(name: string): Entity | null {
const entities = this._nameToEntities.get(name);
return entities && entities.length > 0 ? entities[0] : null;
return entities && entities.length > 0 ? entities[0]! : null;
}
/**

View File

@@ -209,9 +209,9 @@ export class Matcher {
all: [...this.condition.all],
any: [...this.condition.any],
none: [...this.condition.none],
tag: this.condition.tag,
name: this.condition.name,
component: this.condition.component
...(this.condition.tag !== undefined && { tag: this.condition.tag }),
...(this.condition.name !== undefined && { name: this.condition.name }),
...(this.condition.component !== undefined && { component: this.condition.component })
};
}

View File

@@ -75,7 +75,7 @@ export class SparseSet<T> {
// 如果不是最后一个元素,则与最后一个元素交换
if (index !== lastIndex) {
const lastItem = this._dense[lastIndex];
const lastItem = this._dense[lastIndex]!;
this._dense[index] = lastItem;
this._sparse.set(lastItem, index);
}
@@ -140,7 +140,7 @@ export class SparseSet<T> {
*/
public forEach(callback: (item: T, index: number) => void): void {
for (let i = 0; i < this._dense.length; i++) {
callback(this._dense[i], i);
callback(this._dense[i]!, i);
}
}
@@ -153,7 +153,7 @@ export class SparseSet<T> {
public map<U>(callback: (item: T, index: number) => U): U[] {
const result: U[] = [];
for (let i = 0; i < this._dense.length; i++) {
result.push(callback(this._dense[i], i));
result.push(callback(this._dense[i]!, i));
}
return result;
}
@@ -167,8 +167,8 @@ export class SparseSet<T> {
public filter(predicate: (item: T, index: number) => boolean): T[] {
const result: T[] = [];
for (let i = 0; i < this._dense.length; i++) {
if (predicate(this._dense[i], i)) {
result.push(this._dense[i]);
if (predicate(this._dense[i]!, i)) {
result.push(this._dense[i]!);
}
}
return result;
@@ -182,7 +182,7 @@ export class SparseSet<T> {
*/
public find(predicate: (item: T, index: number) => boolean): T | undefined {
for (let i = 0; i < this._dense.length; i++) {
if (predicate(this._dense[i], i)) {
if (predicate(this._dense[i]!, i)) {
return this._dense[i];
}
}
@@ -197,7 +197,7 @@ export class SparseSet<T> {
*/
public some(predicate: (item: T, index: number) => boolean): boolean {
for (let i = 0; i < this._dense.length; i++) {
if (predicate(this._dense[i], i)) {
if (predicate(this._dense[i]!, i)) {
return true;
}
}
@@ -212,7 +212,7 @@ export class SparseSet<T> {
*/
public every(predicate: (item: T, index: number) => boolean): boolean {
for (let i = 0; i < this._dense.length; i++) {
if (!predicate(this._dense[i], i)) {
if (!predicate(this._dense[i]!, i)) {
return false;
}
}
@@ -291,7 +291,7 @@ export class SparseSet<T> {
// 检查映射关系的正确性
for (let i = 0; i < this._dense.length; i++) {
const item = this._dense[i];
const item = this._dense[i]!;
const mappedIndex = this._sparse.get(item);
if (mappedIndex !== i) {
return false;