import { describe, expect, it } from "@suaveplan/testing/runner"; import { Archetype } from "../archetype/archetype.js"; import { BitSet } from "../bitset/bitset.js"; import { ComponentRegistry } from "../component-registry/component-registry.js"; import { defineComponent } from "../schema/schema.js"; import { defineQuery, Query } from "./query.js"; const Position = defineComponent("Position", { x: 0 }); const Velocity = defineComponent("Velocity", { dx: 0 }); const Disabled = defineComponent("Disabled", { d: true }); function makeArch( registry: ComponentRegistry, schemas: (typeof Position)[] ): Archetype { const mask = new BitSet(registry.maxComponents); const ids: number[] = []; for (const s of schemas) { const id = registry.getId(s); mask.set(id); ids.push(id); } ids.sort((a, b) => a - b); return new Archetype(mask, ids); } describe("Query", () => { it("include-only matches every superset archetype", { timeout: 5000, }, () => { const reg = new ComponentRegistry(); reg.register(Position); reg.register(Velocity); const arch1 = makeArch(reg, [Position]); const arch2 = makeArch(reg, [Position, Velocity]); const arch3 = makeArch(reg, [Velocity]); for (const id of [10, 11]) arch1.addEntity(id, new Map()); arch2.addEntity(20, new Map()); arch3.addEntity(30, new Map()); const q = new Query(defineQuery([Position]), reg); const result = q.evaluate([arch1, arch2, arch3]); expect([...result].sort((a, b) => a - b)).toEqual([10, 11, 20]); }); it("exclude filters out archetypes whose mask intersects the exclude mask", { timeout: 5000, }, () => { const reg = new ComponentRegistry(); reg.register(Position); reg.register(Disabled); const archEnabled = makeArch(reg, [Position]); const archDisabled = makeArch(reg, [Position, Disabled]); archEnabled.addEntity(1, new Map()); archDisabled.addEntity(2, new Map()); const q = new Query(defineQuery([Position], [Disabled]), reg); const result = q.evaluate([archEnabled, archDisabled]); expect([...result]).toEqual([1]); }); it("invalidate clears the cached match list and forces a re-scan", { timeout: 5000, }, () => { const reg = new ComponentRegistry(); reg.register(Position); reg.register(Velocity); const arch1 = makeArch(reg, [Position]); arch1.addEntity(1, new Map()); const archetypes: Archetype[] = [arch1]; const q = new Query(defineQuery([Position]), reg); expect(q.evaluate(archetypes)).toEqual([1]); const arch2 = makeArch(reg, [Position, Velocity]); arch2.addEntity(2, new Map()); archetypes.push(arch2); // Without invalidate, the new archetype is not reconsidered. expect(q.evaluate(archetypes)).toEqual([1]); q.invalidate(); expect([...q.evaluate(archetypes)].sort()).toEqual([1, 2]); }); it("defineQuery omits exclude when not supplied", { timeout: 5000 }, () => { const descriptor = defineQuery([Position]); expect(descriptor.exclude).toBeUndefined(); }); it("defineQuery includes exclude when provided", { timeout: 5000 }, () => { const descriptor = defineQuery([Position], [Disabled]); expect(descriptor.exclude).toEqual([Disabled]); }); it("empty exclude array is treated as no exclude", { timeout: 5000, }, () => { const reg = new ComponentRegistry(); reg.register(Position); const q = new Query({ include: [Position], exclude: [] }, reg); expect(q.excludeMask).toBeNull(); }); });