Skip to content

Commit 31cbe9a

Browse files
authored
✨ core: tracking modifiers with logical OR (#225)
* ✨ core: implement tracking modifiers in Or * ⚡️ core: use tracking groups; optimize queries
1 parent 4470410 commit 31cbe9a

File tree

9 files changed

+488
-228
lines changed

9 files changed

+488
-228
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,8 @@ const movingOrVisible = world.query(Or(Velocity, Renderable))
423423
424424
The `Added` modifier tracks all entities that have added the specified traits or relations since the last time the query was run. A new instance of the modifier must be created for tracking to be unique.
425425
426+
When multiple traits are passed to `Added` it uses logical `AND`. Only entities where **all** specified traits have been added will be returned.
427+
426428
```js
427429
import { createAdded } from 'koota'
428430

@@ -434,13 +436,21 @@ const newPositions = world.query(Added(Position))
434436
// Track entities that added a ChildOf relation
435437
const newChildren = world.query(Added(ChildOf))
436438

439+
// Track entities where BOTH Position AND Velocity were added
440+
const fullyAdded = world.query(Added(Position, Velocity))
441+
442+
// Track entities where EITHER Position OR Velocity was added
443+
const eitherAdded = world.query(Or(Added(Position), Added(Velocity)))
444+
437445
// After running the query, the Added modifier is reset
438446
```
439447
440448
#### Removed
441449
442450
The `Removed` modifier tracks all entities that have removed the specified traits or relations since the last time the query was run. This includes entities that have been destroyed. A new instance of the modifier must be created for tracking to be unique.
443451
452+
When multiple traits are passed to `Removed` it uses logical `AND`. Only entities where **all** specified traits have been removed will be returned.
453+
444454
```js
445455
import { createRemoved } from 'koota'
446456

@@ -452,6 +462,12 @@ const stoppedEntities = world.query(Removed(Velocity))
452462
// Track entities that removed a ChildOf relation
453463
const orphaned = world.query(Removed(ChildOf))
454464

465+
// Track entities where BOTH Position AND Velocity were removed
466+
const fullyRemoved = world.query(Removed(Position, Velocity))
467+
468+
// Track entities where EITHER Position OR Velocity was removed
469+
const eitherRemoved = world.query(Or(Removed(Position), Removed(Velocity)))
470+
455471
// After running the query, the Removed modifier is reset
456472
```
457473
@@ -475,6 +491,9 @@ const updatedChildren = world.query(Changed(ChildOf))
475491
// Track entities where BOTH Position AND Velocity have changed
476492
const fullyUpdated = world.query(Changed(Position, Velocity))
477493

494+
// Track entities where EITHER Position OR Velocity has changed
495+
const eitherChanged = world.query(Or(Changed(Position), Changed(Velocity)))
496+
478497
// After running the query, the Changed modifier is reset
479498
```
480499

packages/core/src/query/modifier.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Brand } from '../common';
22
import { Trait } from '../trait/types';
3-
import { Modifier, QueryParameter } from './types';
3+
import { EventType, Modifier, OrModifier, QueryParameter } from './types';
44

55
export const $modifier = Symbol('modifier');
66

@@ -21,3 +21,23 @@ export function createModifier<TTrait extends Trait[] = Trait[], TType extends s
2121
export /* @inline @pure */ function isModifier(param: QueryParameter): param is Modifier {
2222
return (param as Brand<typeof $modifier> | null | undefined)?.[$modifier] as unknown as boolean;
2323
}
24+
25+
/** Check if a modifier is a tracking modifier (added, removed, or changed) */
26+
export function isTrackingModifier(modifier: Modifier): boolean {
27+
const { type } = modifier;
28+
return type.includes('added') || type.includes('removed') || type.includes('changed');
29+
}
30+
31+
/** Get the tracking type from a modifier */
32+
export function getTrackingType(modifier: Modifier): EventType | null {
33+
const { type } = modifier;
34+
if (type.includes('added')) return 'add';
35+
if (type.includes('removed')) return 'remove';
36+
if (type.includes('changed')) return 'change';
37+
return null;
38+
}
39+
40+
/** Check if an Or modifier has nested modifiers */
41+
export function isOrWithModifiers(modifier: Modifier): modifier is OrModifier {
42+
return modifier.type === 'or' && Array.isArray((modifier as OrModifier).modifiers);
43+
}
Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
import type { Trait } from '../../trait/types';
2-
import type { Modifier } from '../types';
3-
import { createModifier } from '../modifier';
2+
import type { Modifier, OrModifier, OrParameter } from '../types';
3+
import { $modifier, createModifier } from '../modifier';
44

5-
export const Or = <T extends Trait[] = Trait[]>(...traits: T): Modifier<T, 'or'> => {
6-
return createModifier('or', 2, traits);
5+
export const Or = <T extends OrParameter[]>(...params: T): OrModifier<T> => {
6+
// Separate traits from nested modifiers
7+
const traits: Trait[] = [];
8+
const modifiers: Modifier[] = [];
9+
10+
for (const param of params) {
11+
if ((param as Modifier)[$modifier]) {
12+
modifiers.push(param as Modifier);
13+
} else {
14+
traits.push(param as Trait);
15+
}
16+
}
17+
18+
const modifier = createModifier('or', 2, traits) as OrModifier<T>;
19+
modifier.modifiers = modifiers;
20+
21+
return modifier;
722
};

0 commit comments

Comments
 (0)