UNPKG

elics

Version:

lightweight, flexible ECS for web games

158 lines 5.92 kB
import { Types } from './types.js'; import BitSet from './bit-set.js'; export class Query { constructor(requiredMask, excludedMask, queryId, valuePredicates = []) { this.requiredMask = requiredMask; this.excludedMask = excludedMask; this.queryId = queryId; this.subscribers = { qualify: new Set(), disqualify: new Set(), }; this.entities = new Set(); this.valuePredicates = []; this.valuePredicates = valuePredicates; } matches(entity) { // Excluded: if any excluded bit is present on entity -> no match if (!this.excludedMask.isEmpty() && this.excludedMask.intersects(entity.bitmask)) { return false; } // Required: entity must contain all required bits if (!entity.bitmask.contains(this.requiredMask)) { return false; } // Value predicates: verify values if (this.valuePredicates.length > 0) { for (const p of this.valuePredicates) { const v = entity.getValue(p.component, p.key); switch (p.op) { case 'eq': if (v !== p.value) { return false; } break; case 'ne': if (v === p.value) { return false; } break; case 'lt': if (!(typeof v === 'number' && typeof p.value === 'number' && v < p.value)) { return false; } break; case 'le': if (!(typeof v === 'number' && typeof p.value === 'number' && v <= p.value)) { return false; } break; case 'gt': if (!(typeof v === 'number' && typeof p.value === 'number' && v > p.value)) { return false; } break; case 'ge': if (!(typeof v === 'number' && typeof p.value === 'number' && v >= p.value)) { return false; } break; case 'in': if (!p.valueSet || !p.valueSet.has(v)) { return false; } break; case 'nin': if (p.valueSet && p.valueSet.has(v)) { return false; } break; } } } return true; } subscribe(event, callback, replayExisting = false) { this.subscribers[event].add(callback); if (event === 'qualify' && replayExisting) { for (const e of this.entities) { callback(e); } } return () => { this.subscribers[event].delete(callback); }; } static generateQueryInfo(queryConfig) { var _a; const requiredMask = new BitSet(); const excludedMask = new BitSet(); for (const c of queryConfig.required) { requiredMask.orInPlace(c.bitmask); } if (queryConfig.excluded) { for (const c of queryConfig.excluded) { excludedMask.orInPlace(c.bitmask); } } // Normalize and validate predicates const rawPreds = (_a = queryConfig.where) !== null && _a !== void 0 ? _a : []; const preds = rawPreds.map((p) => { const schema = p.component.schema[p.key]; if (!schema) { throw new Error(`Predicate key '${p.key}' not found on component ${p.component.id}`); } const t = schema.type; // Validate operator applicability switch (p.op) { case 'lt': case 'le': case 'gt': case 'ge': if (!(t === Types.Int8 || t === Types.Int16 || t === Types.Float32 || t === Types.Float64)) { throw new Error(`Operator '${p.op}' only valid for numeric fields: ${p.component.id}.${p.key}`); } break; case 'in': case 'nin': if (!Array.isArray(p.value)) { throw new Error(`Operator '${p.op}' requires array value for ${p.component.id}.${p.key}`); } break; default: // eq, ne valid for all break; } const np = { ...p }; if (p.op === 'in' || p.op === 'nin') { np.valueSet = new Set(p.value); } return np; }); for (const p of preds) { requiredMask.orInPlace(p.component.bitmask); } const whereStr = preds .map((p) => `${p.component.typeId}:${p.key}:${p.op}=${Array.isArray(p.value) ? 'arr' : String(p.value)}`) .join(','); return { requiredMask, excludedMask, queryId: `required:${requiredMask.toString()}|excluded:${excludedMask.toString()}|where:${whereStr}`, valuePredicates: preds, }; } } //# sourceMappingURL=query.js.map