@vworlds/vecs 1.0.15 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +249 -119
- package/dist/component.d.ts +52 -76
- package/dist/component.js +60 -45
- package/dist/component.js.map +1 -1
- package/dist/component_meta.d.ts +98 -0
- package/dist/component_meta.js +65 -0
- package/dist/component_meta.js.map +1 -0
- package/dist/dsl.d.ts +46 -34
- package/dist/dsl.js +459 -61
- package/dist/dsl.js.map +1 -1
- package/dist/entity/entity.base.d.ts +57 -0
- package/dist/entity/entity.base.js +81 -0
- package/dist/entity/entity.base.js.map +1 -0
- package/dist/entity/entity.components.d.ts +117 -0
- package/dist/entity/entity.components.js +244 -0
- package/dist/entity/entity.components.js.map +1 -0
- package/dist/entity/entity.d.ts +35 -0
- package/dist/entity/entity.identity.d.ts +8 -0
- package/dist/entity/entity.identity.js +15 -0
- package/dist/entity/entity.identity.js.map +1 -0
- package/dist/entity/entity.js +33 -0
- package/dist/entity/entity.js.map +1 -0
- package/dist/entity/entity.lifecycle.d.ts +12 -0
- package/dist/entity/entity.lifecycle.js +111 -0
- package/dist/entity/entity.lifecycle.js.map +1 -0
- package/dist/entity/entity.queries.d.ts +3 -0
- package/dist/entity/entity.queries.js +33 -0
- package/dist/entity/entity.queries.js.map +1 -0
- package/dist/entity/entity.relationships.d.ts +9 -0
- package/dist/entity/entity.relationships.js +74 -0
- package/dist/entity/entity.relationships.js.map +1 -0
- package/dist/entity/index.d.ts +2 -0
- package/dist/entity/index.js +3 -0
- package/dist/entity/index.js.map +1 -0
- package/dist/filter.d.ts +27 -8
- package/dist/filter.js +33 -18
- package/dist/filter.js.map +1 -1
- package/dist/index.d.ts +13 -5
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/inject.d.ts +80 -0
- package/dist/inject.js +270 -0
- package/dist/inject.js.map +1 -0
- package/dist/module.d.ts +23 -0
- package/dist/module.js +17 -0
- package/dist/module.js.map +1 -0
- package/dist/modules/identity.d.ts +15 -0
- package/dist/modules/identity.js +41 -0
- package/dist/modules/identity.js.map +1 -0
- package/dist/modules/singleton.d.ts +26 -0
- package/dist/modules/singleton.js +41 -0
- package/dist/modules/singleton.js.map +1 -0
- package/dist/package.json +12 -1
- package/dist/phase.d.ts +2 -2
- package/dist/query/index.d.ts +6 -0
- package/dist/query/index.js +5 -0
- package/dist/query/index.js.map +1 -0
- package/dist/query/query.00.base.d.ts +23 -0
- package/dist/query/query.00.base.js +77 -0
- package/dist/query/query.00.base.js.map +1 -0
- package/dist/query/query.01.reactive.d.ts +7 -0
- package/dist/query/query.01.reactive.js +58 -0
- package/dist/query/query.01.reactive.js.map +1 -0
- package/dist/query/query.02.lifecycle.d.ts +6 -0
- package/dist/query/query.02.lifecycle.js +63 -0
- package/dist/query/query.02.lifecycle.js.map +1 -0
- package/dist/query/query.03.tracking.d.ts +15 -0
- package/dist/query/query.03.tracking.js +31 -0
- package/dist/query/query.03.tracking.js.map +1 -0
- package/dist/query/query.04.callbacks.d.ts +14 -0
- package/dist/query/query.04.callbacks.js +65 -0
- package/dist/query/query.04.callbacks.js.map +1 -0
- package/dist/query/query.05.updates.d.ts +14 -0
- package/dist/query/query.05.updates.js +81 -0
- package/dist/query/query.05.updates.js.map +1 -0
- package/dist/query/query.06.predicate.d.ts +13 -0
- package/dist/query/query.06.predicate.js +40 -0
- package/dist/query/query.06.predicate.js.map +1 -0
- package/dist/query/query.07.groups.d.ts +41 -0
- package/dist/query/query.07.groups.js +110 -0
- package/dist/query/query.07.groups.js.map +1 -0
- package/dist/query/query.d.ts +53 -0
- package/dist/query/query.js +138 -0
- package/dist/query/query.js.map +1 -0
- package/dist/relationship.d.ts +19 -0
- package/dist/relationship.js +18 -0
- package/dist/relationship.js.map +1 -0
- package/dist/system.d.ts +37 -23
- package/dist/system.js +80 -64
- package/dist/system.js.map +1 -1
- package/dist/terms/all_term.d.ts +32 -0
- package/dist/terms/all_term.js +41 -0
- package/dist/terms/all_term.js.map +1 -0
- package/dist/terms/any_term.d.ts +33 -0
- package/dist/terms/any_term.js +42 -0
- package/dist/terms/any_term.js.map +1 -0
- package/dist/terms/build.d.ts +62 -0
- package/dist/terms/build.js +382 -0
- package/dist/terms/build.js.map +1 -0
- package/dist/terms/component_term.d.ts +37 -0
- package/dist/terms/component_term.js +49 -0
- package/dist/terms/component_term.js.map +1 -0
- package/dist/terms/empty_term.d.ts +6 -0
- package/dist/terms/empty_term.js +12 -0
- package/dist/terms/empty_term.js.map +1 -0
- package/dist/terms/index.d.ts +11 -0
- package/dist/terms/index.js +12 -0
- package/dist/terms/index.js.map +1 -0
- package/dist/terms/not_term.d.ts +35 -0
- package/dist/terms/not_term.js +47 -0
- package/dist/terms/not_term.js.map +1 -0
- package/dist/terms/only_term.d.ts +47 -0
- package/dist/terms/only_term.js +79 -0
- package/dist/terms/only_term.js.map +1 -0
- package/dist/terms/predicate_term.d.ts +80 -0
- package/dist/terms/predicate_term.js +109 -0
- package/dist/terms/predicate_term.js.map +1 -0
- package/dist/terms/target_term.d.ts +43 -0
- package/dist/terms/target_term.js +87 -0
- package/dist/terms/target_term.js.map +1 -0
- package/dist/terms/term.d.ts +94 -0
- package/dist/terms/term.js +202 -0
- package/dist/terms/term.js.map +1 -0
- package/dist/terms/world_term.d.ts +68 -0
- package/dist/terms/world_term.js +99 -0
- package/dist/terms/world_term.js.map +1 -0
- package/dist/timer.js +2 -2
- package/dist/timer.js.map +1 -1
- package/dist/util/array_map.js +12 -0
- package/dist/util/array_map.js.map +1 -1
- package/dist/util/bitset.js +107 -22
- package/dist/util/bitset.js.map +1 -1
- package/dist/util/dense_set.d.ts +1 -0
- package/dist/util/dense_set.js +90 -0
- package/dist/util/dense_set.js.map +1 -0
- package/dist/util/id_pool.d.ts +30 -0
- package/dist/util/id_pool.js +222 -0
- package/dist/util/id_pool.js.map +1 -0
- package/dist/world/index.d.ts +3 -0
- package/dist/world/index.js +3 -0
- package/dist/world/index.js.map +1 -0
- package/dist/world/world.base.d.ts +6 -0
- package/dist/world/world.base.js +21 -0
- package/dist/world/world.base.js.map +1 -0
- package/dist/world/world.components.d.ts +67 -0
- package/dist/world/world.components.js +93 -0
- package/dist/world/world.components.js.map +1 -0
- package/dist/world/world.d.ts +29 -0
- package/dist/world/world.deferred.d.ts +13 -0
- package/dist/world/world.deferred.js +93 -0
- package/dist/world/world.deferred.js.map +1 -0
- package/dist/world/world.entities.d.ts +18 -0
- package/dist/world/world.entities.js +97 -0
- package/dist/world/world.entities.js.map +1 -0
- package/dist/world/world.js +39 -0
- package/dist/world/world.js.map +1 -0
- package/dist/world/world.modules.d.ts +12 -0
- package/dist/world/world.modules.js +21 -0
- package/dist/world/world.modules.js.map +1 -0
- package/dist/world/world.pipeline.d.ts +21 -0
- package/dist/world/world.pipeline.js +106 -0
- package/dist/world/world.pipeline.js.map +1 -0
- package/dist/world/world.pools.d.ts +9 -0
- package/dist/world/world.pools.js +63 -0
- package/dist/world/world.pools.js.map +1 -0
- package/dist/world/world.queries.d.ts +18 -0
- package/dist/world/world.queries.js +101 -0
- package/dist/world/world.queries.js.map +1 -0
- package/dist/world/world.storage.d.ts +7 -0
- package/dist/world/world.storage.js +26 -0
- package/dist/world/world.storage.js.map +1 -0
- package/package.json +12 -1
- package/dist/entity.d.ts +0 -215
- package/dist/entity.js +0 -457
- package/dist/entity.js.map +0 -1
- package/dist/query.d.ts +0 -251
- package/dist/query.js +0 -353
- package/dist/query.js.map +0 -1
- package/dist/world.d.ts +0 -389
- package/dist/world.js +0 -631
- package/dist/world.js.map +0 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Term } from "./term.js";
|
|
2
|
+
/**
|
|
3
|
+
* Composite term that matches entities whose relationship target satisfies a
|
|
4
|
+
* given inner predicate.
|
|
5
|
+
*
|
|
6
|
+
* An entity `e` matches when it carries the relationship component
|
|
7
|
+
* `_relationshipEid` **and** the target entity stored in that component
|
|
8
|
+
* satisfies `_innerTerm`.
|
|
9
|
+
*
|
|
10
|
+
* Children: `[relationshipTerm, innerTerm]`.
|
|
11
|
+
* - `children[0]` (`relationshipTerm`): the `ComponentTerm` for the
|
|
12
|
+
* relationship eid — fires when the relationship is added/removed from `e`.
|
|
13
|
+
* - `children[1]` (`innerTerm`): the term for the inner DSL — fires when the
|
|
14
|
+
* target entity transitions in or out.
|
|
15
|
+
*
|
|
16
|
+
* ## Non-standard event routing
|
|
17
|
+
*
|
|
18
|
+
* `TargetTerm` overrides `_onChildEvent` and `_onChildRefresh` to implement
|
|
19
|
+
* a **fan-out** strategy: when the inner term changes (the target enters or
|
|
20
|
+
* exits), all *sources* that point at that target via the relationship must be
|
|
21
|
+
* re-evaluated. This reverses the normal bottom-up signal direction. The
|
|
22
|
+
* fan-out is why `TargetTerm` subtrees are excluded from diamond collapsing —
|
|
23
|
+
* a plain compiled predicate cannot replicate this per-source walk.
|
|
24
|
+
*/
|
|
25
|
+
export class TargetTerm extends Term {
|
|
26
|
+
constructor(world, key, _relationshipEid, relationshipTerm, _innerTerm, onDestroy) {
|
|
27
|
+
super(world, key, "target", false, onDestroy);
|
|
28
|
+
this._relationshipEid = _relationshipEid;
|
|
29
|
+
this._innerTerm = _innerTerm;
|
|
30
|
+
this._setChildren([relationshipTerm, _innerTerm]);
|
|
31
|
+
this._initializeFromWorld();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build a `TargetTerm` for the given relationship eid and inner DSL.
|
|
35
|
+
*
|
|
36
|
+
* @param world - Owning world.
|
|
37
|
+
* @param relationshipEid - Numeric eid of the relationship component type.
|
|
38
|
+
* @param innerDSL - DSL expression the relationship target must satisfy.
|
|
39
|
+
* @param key - Canonical cache key.
|
|
40
|
+
* @param getTerm - Term canonicalizer / cache accessor.
|
|
41
|
+
* @param onDestroy - Called when the last consumer unsubscribes.
|
|
42
|
+
*/
|
|
43
|
+
static build(world, relationshipEid, innerDSL, key, getTerm, onDestroy) {
|
|
44
|
+
const relationshipTerm = getTerm(world, relationshipEid);
|
|
45
|
+
const innerTerm = getTerm(world, innerDSL);
|
|
46
|
+
return new TargetTerm(world, key, relationshipEid, relationshipTerm, innerTerm, onDestroy);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* @internal Fan-out child membership event handler.
|
|
50
|
+
*
|
|
51
|
+
* When the relationship component is added/removed from `entity`
|
|
52
|
+
* (`child === children[0]`), re-evaluate that entity directly. When the
|
|
53
|
+
* inner term changes for some target entity (`child === children[1]`), walk
|
|
54
|
+
* every source entity that points at that target via the relationship and
|
|
55
|
+
* re-evaluate each one.
|
|
56
|
+
*/
|
|
57
|
+
_onChildEvent(_kind, entity, child) {
|
|
58
|
+
if (child === this._children[0]) {
|
|
59
|
+
this._sync(entity);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
entity.children(this._relationshipEid).forEach((source) => this._sync(source));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* @internal Fan-out child refresh handler.
|
|
66
|
+
*
|
|
67
|
+
* Mirrors `_onChildEvent`: a data/shape change on the relationship component
|
|
68
|
+
* or the inner term may alter whether a source entity matches, so the same
|
|
69
|
+
* fan-out logic applies.
|
|
70
|
+
*/
|
|
71
|
+
_onChildRefresh(entity, child) {
|
|
72
|
+
if (child === this._children[0]) {
|
|
73
|
+
this._sync(entity);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
entity.children(this._relationshipEid).forEach((source) => this._sync(source));
|
|
77
|
+
}
|
|
78
|
+
/** @internal */
|
|
79
|
+
_matches(entity) {
|
|
80
|
+
if (entity._destroyed) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const target = entity._get(this._relationshipEid)?.target;
|
|
84
|
+
return target !== undefined && this._innerTerm.has(target);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=target_term.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"target_term.js","sourceRoot":"","sources":["../../src/terms/target_term.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAiB,MAAM,WAAW,CAAC;AAGhD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,OAAO,UAAW,SAAQ,IAAI;IAClC,YACE,KAAY,EACZ,GAAW,EACM,gBAAwB,EACzC,gBAAsB,EACL,UAAgB,EACjC,SAA+B;QAE/B,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QAL7B,qBAAgB,GAAhB,gBAAgB,CAAQ;QAExB,eAAU,GAAV,UAAU,CAAM;QAIjC,IAAI,CAAC,YAAY,CAAC,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC,CAAC;QAClD,IAAI,CAAC,oBAAoB,EAAE,CAAC;IAC9B,CAAC;IAED;;;;;;;;;OASG;IACI,MAAM,CAAC,KAAK,CACjB,KAAY,EACZ,eAAuB,EACvB,QAAkB,EAClB,GAAW,EACX,OAAyB,EACzB,SAA+B;QAE/B,MAAM,gBAAgB,GAAG,OAAO,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;QACzD,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAC3C,OAAO,IAAI,UAAU,CAAC,KAAK,EAAE,GAAG,EAAE,eAAe,EAAE,gBAAgB,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;IAC7F,CAAC;IAED;;;;;;;;OAQG;IACgB,aAAa,CAAC,KAAoB,EAAE,MAAc,EAAE,KAAW;QAChF,IAAI,KAAK,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;YAChC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACnB,OAAO;QACT,CAAC;QACD,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACjF,CAAC;IAED;;;;;;OAMG;IACgB,eAAe,CAAC,MAAc,EAAE,KAAW;QAC5D,IAAI,KAAK,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;YAChC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACnB,OAAO;QACT,CAAC;QACD,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACjF,CAAC;IAED,gBAAgB;IACN,QAAQ,CAAC,MAAc;QAC/B,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YACtB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,MAAM,GAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAA8B,EAAE,MAAM,CAAC;QACxF,OAAO,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7D,CAAC;CACF"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Entity } from "../entity/index.js";
|
|
2
|
+
import type { World } from "../world/index.js";
|
|
3
|
+
export declare const enum TermEventKind {
|
|
4
|
+
Enter = 0,
|
|
5
|
+
Exit = 1
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Observer interface for a `Term` node.
|
|
9
|
+
*
|
|
10
|
+
* ## Two-signal contract
|
|
11
|
+
*
|
|
12
|
+
* Consumers receive two independent channels:
|
|
13
|
+
*
|
|
14
|
+
* - **`onTermEvent`** — the ordered **membership stream**. Only real
|
|
15
|
+
* enter/exit flips are emitted here (no spurious duplicates). Every
|
|
16
|
+
* consumer — including `Query`, `System`, and composite parent terms —
|
|
17
|
+
* implements this method. It is the sole authoritative source of "who is
|
|
18
|
+
* in the set".
|
|
19
|
+
*
|
|
20
|
+
* - **`onTermRefresh`** — an *optional* **invalidation hint**. It signals
|
|
21
|
+
* that an entity's data or shape changed but membership *may* not have.
|
|
22
|
+
* Only composite terms that need to pull-recompute on data changes
|
|
23
|
+
* (e.g. `PredicateTerm`, `OnlyTerm`) implement this; membership-only
|
|
24
|
+
* consumers such as `Query` omit it and are never woken by pure data
|
|
25
|
+
* updates. The `?` makes the method optional so implementors that do not
|
|
26
|
+
* care about data refreshes pay zero overhead.
|
|
27
|
+
*/
|
|
28
|
+
export interface TermConsumer {
|
|
29
|
+
/** Ordered membership stream. Only real enter/exit flips are emitted here. */
|
|
30
|
+
onTermEvent(kind: TermEventKind, entity: Entity): void;
|
|
31
|
+
}
|
|
32
|
+
export type TermKind = "world" | "component" | "empty" | "all" | "any" | "not" | "only" | "predicate" | "target" | "merged";
|
|
33
|
+
/**
|
|
34
|
+
* Base class for all reactive entity-set nodes in the term graph.
|
|
35
|
+
*
|
|
36
|
+
* A `Term` maintains a live `Set<Entity>` of every entity currently satisfying
|
|
37
|
+
* its sub-predicate. It subscribes to its children via the two-signal contract
|
|
38
|
+
* defined on {@link TermConsumer} and propagates enter/exit events upward to
|
|
39
|
+
* its own consumers.
|
|
40
|
+
*
|
|
41
|
+
* Subclasses implement `_matches` (the membership test) and may override
|
|
42
|
+
* `_onChildEvent`, `_onChildRefresh`, or `onTermRefresh` to customize how
|
|
43
|
+
* signals from children are interpreted.
|
|
44
|
+
*/
|
|
45
|
+
export declare abstract class Term implements TermConsumer {
|
|
46
|
+
readonly world: World;
|
|
47
|
+
readonly key: string;
|
|
48
|
+
readonly kind: TermKind;
|
|
49
|
+
private readonly _pinned;
|
|
50
|
+
private readonly _onDestroy;
|
|
51
|
+
private _destroyed;
|
|
52
|
+
private readonly _childUnsubscribes;
|
|
53
|
+
protected constructor(world: World, key: string, kind: TermKind, _pinned: boolean, _onDestroy: (term: Term) => void);
|
|
54
|
+
get count(): number;
|
|
55
|
+
[Symbol.iterator](): IterableIterator<Entity>;
|
|
56
|
+
children(): readonly Term[];
|
|
57
|
+
has(entity: Entity): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Default membership-event handler: re-run `_matches` and add/remove
|
|
60
|
+
* accordingly. Composite terms typically let this default run; leaf terms
|
|
61
|
+
* use explicit `_add`/`_delete` calls instead and never receive child events.
|
|
62
|
+
*/
|
|
63
|
+
onTermEvent(_kind: TermEventKind, entity: Entity): void;
|
|
64
|
+
/**
|
|
65
|
+
* Default invalidation-hint handler: re-run `_matches` via `_sync`.
|
|
66
|
+
*
|
|
67
|
+
* Terms that should *not* re-evaluate on a data/shape refresh (e.g.
|
|
68
|
+
* `NotTerm`) override this with a no-op. Terms that need the shape channel
|
|
69
|
+
* but want to override the full handler (e.g. `OnlyTerm`) override with a
|
|
70
|
+
* call to `_sync` as well — this default is already correct for most cases.
|
|
71
|
+
*/
|
|
72
|
+
onTermRefresh(entity: Entity): void;
|
|
73
|
+
/**
|
|
74
|
+
* Add `consumer` to the set of active consumers and return an unsubscribe
|
|
75
|
+
* function.
|
|
76
|
+
*
|
|
77
|
+
* Consumer count drives term lifetime: when the count falls to zero on a
|
|
78
|
+
* non-pinned term the term destroys itself and cascades destruction to
|
|
79
|
+
* children. Calling the unsubscribe function more than once is safe (idempotent).
|
|
80
|
+
*
|
|
81
|
+
* @throws If the term has already been destroyed.
|
|
82
|
+
*/
|
|
83
|
+
subscribe(consumer: TermConsumer): () => void;
|
|
84
|
+
/**
|
|
85
|
+
* Permanently destroy this term.
|
|
86
|
+
*
|
|
87
|
+
* Clears all consumers, unsubscribes from all children (cascading
|
|
88
|
+
* destruction when their consumer counts reach zero), clears the entity
|
|
89
|
+
* set, and calls `_onDestroy` to remove the term from the world cache.
|
|
90
|
+
* Pinned terms (`WorldTerm`, `ComponentTerm`, `EmptyTerm`) ignore this call.
|
|
91
|
+
*/
|
|
92
|
+
destroy(): void;
|
|
93
|
+
private _emit;
|
|
94
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for all reactive entity-set nodes in the term graph.
|
|
3
|
+
*
|
|
4
|
+
* A `Term` maintains a live `Set<Entity>` of every entity currently satisfying
|
|
5
|
+
* its sub-predicate. It subscribes to its children via the two-signal contract
|
|
6
|
+
* defined on {@link TermConsumer} and propagates enter/exit events upward to
|
|
7
|
+
* its own consumers.
|
|
8
|
+
*
|
|
9
|
+
* Subclasses implement `_matches` (the membership test) and may override
|
|
10
|
+
* `_onChildEvent`, `_onChildRefresh`, or `onTermRefresh` to customize how
|
|
11
|
+
* signals from children are interpreted.
|
|
12
|
+
*/
|
|
13
|
+
export class Term {
|
|
14
|
+
constructor(world, key, kind, _pinned, _onDestroy) {
|
|
15
|
+
this.world = world;
|
|
16
|
+
this.key = key;
|
|
17
|
+
this.kind = kind;
|
|
18
|
+
this._pinned = _pinned;
|
|
19
|
+
this._onDestroy = _onDestroy;
|
|
20
|
+
/** @internal */
|
|
21
|
+
this._children = [];
|
|
22
|
+
/** @internal */
|
|
23
|
+
this._consumers = new Set();
|
|
24
|
+
/** @internal */
|
|
25
|
+
this._entities = new Set();
|
|
26
|
+
this._destroyed = false;
|
|
27
|
+
this._childUnsubscribes = [];
|
|
28
|
+
}
|
|
29
|
+
get count() {
|
|
30
|
+
return this._entities.size;
|
|
31
|
+
}
|
|
32
|
+
[Symbol.iterator]() {
|
|
33
|
+
return this._entities[Symbol.iterator]();
|
|
34
|
+
}
|
|
35
|
+
children() {
|
|
36
|
+
return this._children;
|
|
37
|
+
}
|
|
38
|
+
has(entity) {
|
|
39
|
+
return this._entities.has(entity);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Default membership-event handler: re-run `_matches` and add/remove
|
|
43
|
+
* accordingly. Composite terms typically let this default run; leaf terms
|
|
44
|
+
* use explicit `_add`/`_delete` calls instead and never receive child events.
|
|
45
|
+
*/
|
|
46
|
+
onTermEvent(_kind, entity) {
|
|
47
|
+
this._sync(entity);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Default invalidation-hint handler: re-run `_matches` via `_sync`.
|
|
51
|
+
*
|
|
52
|
+
* Terms that should *not* re-evaluate on a data/shape refresh (e.g.
|
|
53
|
+
* `NotTerm`) override this with a no-op. Terms that need the shape channel
|
|
54
|
+
* but want to override the full handler (e.g. `OnlyTerm`) override with a
|
|
55
|
+
* call to `_sync` as well — this default is already correct for most cases.
|
|
56
|
+
*/
|
|
57
|
+
onTermRefresh(entity) {
|
|
58
|
+
this._sync(entity);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Add `consumer` to the set of active consumers and return an unsubscribe
|
|
62
|
+
* function.
|
|
63
|
+
*
|
|
64
|
+
* Consumer count drives term lifetime: when the count falls to zero on a
|
|
65
|
+
* non-pinned term the term destroys itself and cascades destruction to
|
|
66
|
+
* children. Calling the unsubscribe function more than once is safe (idempotent).
|
|
67
|
+
*
|
|
68
|
+
* @throws If the term has already been destroyed.
|
|
69
|
+
*/
|
|
70
|
+
subscribe(consumer) {
|
|
71
|
+
if (this._destroyed) {
|
|
72
|
+
throw new Error(`Cannot subscribe to destroyed ${this.kind} term`);
|
|
73
|
+
}
|
|
74
|
+
this._consumers.add(consumer);
|
|
75
|
+
let active = true;
|
|
76
|
+
return () => {
|
|
77
|
+
if (!active) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
active = false;
|
|
81
|
+
this._consumers.delete(consumer);
|
|
82
|
+
if (!this._pinned && this._consumers.size === 0) {
|
|
83
|
+
this.destroy();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Permanently destroy this term.
|
|
89
|
+
*
|
|
90
|
+
* Clears all consumers, unsubscribes from all children (cascading
|
|
91
|
+
* destruction when their consumer counts reach zero), clears the entity
|
|
92
|
+
* set, and calls `_onDestroy` to remove the term from the world cache.
|
|
93
|
+
* Pinned terms (`WorldTerm`, `ComponentTerm`, `EmptyTerm`) ignore this call.
|
|
94
|
+
*/
|
|
95
|
+
destroy() {
|
|
96
|
+
if (this._destroyed || this._pinned) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this._destroyed = true;
|
|
100
|
+
this._consumers.clear();
|
|
101
|
+
while (this._childUnsubscribes.length > 0) {
|
|
102
|
+
this._childUnsubscribes.pop()();
|
|
103
|
+
}
|
|
104
|
+
this._entities.clear();
|
|
105
|
+
this._onDestroy(this);
|
|
106
|
+
}
|
|
107
|
+
/** @internal Emit an enter event and add entity; no-op if already present. */
|
|
108
|
+
_add(entity) {
|
|
109
|
+
if (this._entities.has(entity)) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
this._entities.add(entity);
|
|
113
|
+
this._emit(0 /* TermEventKind.Enter */, entity);
|
|
114
|
+
}
|
|
115
|
+
/** @internal Emit an exit event and remove entity; no-op if not present. */
|
|
116
|
+
_delete(entity) {
|
|
117
|
+
if (!this._entities.delete(entity)) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this._emit(1 /* TermEventKind.Exit */, entity);
|
|
121
|
+
}
|
|
122
|
+
/** @internal Propagate a refresh hint to all consumers (both channels). */
|
|
123
|
+
_emitRefresh(entity) {
|
|
124
|
+
this._consumers.forEach((consumer) => consumer.onTermRefresh?.(entity));
|
|
125
|
+
}
|
|
126
|
+
/** @internal Register an additional cleanup callback run during `destroy`. */
|
|
127
|
+
_addChildCleanup(cleanup) {
|
|
128
|
+
this._childUnsubscribes.push(cleanup);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* @internal Seed the entity set from the current world state without
|
|
132
|
+
* emitting events.
|
|
133
|
+
*
|
|
134
|
+
* Called once at the end of construction so the term starts up-to-date
|
|
135
|
+
* before any consumers subscribe.
|
|
136
|
+
*/
|
|
137
|
+
_initializeFromWorld() {
|
|
138
|
+
this.world.entities.forEach((entity) => {
|
|
139
|
+
if (this._matches(entity)) {
|
|
140
|
+
this._entities.add(entity);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* @internal Register `children` as this term's child nodes.
|
|
146
|
+
*
|
|
147
|
+
* For each child, subscribes **both** channels via an anonymous
|
|
148
|
+
* `TermConsumer` that routes to `_onChildEvent` and `_onChildRefresh`.
|
|
149
|
+
* Registering both channels here (rather than in subclass constructors)
|
|
150
|
+
* ensures that every composite term receives refresh hints without
|
|
151
|
+
* boilerplate — subclasses then filter in `_onChildRefresh` to decide
|
|
152
|
+
* whether to act.
|
|
153
|
+
*/
|
|
154
|
+
_setChildren(children) {
|
|
155
|
+
this._children.push(...children);
|
|
156
|
+
children.forEach((child) => {
|
|
157
|
+
this._childUnsubscribes.push(child.subscribe({
|
|
158
|
+
onTermEvent: (kind, entity) => this._onChildEvent(kind, entity, child),
|
|
159
|
+
onTermRefresh: (entity) => this._onChildRefresh(entity, child),
|
|
160
|
+
}));
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* @internal Default child membership-event handler: forward to
|
|
165
|
+
* `onTermEvent`. Composite terms (e.g. `TargetTerm`) override this to
|
|
166
|
+
* implement non-trivial fan-out logic.
|
|
167
|
+
*/
|
|
168
|
+
_onChildEvent(kind, entity, _child) {
|
|
169
|
+
this.onTermEvent(kind, entity);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* @internal Default child refresh handler: forward to `onTermRefresh`.
|
|
173
|
+
*
|
|
174
|
+
* Most composites delegate to `onTermRefresh` which defaults to `_sync`.
|
|
175
|
+
* Subclasses that only want to react to *specific* child kinds (e.g.
|
|
176
|
+
* `PredicateTerm` reacting only to component-kind children) override this
|
|
177
|
+
* to filter before calling `_sync`.
|
|
178
|
+
*/
|
|
179
|
+
_onChildRefresh(entity, _child) {
|
|
180
|
+
this.onTermRefresh(entity);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* @internal Pull-recompute membership for `entity`.
|
|
184
|
+
*
|
|
185
|
+
* Re-evaluates `_matches` and calls `_add` or `_delete`. Because `_add` and
|
|
186
|
+
* `_delete` are idempotent, `_sync` is safe to call redundantly — it emits
|
|
187
|
+
* an event only on a real flip, which is the core of the glitch-free
|
|
188
|
+
* propagation guarantee.
|
|
189
|
+
*/
|
|
190
|
+
_sync(entity) {
|
|
191
|
+
if (this._matches(entity)) {
|
|
192
|
+
this._add(entity);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
this._delete(entity);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
_emit(kind, entity) {
|
|
199
|
+
this._consumers.forEach((consumer) => consumer.onTermEvent(kind, entity));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=term.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"term.js","sourceRoot":"","sources":["../../src/terms/term.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Entity } from "../entity/index.js";
|
|
2
|
+
import type { World } from "../world/index.js";
|
|
3
|
+
import { Term, type TermConsumer } from "./term.js";
|
|
4
|
+
/**
|
|
5
|
+
* World-pinned leaf term that matches every live entity.
|
|
6
|
+
*
|
|
7
|
+
* `WorldTerm` is the "universe" node: it holds all entities that are not
|
|
8
|
+
* destroyed. Component-definition entities are first-class members too (they
|
|
9
|
+
* are ordinary entities), so world-rooted queries can enumerate them — this is
|
|
10
|
+
* what makes schema reflection possible. Every other term that needs
|
|
11
|
+
* world-lifecycle awareness (e.g. `NotTerm`, `PredicateTerm`) subscribes to
|
|
12
|
+
* this leaf via the standard membership channel so they see entity creation
|
|
13
|
+
* and destruction.
|
|
14
|
+
*
|
|
15
|
+
* ## Shape channel
|
|
16
|
+
*
|
|
17
|
+
* `WorldTerm` also maintains a separate **shape channel** via
|
|
18
|
+
* {@link subscribeShape} / {@link entityShapeChanged}. When a component is
|
|
19
|
+
* added to or removed from an entity, `build.ts` calls
|
|
20
|
+
* `entityShapeChanged`; the world term then fans that notification out to all
|
|
21
|
+
* registered shape consumers as an `onTermRefresh` call. Only terms that are
|
|
22
|
+
* sensitive to the *set* of components on an entity (currently `OnlyTerm` and
|
|
23
|
+
* merged `PredicateTerm` nodes that contain an `only(...)` sub-expression)
|
|
24
|
+
* register on this channel, keeping it zero-cost for the common case.
|
|
25
|
+
*/
|
|
26
|
+
export declare class WorldTerm extends Term {
|
|
27
|
+
constructor(world: World, key: string, onDestroy: (term: Term) => void);
|
|
28
|
+
/**
|
|
29
|
+
* Called by `build.ts` when a new entity is created.
|
|
30
|
+
*
|
|
31
|
+
* Adds the entity if it matches (i.e. it is live), which propagates an enter
|
|
32
|
+
* event to all consumers.
|
|
33
|
+
*/
|
|
34
|
+
entityCreated(entity: Entity): void;
|
|
35
|
+
/**
|
|
36
|
+
* Called by `build.ts` when an entity is destroyed.
|
|
37
|
+
*
|
|
38
|
+
* Unconditionally removes the entity, propagating an exit event.
|
|
39
|
+
*/
|
|
40
|
+
entityDestroyed(entity: Entity): void;
|
|
41
|
+
/**
|
|
42
|
+
* Called by `build.ts` when a component is added to or removed from an
|
|
43
|
+
* entity (a shape change).
|
|
44
|
+
*
|
|
45
|
+
* Notifies all {@link _shapeConsumers} via `onTermRefresh` so terms like
|
|
46
|
+
* `OnlyTerm` can re-evaluate whether the entity's component set still
|
|
47
|
+
* satisfies their exact-match predicate.
|
|
48
|
+
*
|
|
49
|
+
* Only fires for entities already in this term's set (i.e. live entities) —
|
|
50
|
+
* there is no point notifying about shape changes on entities that the world
|
|
51
|
+
* term itself does not track.
|
|
52
|
+
*/
|
|
53
|
+
entityShapeChanged(entity: Entity): void;
|
|
54
|
+
/**
|
|
55
|
+
* Register `consumer` on the shape channel and return an unsubscribe
|
|
56
|
+
* function.
|
|
57
|
+
*
|
|
58
|
+
* The shape channel is separate from the standard membership channel so
|
|
59
|
+
* that component-add/remove events can be distinguished from
|
|
60
|
+
* entity-create/destroy events. Consumers on this channel receive
|
|
61
|
+
* `onTermRefresh` calls (not `onTermEvent`) because the shape change does
|
|
62
|
+
* not itself imply a membership change in the world term.
|
|
63
|
+
*
|
|
64
|
+
* @param consumer - The term or callback object to notify.
|
|
65
|
+
* @returns Cleanup function; safe to call multiple times.
|
|
66
|
+
*/
|
|
67
|
+
subscribeShape(consumer: TermConsumer): () => void;
|
|
68
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Term } from "./term.js";
|
|
2
|
+
/**
|
|
3
|
+
* World-pinned leaf term that matches every live entity.
|
|
4
|
+
*
|
|
5
|
+
* `WorldTerm` is the "universe" node: it holds all entities that are not
|
|
6
|
+
* destroyed. Component-definition entities are first-class members too (they
|
|
7
|
+
* are ordinary entities), so world-rooted queries can enumerate them — this is
|
|
8
|
+
* what makes schema reflection possible. Every other term that needs
|
|
9
|
+
* world-lifecycle awareness (e.g. `NotTerm`, `PredicateTerm`) subscribes to
|
|
10
|
+
* this leaf via the standard membership channel so they see entity creation
|
|
11
|
+
* and destruction.
|
|
12
|
+
*
|
|
13
|
+
* ## Shape channel
|
|
14
|
+
*
|
|
15
|
+
* `WorldTerm` also maintains a separate **shape channel** via
|
|
16
|
+
* {@link subscribeShape} / {@link entityShapeChanged}. When a component is
|
|
17
|
+
* added to or removed from an entity, `build.ts` calls
|
|
18
|
+
* `entityShapeChanged`; the world term then fans that notification out to all
|
|
19
|
+
* registered shape consumers as an `onTermRefresh` call. Only terms that are
|
|
20
|
+
* sensitive to the *set* of components on an entity (currently `OnlyTerm` and
|
|
21
|
+
* merged `PredicateTerm` nodes that contain an `only(...)` sub-expression)
|
|
22
|
+
* register on this channel, keeping it zero-cost for the common case.
|
|
23
|
+
*/
|
|
24
|
+
export class WorldTerm extends Term {
|
|
25
|
+
constructor(world, key, onDestroy) {
|
|
26
|
+
super(world, key, "world", true, onDestroy);
|
|
27
|
+
/**
|
|
28
|
+
* @internal Consumers registered via {@link subscribeShape} that want
|
|
29
|
+
* notification when any entity's component set changes.
|
|
30
|
+
*/
|
|
31
|
+
this._shapeConsumers = new Set();
|
|
32
|
+
this._initializeFromWorld();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Called by `build.ts` when a new entity is created.
|
|
36
|
+
*
|
|
37
|
+
* Adds the entity if it matches (i.e. it is live), which propagates an enter
|
|
38
|
+
* event to all consumers.
|
|
39
|
+
*/
|
|
40
|
+
entityCreated(entity) {
|
|
41
|
+
if (this._matches(entity)) {
|
|
42
|
+
this._add(entity);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Called by `build.ts` when an entity is destroyed.
|
|
47
|
+
*
|
|
48
|
+
* Unconditionally removes the entity, propagating an exit event.
|
|
49
|
+
*/
|
|
50
|
+
entityDestroyed(entity) {
|
|
51
|
+
this._delete(entity);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Called by `build.ts` when a component is added to or removed from an
|
|
55
|
+
* entity (a shape change).
|
|
56
|
+
*
|
|
57
|
+
* Notifies all {@link _shapeConsumers} via `onTermRefresh` so terms like
|
|
58
|
+
* `OnlyTerm` can re-evaluate whether the entity's component set still
|
|
59
|
+
* satisfies their exact-match predicate.
|
|
60
|
+
*
|
|
61
|
+
* Only fires for entities already in this term's set (i.e. live entities) —
|
|
62
|
+
* there is no point notifying about shape changes on entities that the world
|
|
63
|
+
* term itself does not track.
|
|
64
|
+
*/
|
|
65
|
+
entityShapeChanged(entity) {
|
|
66
|
+
if (this.has(entity)) {
|
|
67
|
+
this._shapeConsumers.forEach((consumer) => consumer.onTermRefresh?.(entity));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Register `consumer` on the shape channel and return an unsubscribe
|
|
72
|
+
* function.
|
|
73
|
+
*
|
|
74
|
+
* The shape channel is separate from the standard membership channel so
|
|
75
|
+
* that component-add/remove events can be distinguished from
|
|
76
|
+
* entity-create/destroy events. Consumers on this channel receive
|
|
77
|
+
* `onTermRefresh` calls (not `onTermEvent`) because the shape change does
|
|
78
|
+
* not itself imply a membership change in the world term.
|
|
79
|
+
*
|
|
80
|
+
* @param consumer - The term or callback object to notify.
|
|
81
|
+
* @returns Cleanup function; safe to call multiple times.
|
|
82
|
+
*/
|
|
83
|
+
subscribeShape(consumer) {
|
|
84
|
+
this._shapeConsumers.add(consumer);
|
|
85
|
+
let active = true;
|
|
86
|
+
return () => {
|
|
87
|
+
if (!active) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
active = false;
|
|
91
|
+
this._shapeConsumers.delete(consumer);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** @internal */
|
|
95
|
+
_matches(entity) {
|
|
96
|
+
return !entity._destroyed;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=world_term.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"world_term.js","sourceRoot":"","sources":["../../src/terms/world_term.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAqB,MAAM,WAAW,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,OAAO,SAAU,SAAQ,IAAI;IAOjC,YAAmB,KAAY,EAAE,GAAW,EAAE,SAA+B;QAC3E,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QAP9C;;;WAGG;QACc,oBAAe,GAAG,IAAI,GAAG,EAAgB,CAAC;QAIzD,IAAI,CAAC,oBAAoB,EAAE,CAAC;IAC9B,CAAC;IAED;;;;;OAKG;IACI,aAAa,CAAC,MAAc;QACjC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAED;;;;OAIG;IACI,eAAe,CAAC,MAAc;QACnC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC;IAED;;;;;;;;;;;OAWG;IACI,kBAAkB,CAAC,MAAc;QACtC,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACrB,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;OAYG;IACI,cAAc,CAAC,QAAsB;QAC1C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACnC,IAAI,MAAM,GAAG,IAAI,CAAC;QAClB,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO;YACT,CAAC;YACD,MAAM,GAAG,KAAK,CAAC;YACf,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACxC,CAAC,CAAC;IACJ,CAAC;IAED,gBAAgB;IACN,QAAQ,CAAC,MAAc;QAC/B,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC;IAC5B,CAAC;CACF"}
|
package/dist/timer.js
CHANGED
|
@@ -49,7 +49,7 @@ export class IntervalTickSource extends BaseTickSource {
|
|
|
49
49
|
super();
|
|
50
50
|
this._accumulator = 0;
|
|
51
51
|
if (intervalSeconds <= 0) {
|
|
52
|
-
throw "interval seconds must be greater than 0";
|
|
52
|
+
throw new Error("interval seconds must be greater than 0");
|
|
53
53
|
}
|
|
54
54
|
this._intervalMs = intervalSeconds * 1000;
|
|
55
55
|
}
|
|
@@ -93,7 +93,7 @@ export class RateTickSource extends BaseTickSource {
|
|
|
93
93
|
super();
|
|
94
94
|
this._counter = 0;
|
|
95
95
|
if (!Number.isInteger(rate) || rate <= 0) {
|
|
96
|
-
throw "rate must be a positive integer";
|
|
96
|
+
throw new Error("rate must be a positive integer");
|
|
97
97
|
}
|
|
98
98
|
this._rate = rate;
|
|
99
99
|
this._source = source;
|
package/dist/timer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timer.js","sourceRoot":"","sources":["../src/timer.ts"],"names":[],"mappings":"AAcA,qEAAqE;AACrE,MAAe,cAAc;IAA7B;QACE,gBAAgB;QACN,aAAQ,GAAG,KAAK,CAAC;QAC3B,gBAAgB;QACN,aAAQ,GAAG,IAAI,CAAC;QAC1B,gBAAgB;QACN,wBAAmB,GAAG,CAAC,CAAC,CAAC;QACnC,gBAAgB;QACN,uBAAkB,GAAG,CAAC,CAAC;QACjC,8EAA8E;QACvE,mBAAc,GAAG,CAAC,CAAC;IA6B5B,CAAC;IA3BC,IAAW,OAAO;QAChB,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,IAAW,aAAa;QACtB,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAED,uEAAuE;IAChE,KAAK;QACV,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6EAA6E;IACtE,IAAI;QACT,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAKD,mDAAmD;IAC5C,SAAS,CAAC,KAAY;QAC3B,KAAK,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;CACF;AAED,oEAAoE;AACpE,MAAM,OAAO,kBAAmB,SAAQ,cAAc;IAIpD;;;;;;;;;OASG;IACH,YAAmB,eAAuB;QACxC,KAAK,EAAE,CAAC;QAbF,iBAAY,GAAG,CAAC,CAAC;QAcvB,IAAI,eAAe,IAAI,CAAC,EAAE,CAAC;YACzB,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"timer.js","sourceRoot":"","sources":["../src/timer.ts"],"names":[],"mappings":"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"}
|
package/dist/util/array_map.js
CHANGED
|
@@ -77,6 +77,18 @@ export class ArrayMap {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
+
/** Return every present value in ascending key order. */
|
|
81
|
+
values() {
|
|
82
|
+
const out = [];
|
|
83
|
+
const backend = this._backend;
|
|
84
|
+
for (let i = 0; i < backend.length; i++) {
|
|
85
|
+
const value = backend[i];
|
|
86
|
+
if (value !== undefined) {
|
|
87
|
+
out.push(value);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
80
92
|
/** Remove all entries and reset {@link size} to zero. */
|
|
81
93
|
clear() {
|
|
82
94
|
this._backend.length = 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"array_map.js","sourceRoot":"","sources":["../../src/util/array_map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,QAAQ;IAArB;QACU,aAAQ,GAAsB,EAAE,CAAC;QACjC,UAAK,GAAW,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"array_map.js","sourceRoot":"","sources":["../../src/util/array_map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,QAAQ;IAArB;QACU,aAAQ,GAAsB,EAAE,CAAC;QACjC,UAAK,GAAW,CAAC,CAAC;IAqF5B,CAAC;IAnFC,kDAAkD;IAClD,IAAW,IAAI;QACb,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED;;;;;OAKG;IACI,GAAG,CAAC,GAAW,EAAE,KAAQ;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC;YAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACvB,CAAC;IAED;;;;OAIG;IACI,GAAG,CAAC,GAAW;QACpB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED;;;;OAIG;IACI,GAAG,CAAC,GAAW;QACpB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC;IAC1C,CAAC;IAED;;;;OAIG;IACI,MAAM,CAAC,GAAW;QACvB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC;YAC/B,OAAO,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;YACzB,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;;OAIG;IACI,OAAO,CAAC,QAA2D;QACxE,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YACzB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAED,yDAAyD;IAClD,MAAM;QACX,MAAM,GAAG,GAAQ,EAAE,CAAC;QACpB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YACzB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,yDAAyD;IAClD,KAAK;QACV,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;IACjB,CAAC;CACF"}
|