libpetri 2.7.1 → 2.9.0
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 +4 -0
- package/dist/chunk-E3ZWB645.js +27 -0
- package/dist/chunk-E3ZWB645.js.map +1 -0
- package/dist/{chunk-ULX3OG6H.js → chunk-JVI5HFRX.js} +12 -6
- package/dist/chunk-JVI5HFRX.js.map +1 -0
- package/dist/chunk-M3KT7BFO.js +2773 -0
- package/dist/chunk-M3KT7BFO.js.map +1 -0
- package/dist/debug/index.d.ts +2 -2
- package/dist/debug/index.js +2 -1
- package/dist/debug/index.js.map +1 -1
- package/dist/doclet/index.d.ts +1 -1
- package/dist/doclet/index.js +3 -2
- package/dist/doclet/index.js.map +1 -1
- package/dist/dot-exporter-SHBYMMJ3.js +9 -0
- package/dist/{event-store-8XkpYUeU.d.ts → event-store-CAkN_ayv.d.ts} +1 -1
- package/dist/export/index.d.ts +1 -1
- package/dist/export/index.js +2 -1
- package/dist/index.d.ts +67 -4
- package/dist/index.js +632 -12
- package/dist/index.js.map +1 -1
- package/dist/{petri-net-D73-PO6d.d.ts → petri-net-B4wQwsUj.d.ts} +140 -3
- package/dist/verification/index.d.ts +40 -2
- package/dist/verification/index.js +9 -508
- package/dist/verification/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-ETNHEAS6.js +0 -1458
- package/dist/chunk-ETNHEAS6.js.map +0 -1
- package/dist/chunk-ULX3OG6H.js.map +0 -1
- package/dist/dot-exporter-TYC6FUAD.js +0 -8
- /package/dist/{dot-exporter-TYC6FUAD.js.map → dot-exporter-SHBYMMJ3.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -12,11 +12,17 @@ import {
|
|
|
12
12
|
timeoutPlace,
|
|
13
13
|
xor,
|
|
14
14
|
xorPlaces
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-M3KT7BFO.js";
|
|
16
16
|
import {
|
|
17
17
|
eventTransitionName,
|
|
18
18
|
isFailureEvent
|
|
19
19
|
} from "./chunk-SXK2Z45Z.js";
|
|
20
|
+
import {
|
|
21
|
+
keyForPlace,
|
|
22
|
+
matchCorrelates,
|
|
23
|
+
matchKey,
|
|
24
|
+
matchSpec
|
|
25
|
+
} from "./chunk-E3ZWB645.js";
|
|
20
26
|
import {
|
|
21
27
|
MAX_DURATION_MS,
|
|
22
28
|
deadline,
|
|
@@ -131,6 +137,11 @@ function consumptionCount(spec, available) {
|
|
|
131
137
|
}
|
|
132
138
|
}
|
|
133
139
|
|
|
140
|
+
// src/core/name.ts
|
|
141
|
+
function nameId(value) {
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
|
|
134
145
|
// src/core/transition-action.ts
|
|
135
146
|
function passthrough() {
|
|
136
147
|
return PASSTHROUGH;
|
|
@@ -204,6 +215,7 @@ function withTimeout(action, timeoutMs, timeoutPlace2, timeoutValue) {
|
|
|
204
215
|
|
|
205
216
|
// src/core/transition-context.ts
|
|
206
217
|
var EMPTY_ALIAS = /* @__PURE__ */ new Map();
|
|
218
|
+
var GLOBAL_FRESH_NAME_COUNTER = 0;
|
|
207
219
|
var TransitionContext = class {
|
|
208
220
|
rawInput;
|
|
209
221
|
_rawOutput;
|
|
@@ -217,6 +229,7 @@ var TransitionContext = class {
|
|
|
217
229
|
executionCtx;
|
|
218
230
|
_logFn;
|
|
219
231
|
placeAlias;
|
|
232
|
+
_freshNameSupplier;
|
|
220
233
|
constructor(transitionName, rawInput, rawOutput, inputPlaces, readPlaces, outputPlaces, executionContext, logFn, placeAlias) {
|
|
221
234
|
this._transitionName = transitionName;
|
|
222
235
|
this.rawInput = rawInput;
|
|
@@ -358,6 +371,28 @@ var TransitionContext = class {
|
|
|
358
371
|
);
|
|
359
372
|
}
|
|
360
373
|
}
|
|
374
|
+
// ==================== ν-name minting (NU-010) ====================
|
|
375
|
+
/**
|
|
376
|
+
* @internal Installs the ν-name minter. Wired by the executor at firing time
|
|
377
|
+
* so names minted by {@link freshName} are monotonic across the run and
|
|
378
|
+
* instance-prefixed (spec NU-010, NU-030).
|
|
379
|
+
*/
|
|
380
|
+
setFreshNameSupplier(supplier) {
|
|
381
|
+
this._freshNameSupplier = supplier;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Mints a fresh ν-name (the ν-binder primitive — spec NU-010).
|
|
385
|
+
*
|
|
386
|
+
* An action calls this on the fork side to create a correlation id, then
|
|
387
|
+
* writes it into the sibling output payloads; a later join correlates those
|
|
388
|
+
* siblings via a {@link import('./match-spec.js').MatchSpec}. Uses the
|
|
389
|
+
* executor-installed minter when present; otherwise falls back to a
|
|
390
|
+
* process-global counter prefixed by the transition name.
|
|
391
|
+
*/
|
|
392
|
+
freshName() {
|
|
393
|
+
if (this._freshNameSupplier) return this._freshNameSupplier();
|
|
394
|
+
return nameId(`${this._transitionName}#${GLOBAL_FRESH_NAME_COUNTER++}`);
|
|
395
|
+
}
|
|
361
396
|
// ==================== Structure Info ====================
|
|
362
397
|
/** Returns the transition name. */
|
|
363
398
|
transitionName() {
|
|
@@ -472,6 +507,11 @@ var Transition = class {
|
|
|
472
507
|
actionTimeout;
|
|
473
508
|
action;
|
|
474
509
|
priority;
|
|
510
|
+
/**
|
|
511
|
+
* ν-net join correlation: a subset of `inputSpecs` that must be correlated by
|
|
512
|
+
* name equality on firing (spec NU-020). `null` for ordinary transitions.
|
|
513
|
+
*/
|
|
514
|
+
matchSpec;
|
|
475
515
|
/**
|
|
476
516
|
* Per-transition **declared → actual** place correspondence (per
|
|
477
517
|
* **MOD-031**), keyed by the author-original declared place **name** →
|
|
@@ -488,7 +528,7 @@ var Transition = class {
|
|
|
488
528
|
_readPlaces;
|
|
489
529
|
_outputPlaces;
|
|
490
530
|
/** @internal Use {@link Transition.builder} to create instances. */
|
|
491
|
-
constructor(key, name, inputSpecs, outputSpec, inhibitors, reads, resets, timing, action, priority, placeAlias = EMPTY_PLACE_ALIAS) {
|
|
531
|
+
constructor(key, name, inputSpecs, outputSpec, inhibitors, reads, resets, timing, action, priority, placeAlias = EMPTY_PLACE_ALIAS, matchSpec2 = null) {
|
|
492
532
|
if (key !== TRANSITION_KEY) throw new Error("Use Transition.builder() to create instances");
|
|
493
533
|
this.name = name;
|
|
494
534
|
this.inputSpecs = inputSpecs;
|
|
@@ -501,6 +541,7 @@ var Transition = class {
|
|
|
501
541
|
this.action = action;
|
|
502
542
|
this.priority = priority;
|
|
503
543
|
this.placeAlias = placeAlias.size === 0 ? EMPTY_PLACE_ALIAS : placeAlias;
|
|
544
|
+
this.matchSpec = matchSpec2;
|
|
504
545
|
const inputPlaces = /* @__PURE__ */ new Set();
|
|
505
546
|
for (const spec of inputSpecs) {
|
|
506
547
|
inputPlaces.add(spec.place);
|
|
@@ -553,6 +594,7 @@ var TransitionBuilder = class {
|
|
|
553
594
|
_action = passthrough();
|
|
554
595
|
_priority = 0;
|
|
555
596
|
_placeAlias = EMPTY_PLACE_ALIAS;
|
|
597
|
+
_matchSpec = null;
|
|
556
598
|
constructor(name) {
|
|
557
599
|
this._name = name;
|
|
558
600
|
}
|
|
@@ -617,6 +659,15 @@ var TransitionBuilder = class {
|
|
|
617
659
|
this._priority = priority;
|
|
618
660
|
return this;
|
|
619
661
|
}
|
|
662
|
+
/**
|
|
663
|
+
* Sets the ν-net join correlation spec: the named input places must be
|
|
664
|
+
* correlated by name equality on firing (spec NU-020). Every place referenced
|
|
665
|
+
* by the spec must also be declared as an input.
|
|
666
|
+
*/
|
|
667
|
+
match(spec) {
|
|
668
|
+
this._matchSpec = spec;
|
|
669
|
+
return this;
|
|
670
|
+
}
|
|
620
671
|
/**
|
|
621
672
|
* Sets the per-transition declared→actual place correspondence (per
|
|
622
673
|
* **MOD-031**). Populated by the subnet rewriter during the compose-time
|
|
@@ -638,6 +689,16 @@ var TransitionBuilder = class {
|
|
|
638
689
|
}
|
|
639
690
|
}
|
|
640
691
|
}
|
|
692
|
+
if (this._matchSpec !== null) {
|
|
693
|
+
const inputPlaceNames = new Set(this._inputSpecs.map((s) => s.place.name));
|
|
694
|
+
for (const k of this._matchSpec.keys) {
|
|
695
|
+
if (!inputPlaceNames.has(k.place.name)) {
|
|
696
|
+
throw new Error(
|
|
697
|
+
`Transition '${this._name}': MatchSpec correlates non-input place '${k.place.name}'`
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
641
702
|
return new Transition(
|
|
642
703
|
TRANSITION_KEY,
|
|
643
704
|
this._name,
|
|
@@ -649,7 +710,8 @@ var TransitionBuilder = class {
|
|
|
649
710
|
this._timing,
|
|
650
711
|
this._action,
|
|
651
712
|
this._priority,
|
|
652
|
-
this._placeAlias
|
|
713
|
+
this._placeAlias,
|
|
714
|
+
this._matchSpec
|
|
653
715
|
);
|
|
654
716
|
}
|
|
655
717
|
};
|
|
@@ -967,6 +1029,14 @@ function rebuildWithName(t, name, remap) {
|
|
|
967
1029
|
for (let i = 0; i < t.resets.length; i++) {
|
|
968
1030
|
builder.reset(rewriteReset(t.resets[i], remap).place);
|
|
969
1031
|
}
|
|
1032
|
+
if (t.matchSpec !== null) {
|
|
1033
|
+
builder.match({
|
|
1034
|
+
keys: t.matchSpec.keys.map((k) => ({
|
|
1035
|
+
place: remap.get(k.place.name) ?? k.place,
|
|
1036
|
+
key: k.key
|
|
1037
|
+
}))
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
970
1040
|
return builder.build();
|
|
971
1041
|
}
|
|
972
1042
|
function buildPlaceAlias(t, remap) {
|
|
@@ -2298,7 +2368,8 @@ var Marking = class _Marking {
|
|
|
2298
2368
|
for (let i = 0; i < queue.length; i++) {
|
|
2299
2369
|
const token = queue[i];
|
|
2300
2370
|
if (spec.guard(token.value)) {
|
|
2301
|
-
|
|
2371
|
+
if (i === 0) queue.shift();
|
|
2372
|
+
else queue.splice(i, 1);
|
|
2302
2373
|
return token;
|
|
2303
2374
|
}
|
|
2304
2375
|
}
|
|
@@ -2387,6 +2458,10 @@ var CompiledNet = class _CompiledNet {
|
|
|
2387
2458
|
// Cardinality and guard flags
|
|
2388
2459
|
_cardinalityChecks;
|
|
2389
2460
|
_hasGuards;
|
|
2461
|
+
// ν-net join flag: the transition carries a MatchSpec (NU-020). Precomputed
|
|
2462
|
+
// so the hot enablement loop can skip the match check on non-ν transitions
|
|
2463
|
+
// (zero-cost gating), mirroring `_hasGuards`.
|
|
2464
|
+
_hasMatch;
|
|
2390
2465
|
constructor(net) {
|
|
2391
2466
|
this.net = net;
|
|
2392
2467
|
const allPlacesSet = /* @__PURE__ */ new Map();
|
|
@@ -2418,12 +2493,14 @@ var CompiledNet = class _CompiledNet {
|
|
|
2418
2493
|
this._consumptionPlaceIds = new Array(this.transitionCount);
|
|
2419
2494
|
this._cardinalityChecks = new Array(this.transitionCount).fill(null);
|
|
2420
2495
|
this._hasGuards = new Array(this.transitionCount).fill(false);
|
|
2496
|
+
this._hasMatch = new Array(this.transitionCount).fill(false);
|
|
2421
2497
|
const placeToTransitionsList = new Array(this.placeCount);
|
|
2422
2498
|
for (let i = 0; i < this.placeCount; i++) {
|
|
2423
2499
|
placeToTransitionsList[i] = [];
|
|
2424
2500
|
}
|
|
2425
2501
|
for (let tid = 0; tid < this.transitionCount; tid++) {
|
|
2426
2502
|
const t = this._transitionsById[tid];
|
|
2503
|
+
this._hasMatch[tid] = t.matchSpec !== null;
|
|
2427
2504
|
const needs = new Uint32Array(this.wordCount);
|
|
2428
2505
|
const inhibitors = new Uint32Array(this.wordCount);
|
|
2429
2506
|
let needsCardinality = false;
|
|
@@ -2505,6 +2582,9 @@ var CompiledNet = class _CompiledNet {
|
|
|
2505
2582
|
hasGuards(tid) {
|
|
2506
2583
|
return this._hasGuards[tid];
|
|
2507
2584
|
}
|
|
2585
|
+
hasMatch(tid) {
|
|
2586
|
+
return this._hasMatch[tid];
|
|
2587
|
+
}
|
|
2508
2588
|
// ==================== Enablement Check ====================
|
|
2509
2589
|
/**
|
|
2510
2590
|
* Two-phase bitmap enablement check for a transition:
|
|
@@ -2609,6 +2689,224 @@ function inMemoryEventStore() {
|
|
|
2609
2689
|
return new InMemoryEventStore();
|
|
2610
2690
|
}
|
|
2611
2691
|
|
|
2692
|
+
// src/runtime/match-engine.ts
|
|
2693
|
+
function selectMatchName(perPlace, requireds) {
|
|
2694
|
+
if (perPlace.length === 0) return null;
|
|
2695
|
+
let seed = 0;
|
|
2696
|
+
for (let i = 1; i < perPlace.length; i++) {
|
|
2697
|
+
if (perPlace[i].size < perPlace[seed].size) seed = i;
|
|
2698
|
+
}
|
|
2699
|
+
let bestName = null;
|
|
2700
|
+
let bestTs = 0;
|
|
2701
|
+
for (const [name, stat] of perPlace[seed]) {
|
|
2702
|
+
if (stat.count < requireds[seed]) continue;
|
|
2703
|
+
let repTs = Number.MAX_SAFE_INTEGER;
|
|
2704
|
+
let satisfied = true;
|
|
2705
|
+
for (let j = 0; j < perPlace.length; j++) {
|
|
2706
|
+
const s = perPlace[j].get(name);
|
|
2707
|
+
if (s === void 0 || s.count < requireds[j]) {
|
|
2708
|
+
satisfied = false;
|
|
2709
|
+
break;
|
|
2710
|
+
}
|
|
2711
|
+
repTs = Math.min(repTs, s.minCreatedAt);
|
|
2712
|
+
}
|
|
2713
|
+
if (!satisfied) continue;
|
|
2714
|
+
const take = bestName === null || repTs < bestTs || repTs === bestTs && name < bestName;
|
|
2715
|
+
if (take) {
|
|
2716
|
+
bestName = name;
|
|
2717
|
+
bestTs = repTs;
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
return bestName;
|
|
2721
|
+
}
|
|
2722
|
+
function buildNameIndex(tokens, key, guard) {
|
|
2723
|
+
const index = /* @__PURE__ */ new Map();
|
|
2724
|
+
for (const token of tokens) {
|
|
2725
|
+
if (guard && !guard(token.value)) continue;
|
|
2726
|
+
const name = key(token.value);
|
|
2727
|
+
if (name === void 0 || name === null) continue;
|
|
2728
|
+
const ts = token.createdAt;
|
|
2729
|
+
const prev = index.get(name);
|
|
2730
|
+
if (prev) {
|
|
2731
|
+
prev.count++;
|
|
2732
|
+
if (ts < prev.minCreatedAt) prev.minCreatedAt = ts;
|
|
2733
|
+
} else {
|
|
2734
|
+
index.set(name, { count: 1, minCreatedAt: ts });
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
return index;
|
|
2738
|
+
}
|
|
2739
|
+
function requiredFor(t, placeName) {
|
|
2740
|
+
for (const inSpec of t.inputSpecs) {
|
|
2741
|
+
if (inSpec.place.name === placeName) {
|
|
2742
|
+
if (inSpec.type === "exactly") return inSpec.count;
|
|
2743
|
+
if (inSpec.type === "at-least") return inSpec.minimum;
|
|
2744
|
+
return 1;
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
return 1;
|
|
2748
|
+
}
|
|
2749
|
+
function findBinding(t, getTokens) {
|
|
2750
|
+
const ms = t.matchSpec;
|
|
2751
|
+
if (!ms) return null;
|
|
2752
|
+
const perPlace = [];
|
|
2753
|
+
const requireds = [];
|
|
2754
|
+
for (const k of ms.keys) {
|
|
2755
|
+
perPlace.push(buildNameIndex(getTokens(k.place), k.key, guardFor(t, k.place.name)));
|
|
2756
|
+
requireds.push(requiredFor(t, k.place.name));
|
|
2757
|
+
}
|
|
2758
|
+
return selectMatchName(perPlace, requireds);
|
|
2759
|
+
}
|
|
2760
|
+
function guardFor(t, placeName) {
|
|
2761
|
+
for (const inSpec of t.inputSpecs) {
|
|
2762
|
+
if (inSpec.place.name === placeName) return inSpec.guard;
|
|
2763
|
+
}
|
|
2764
|
+
return void 0;
|
|
2765
|
+
}
|
|
2766
|
+
var MinQueue = class {
|
|
2767
|
+
inStack = [];
|
|
2768
|
+
inMin = [];
|
|
2769
|
+
outStack = [];
|
|
2770
|
+
outMin = [];
|
|
2771
|
+
get size() {
|
|
2772
|
+
return this.inStack.length + this.outStack.length;
|
|
2773
|
+
}
|
|
2774
|
+
pushBack(v) {
|
|
2775
|
+
this.inStack.push(v);
|
|
2776
|
+
const top = this.inMin.length;
|
|
2777
|
+
this.inMin.push(top ? Math.min(this.inMin[top - 1], v) : v);
|
|
2778
|
+
}
|
|
2779
|
+
popFront() {
|
|
2780
|
+
if (this.outStack.length === 0) {
|
|
2781
|
+
while (this.inStack.length) {
|
|
2782
|
+
const v = this.inStack.pop();
|
|
2783
|
+
this.inMin.pop();
|
|
2784
|
+
this.outStack.push(v);
|
|
2785
|
+
const top = this.outMin.length;
|
|
2786
|
+
this.outMin.push(top ? Math.min(this.outMin[top - 1], v) : v);
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
this.outStack.pop();
|
|
2790
|
+
this.outMin.pop();
|
|
2791
|
+
}
|
|
2792
|
+
min() {
|
|
2793
|
+
const a = this.inMin.length ? this.inMin[this.inMin.length - 1] : Infinity;
|
|
2794
|
+
const b = this.outMin.length ? this.outMin[this.outMin.length - 1] : Infinity;
|
|
2795
|
+
return a < b ? a : b;
|
|
2796
|
+
}
|
|
2797
|
+
};
|
|
2798
|
+
var ReadyHeap = class {
|
|
2799
|
+
a = [];
|
|
2800
|
+
get size() {
|
|
2801
|
+
return this.a.length;
|
|
2802
|
+
}
|
|
2803
|
+
peek() {
|
|
2804
|
+
return this.a[0];
|
|
2805
|
+
}
|
|
2806
|
+
less(x, y) {
|
|
2807
|
+
return x.ts < y.ts || x.ts === y.ts && x.name < y.name;
|
|
2808
|
+
}
|
|
2809
|
+
push(e) {
|
|
2810
|
+
const a = this.a;
|
|
2811
|
+
a.push(e);
|
|
2812
|
+
let i = a.length - 1;
|
|
2813
|
+
while (i > 0) {
|
|
2814
|
+
const p = i - 1 >> 1;
|
|
2815
|
+
if (this.less(a[i], a[p])) {
|
|
2816
|
+
const tmp = a[i];
|
|
2817
|
+
a[i] = a[p];
|
|
2818
|
+
a[p] = tmp;
|
|
2819
|
+
i = p;
|
|
2820
|
+
} else break;
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
pop() {
|
|
2824
|
+
const a = this.a;
|
|
2825
|
+
const last = a.pop();
|
|
2826
|
+
if (a.length === 0) return;
|
|
2827
|
+
a[0] = last;
|
|
2828
|
+
let i = 0;
|
|
2829
|
+
const n = a.length;
|
|
2830
|
+
for (; ; ) {
|
|
2831
|
+
const l = 2 * i + 1;
|
|
2832
|
+
const r = 2 * i + 2;
|
|
2833
|
+
let m = i;
|
|
2834
|
+
if (l < n && this.less(a[l], a[m])) m = l;
|
|
2835
|
+
if (r < n && this.less(a[r], a[m])) m = r;
|
|
2836
|
+
if (m === i) break;
|
|
2837
|
+
const tmp = a[i];
|
|
2838
|
+
a[i] = a[m];
|
|
2839
|
+
a[m] = tmp;
|
|
2840
|
+
i = m;
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
};
|
|
2844
|
+
var IncrementalMatcher = class {
|
|
2845
|
+
constructor(requireds) {
|
|
2846
|
+
this.requireds = requireds;
|
|
2847
|
+
this.ts = requireds.map(() => /* @__PURE__ */ new Map());
|
|
2848
|
+
}
|
|
2849
|
+
ts;
|
|
2850
|
+
heap = new ReadyHeap();
|
|
2851
|
+
ready = /* @__PURE__ */ new Map();
|
|
2852
|
+
/** Add one (already guard-passing) token carrying `name` at `createdAt` to input `i`. */
|
|
2853
|
+
add(i, name, createdAt) {
|
|
2854
|
+
let q = this.ts[i].get(name);
|
|
2855
|
+
if (!q) {
|
|
2856
|
+
q = new MinQueue();
|
|
2857
|
+
this.ts[i].set(name, q);
|
|
2858
|
+
}
|
|
2859
|
+
q.pushBack(createdAt);
|
|
2860
|
+
this.refresh(name);
|
|
2861
|
+
this.cleanTop();
|
|
2862
|
+
}
|
|
2863
|
+
/** Remove the `required` earliest-inserted tokens of `name` from every input (NU-020). */
|
|
2864
|
+
consume(name) {
|
|
2865
|
+
for (let i = 0; i < this.requireds.length; i++) {
|
|
2866
|
+
const q = this.ts[i].get(name);
|
|
2867
|
+
if (q) {
|
|
2868
|
+
for (let r = 0; r < this.requireds[i]; r++) q.popFront();
|
|
2869
|
+
if (q.size === 0) this.ts[i].delete(name);
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
this.refresh(name);
|
|
2873
|
+
this.cleanTop();
|
|
2874
|
+
}
|
|
2875
|
+
/** The name `selectMatchName` would pick, or `null`. O(1) read. */
|
|
2876
|
+
best() {
|
|
2877
|
+
const top = this.heap.peek();
|
|
2878
|
+
return top ? top.name : null;
|
|
2879
|
+
}
|
|
2880
|
+
repTs(name) {
|
|
2881
|
+
let rep = Infinity;
|
|
2882
|
+
for (let i = 0; i < this.requireds.length; i++) {
|
|
2883
|
+
const q = this.ts[i].get(name);
|
|
2884
|
+
if (!q || q.size < this.requireds[i]) return null;
|
|
2885
|
+
const m = q.min();
|
|
2886
|
+
if (m < rep) rep = m;
|
|
2887
|
+
}
|
|
2888
|
+
return rep;
|
|
2889
|
+
}
|
|
2890
|
+
refresh(name) {
|
|
2891
|
+
const rep = this.repTs(name);
|
|
2892
|
+
if (rep !== null) {
|
|
2893
|
+
if (this.ready.get(name) !== rep) {
|
|
2894
|
+
this.ready.set(name, rep);
|
|
2895
|
+
this.heap.push({ ts: rep, name });
|
|
2896
|
+
}
|
|
2897
|
+
} else {
|
|
2898
|
+
this.ready.delete(name);
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
cleanTop() {
|
|
2902
|
+
for (; ; ) {
|
|
2903
|
+
const top = this.heap.peek();
|
|
2904
|
+
if (top && this.ready.get(top.name) !== top.ts) this.heap.pop();
|
|
2905
|
+
else break;
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
};
|
|
2909
|
+
|
|
2612
2910
|
// src/runtime/out-violation-error.ts
|
|
2613
2911
|
var OutViolationError = class extends Error {
|
|
2614
2912
|
constructor(message) {
|
|
@@ -2682,6 +2980,18 @@ function produceTimeoutOutput(context, timeoutChild) {
|
|
|
2682
2980
|
var BitmapNetExecutor = class {
|
|
2683
2981
|
compiled;
|
|
2684
2982
|
marking;
|
|
2983
|
+
/** Monotonic source for ν-name minting (ctx.freshName(), NU-010). */
|
|
2984
|
+
freshNameCounter = 0;
|
|
2985
|
+
/**
|
|
2986
|
+
* ν-net incremental match caches (NU-020): per matched transition, an
|
|
2987
|
+
* {@link IncrementalMatcher} kept in lockstep with the marking when the
|
|
2988
|
+
* transition is fast-path eligible (every correlated input is `one`/`exactly`,
|
|
2989
|
+
* consumed by no other transition, never reset), else `null` → fall back to
|
|
2990
|
+
* the O(n) rebuild {@link findBinding}. Turns a draining matched join from
|
|
2991
|
+
* O(n²) into O(n log n). Mirrors the precompiled executor and the Rust backends.
|
|
2992
|
+
*/
|
|
2993
|
+
matchCaches = [];
|
|
2994
|
+
placeMatchTargets = [];
|
|
2685
2995
|
eventStore;
|
|
2686
2996
|
environmentPlaces;
|
|
2687
2997
|
hasEnvironmentPlaces;
|
|
@@ -2776,6 +3086,90 @@ var BitmapNetExecutor = class {
|
|
|
2776
3086
|
for (const spec of t.inputSpecs) names.add(spec.place.name);
|
|
2777
3087
|
this.transitionInputPlaceNames.set(t, names);
|
|
2778
3088
|
}
|
|
3089
|
+
this.initMatchCaches();
|
|
3090
|
+
}
|
|
3091
|
+
/**
|
|
3092
|
+
* Builds the ν-net incremental match caches (NU-020). A matched join is
|
|
3093
|
+
* fast-path eligible only when every correlated input is `one`/`exactly`, is
|
|
3094
|
+
* consumed by no other transition, and is never reset — so the cache can never
|
|
3095
|
+
* desync from the marking. Mirrors the precompiled executor.
|
|
3096
|
+
*/
|
|
3097
|
+
initMatchCaches() {
|
|
3098
|
+
const compiled = this.compiled;
|
|
3099
|
+
const tc = compiled.transitionCount;
|
|
3100
|
+
const pc = compiled.placeCount;
|
|
3101
|
+
this.matchCaches = new Array(tc).fill(null);
|
|
3102
|
+
this.placeMatchTargets = Array.from({ length: pc }, () => []);
|
|
3103
|
+
let anyMatch = false;
|
|
3104
|
+
for (let tid = 0; tid < tc; tid++) {
|
|
3105
|
+
if (compiled.hasMatch(tid)) {
|
|
3106
|
+
anyMatch = true;
|
|
3107
|
+
break;
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
if (!anyMatch) return;
|
|
3111
|
+
const inputConsumers = Array.from({ length: pc }, () => []);
|
|
3112
|
+
const resetTarget = new Array(pc).fill(false);
|
|
3113
|
+
for (let tid = 0; tid < tc; tid++) {
|
|
3114
|
+
const t = compiled.transition(tid);
|
|
3115
|
+
for (const spec of t.inputSpecs) inputConsumers[compiled.placeId(spec.place)].push(tid);
|
|
3116
|
+
for (const arc of t.resets) resetTarget[compiled.placeId(arc.place)] = true;
|
|
3117
|
+
}
|
|
3118
|
+
for (let tid = 0; tid < tc; tid++) {
|
|
3119
|
+
if (!compiled.hasMatch(tid)) continue;
|
|
3120
|
+
const t = compiled.transition(tid);
|
|
3121
|
+
const ms = t.matchSpec;
|
|
3122
|
+
if (!ms) continue;
|
|
3123
|
+
const requireds = [];
|
|
3124
|
+
let eligible = true;
|
|
3125
|
+
for (const mk of ms.keys) {
|
|
3126
|
+
const pid = compiled.placeId(mk.place);
|
|
3127
|
+
const spec = t.inputSpecs.find((s) => s.place.name === mk.place.name);
|
|
3128
|
+
let required;
|
|
3129
|
+
if (spec?.type === "one") required = 1;
|
|
3130
|
+
else if (spec?.type === "exactly") required = spec.count;
|
|
3131
|
+
else {
|
|
3132
|
+
eligible = false;
|
|
3133
|
+
break;
|
|
3134
|
+
}
|
|
3135
|
+
const cons = inputConsumers[pid];
|
|
3136
|
+
if (resetTarget[pid] || cons.length !== 1 || cons[0] !== tid) {
|
|
3137
|
+
eligible = false;
|
|
3138
|
+
break;
|
|
3139
|
+
}
|
|
3140
|
+
requireds.push(required);
|
|
3141
|
+
}
|
|
3142
|
+
if (!eligible) continue;
|
|
3143
|
+
const matcher = new IncrementalMatcher(requireds);
|
|
3144
|
+
for (let keyIdx = 0; keyIdx < ms.keys.length; keyIdx++) {
|
|
3145
|
+
const mk = ms.keys[keyIdx];
|
|
3146
|
+
const pid = compiled.placeId(mk.place);
|
|
3147
|
+
const guard = t.inputSpecs.find((s) => s.place.name === mk.place.name)?.guard;
|
|
3148
|
+
for (const token of this.marking.peekTokens(mk.place)) {
|
|
3149
|
+
if (guard && !guard(token.value)) continue;
|
|
3150
|
+
const name = mk.key(token.value);
|
|
3151
|
+
if (name !== void 0 && name !== null) matcher.add(keyIdx, name, token.createdAt);
|
|
3152
|
+
}
|
|
3153
|
+
this.placeMatchTargets[pid].push([tid, keyIdx]);
|
|
3154
|
+
}
|
|
3155
|
+
this.matchCaches[tid] = matcher;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
/** Mirror a token added to correlated input `pid` into every fast-path matcher. */
|
|
3159
|
+
cacheAddToken(pid, token) {
|
|
3160
|
+
const targets = this.placeMatchTargets[pid];
|
|
3161
|
+
if (targets === void 0 || targets.length === 0) return;
|
|
3162
|
+
const compiled = this.compiled;
|
|
3163
|
+
for (const [tid, keyIdx] of targets) {
|
|
3164
|
+
const cache = this.matchCaches[tid];
|
|
3165
|
+
if (cache == null) continue;
|
|
3166
|
+
const t = compiled.transition(tid);
|
|
3167
|
+
const mk = t.matchSpec.keys[keyIdx];
|
|
3168
|
+
const guard = t.inputSpecs.find((s) => s.place.name === mk.place.name)?.guard;
|
|
3169
|
+
if (guard && !guard(token.value)) continue;
|
|
3170
|
+
const name = mk.key(token.value);
|
|
3171
|
+
if (name !== void 0 && name !== null) cache.add(keyIdx, name, token.createdAt);
|
|
3172
|
+
}
|
|
2779
3173
|
}
|
|
2780
3174
|
// ======================== Execution ========================
|
|
2781
3175
|
async run(timeoutMs) {
|
|
@@ -2974,12 +3368,22 @@ var BitmapNetExecutor = class {
|
|
|
2974
3368
|
}
|
|
2975
3369
|
if (this.compiled.hasGuards(tid)) {
|
|
2976
3370
|
const t = this.compiled.transition(tid);
|
|
3371
|
+
const cache = this.matchCaches[tid];
|
|
3372
|
+
const ms = t.matchSpec;
|
|
2977
3373
|
for (const spec of t.inputSpecs) {
|
|
2978
3374
|
if (!spec.guard) continue;
|
|
3375
|
+
if (cache != null && ms && keyForPlace(ms, spec.place.name) !== void 0) continue;
|
|
2979
3376
|
const requiredCount2 = spec.type === "one" ? 1 : spec.type === "exactly" ? spec.count : spec.type === "at-least" ? spec.minimum : 1;
|
|
2980
3377
|
if (this.marking.countMatching(spec) < requiredCount2) return false;
|
|
2981
3378
|
}
|
|
2982
3379
|
}
|
|
3380
|
+
if (this.compiled.hasMatch(tid)) {
|
|
3381
|
+
const cache = this.matchCaches[tid];
|
|
3382
|
+
const noBinding = cache != null ? cache.best() === null : findBinding(this.compiled.transition(tid), (p) => this.marking.peekTokens(p)) === null;
|
|
3383
|
+
if (noBinding) {
|
|
3384
|
+
return false;
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
2983
3387
|
return true;
|
|
2984
3388
|
}
|
|
2985
3389
|
hasInputFromResetPlace(t) {
|
|
@@ -3057,7 +3461,22 @@ var BitmapNetExecutor = class {
|
|
|
3057
3461
|
const t = this.compiled.transition(tid);
|
|
3058
3462
|
const inputs = new TokenInput();
|
|
3059
3463
|
const consumed = [];
|
|
3464
|
+
const ms = t.matchSpec;
|
|
3465
|
+
const cache = ms ? this.matchCaches[tid] : null;
|
|
3466
|
+
const chosen = ms ? cache != null ? cache.best() : findBinding(t, (p) => this.marking.peekTokens(p)) : null;
|
|
3467
|
+
if (cache != null && chosen !== null) cache.consume(chosen);
|
|
3060
3468
|
for (const inSpec of t.inputSpecs) {
|
|
3469
|
+
const keyFn = ms ? keyForPlace(ms, inSpec.place.name) : void 0;
|
|
3470
|
+
let spec;
|
|
3471
|
+
if (keyFn && chosen !== null) {
|
|
3472
|
+
const baseGuard = inSpec.guard;
|
|
3473
|
+
spec = {
|
|
3474
|
+
place: inSpec.place,
|
|
3475
|
+
guard: (v) => (baseGuard ? baseGuard(v) : true) && keyFn(v) === chosen
|
|
3476
|
+
};
|
|
3477
|
+
} else {
|
|
3478
|
+
spec = inSpec;
|
|
3479
|
+
}
|
|
3061
3480
|
let toConsume;
|
|
3062
3481
|
switch (inSpec.type) {
|
|
3063
3482
|
case "one":
|
|
@@ -3067,14 +3486,12 @@ var BitmapNetExecutor = class {
|
|
|
3067
3486
|
toConsume = inSpec.count;
|
|
3068
3487
|
break;
|
|
3069
3488
|
case "all":
|
|
3070
|
-
toConsume = inSpec.guard ? this.marking.countMatching(inSpec) : this.marking.tokenCount(inSpec.place);
|
|
3071
|
-
break;
|
|
3072
3489
|
case "at-least":
|
|
3073
|
-
toConsume =
|
|
3490
|
+
toConsume = spec.guard ? this.marking.countMatching(spec) : this.marking.tokenCount(inSpec.place);
|
|
3074
3491
|
break;
|
|
3075
3492
|
}
|
|
3076
3493
|
for (let i = 0; i < toConsume; i++) {
|
|
3077
|
-
const token =
|
|
3494
|
+
const token = spec.guard ? this.marking.removeFirstMatching(spec) : this.marking.removeFirst(inSpec.place);
|
|
3078
3495
|
if (token === null) break;
|
|
3079
3496
|
consumed.push(token);
|
|
3080
3497
|
inputs.add(inSpec.place, token);
|
|
@@ -3136,6 +3553,8 @@ var BitmapNetExecutor = class {
|
|
|
3136
3553
|
logFn,
|
|
3137
3554
|
t.placeAlias
|
|
3138
3555
|
);
|
|
3556
|
+
const freshNameBase = t.name;
|
|
3557
|
+
context.setFreshNameSupplier(() => nameId(`${freshNameBase}#${this.freshNameCounter++}`));
|
|
3139
3558
|
let actionPromise = t.action(context);
|
|
3140
3559
|
if (t.hasActionTimeout()) {
|
|
3141
3560
|
const timeoutSpec = t.actionTimeout;
|
|
@@ -3237,9 +3656,10 @@ var BitmapNetExecutor = class {
|
|
|
3237
3656
|
}
|
|
3238
3657
|
const produced = [];
|
|
3239
3658
|
for (const entry of outputs.entries()) {
|
|
3659
|
+
const pid = this.compiled.placeId(entry.place);
|
|
3660
|
+
this.cacheAddToken(pid, entry.token);
|
|
3240
3661
|
this.marking.addToken(entry.place, entry.token);
|
|
3241
3662
|
produced.push(entry.token);
|
|
3242
|
-
const pid = this.compiled.placeId(entry.place);
|
|
3243
3663
|
setBit(this.markedPlaces, pid);
|
|
3244
3664
|
this.markDirty(pid);
|
|
3245
3665
|
this.emitEvent({
|
|
@@ -3280,8 +3700,9 @@ var BitmapNetExecutor = class {
|
|
|
3280
3700
|
for (let i = 0; i < len; i++) {
|
|
3281
3701
|
const event = this.externalQueue[i];
|
|
3282
3702
|
try {
|
|
3283
|
-
this.marking.addToken(event.place, event.token);
|
|
3284
3703
|
const pid = this.compiled.placeId(event.place);
|
|
3704
|
+
this.cacheAddToken(pid, event.token);
|
|
3705
|
+
this.marking.addToken(event.place, event.token);
|
|
3285
3706
|
setBit(this.markedPlaces, pid);
|
|
3286
3707
|
this.markDirty(pid);
|
|
3287
3708
|
this.emitEvent({
|
|
@@ -3488,6 +3909,9 @@ var PrecompiledNet = class _PrecompiledNet {
|
|
|
3488
3909
|
// ==================== Cardinality & Guards ====================
|
|
3489
3910
|
cardinalityChecks;
|
|
3490
3911
|
hasGuards;
|
|
3912
|
+
// ν-net join flag (NU-020): gates the match-binding check off the hot
|
|
3913
|
+
// enablement path for non-ν transitions, mirroring `hasGuards`.
|
|
3914
|
+
hasMatch;
|
|
3491
3915
|
// ==================== Global Flags ====================
|
|
3492
3916
|
allImmediate;
|
|
3493
3917
|
allSamePriority;
|
|
@@ -3582,12 +4006,15 @@ var PrecompiledNet = class _PrecompiledNet {
|
|
|
3582
4006
|
this.consumptionPlaceIds = consumptionPlaceIds;
|
|
3583
4007
|
const cardinalityChecks = new Array(tc);
|
|
3584
4008
|
const hasGuards = new Array(tc);
|
|
4009
|
+
const hasMatch = new Array(tc);
|
|
3585
4010
|
for (let tid = 0; tid < tc; tid++) {
|
|
3586
4011
|
cardinalityChecks[tid] = compiled.cardinalityCheck(tid);
|
|
3587
4012
|
hasGuards[tid] = compiled.hasGuards(tid);
|
|
4013
|
+
hasMatch[tid] = compiled.hasMatch(tid);
|
|
3588
4014
|
}
|
|
3589
4015
|
this.cardinalityChecks = cardinalityChecks;
|
|
3590
4016
|
this.hasGuards = hasGuards;
|
|
4017
|
+
this.hasMatch = hasMatch;
|
|
3591
4018
|
this.earliestMs = new Float64Array(tc);
|
|
3592
4019
|
this.latestMs = new Float64Array(tc);
|
|
3593
4020
|
this.hasDeadline = new Uint8Array(tc);
|
|
@@ -3783,6 +4210,28 @@ var PrecompiledNetExecutor = class {
|
|
|
3783
4210
|
// ==================== Token Storage ====================
|
|
3784
4211
|
/** Per-place token arrays, indexed by pid. */
|
|
3785
4212
|
tokenQueues;
|
|
4213
|
+
/** Monotonic source for ν-name minting (ctx.freshName(), NU-010). */
|
|
4214
|
+
freshNameCounter = 0;
|
|
4215
|
+
/**
|
|
4216
|
+
* Per-transition ν-name minter cache, indexed by tid (built lazily). Each
|
|
4217
|
+
* supplier is created once and reused across firings, so installing it on a
|
|
4218
|
+
* per-fire context is a field assignment rather than a per-fire closure
|
|
4219
|
+
* allocation — keeping the fire path lean for non-ν transitions. Every
|
|
4220
|
+
* transition gets one (forks mint but carry no MatchSpec, so gating on
|
|
4221
|
+
* `hasMatch` would wrongly starve them).
|
|
4222
|
+
*/
|
|
4223
|
+
freshNameSuppliers = [];
|
|
4224
|
+
// ==================== ν-net incremental match caches (NU-020) ====================
|
|
4225
|
+
/**
|
|
4226
|
+
* Per matched transition: an {@link IncrementalMatcher} kept in lockstep with
|
|
4227
|
+
* the token queues when the transition is fast-path eligible (every correlated
|
|
4228
|
+
* input is `one`/`exactly`, consumed by no other transition, and never reset),
|
|
4229
|
+
* else `null` → fall back to the O(n) rebuild {@link findBinding}. This turns a
|
|
4230
|
+
* draining matched join from O(n²) into O(n log n).
|
|
4231
|
+
*/
|
|
4232
|
+
matchCaches = [];
|
|
4233
|
+
/** Per place (by pid): the `[tid, keyIndex]` of every fast-path correlated input it feeds. */
|
|
4234
|
+
placeMatchTargets = [];
|
|
3786
4235
|
// ==================== Marking Bitmap ====================
|
|
3787
4236
|
markingBitmap;
|
|
3788
4237
|
// ==================== Transition State ====================
|
|
@@ -3869,6 +4318,92 @@ var PrecompiledNetExecutor = class {
|
|
|
3869
4318
|
this.pendingResetWords = new Uint32Array(wc);
|
|
3870
4319
|
this.markingSnapBuffer = new Uint32Array(wc);
|
|
3871
4320
|
this.firingSnapBuffer = new Uint32Array(wc);
|
|
4321
|
+
this.initMatchCaches();
|
|
4322
|
+
}
|
|
4323
|
+
/**
|
|
4324
|
+
* Builds the ν-net incremental match caches (NU-020). A matched join is
|
|
4325
|
+
* fast-path eligible only when every correlated input is `one`/`exactly`, is
|
|
4326
|
+
* consumed by no other transition, and is never reset — so tokens enter a
|
|
4327
|
+
* correlated input only via produce/inject (mirrored by `add`) and leave only
|
|
4328
|
+
* via this join's matched consume (mirrored by `consume`), and the cache can
|
|
4329
|
+
* never desync. Mirrors the Rust backends.
|
|
4330
|
+
*/
|
|
4331
|
+
initMatchCaches() {
|
|
4332
|
+
const prog = this.program;
|
|
4333
|
+
const tc = prog.transitionCount;
|
|
4334
|
+
const pc = prog.placeCount;
|
|
4335
|
+
this.matchCaches = new Array(tc).fill(null);
|
|
4336
|
+
this.placeMatchTargets = Array.from({ length: pc }, () => []);
|
|
4337
|
+
let anyMatch = false;
|
|
4338
|
+
for (let tid = 0; tid < tc; tid++) {
|
|
4339
|
+
if (prog.hasMatch[tid]) {
|
|
4340
|
+
anyMatch = true;
|
|
4341
|
+
break;
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
if (!anyMatch) return;
|
|
4345
|
+
const inputConsumers = Array.from({ length: pc }, () => []);
|
|
4346
|
+
const resetTarget = new Array(pc).fill(false);
|
|
4347
|
+
for (let tid = 0; tid < tc; tid++) {
|
|
4348
|
+
const t = prog.compiled.transition(tid);
|
|
4349
|
+
for (const spec of t.inputSpecs) inputConsumers[prog.compiled.placeId(spec.place)].push(tid);
|
|
4350
|
+
for (const arc of t.resets) resetTarget[prog.compiled.placeId(arc.place)] = true;
|
|
4351
|
+
}
|
|
4352
|
+
for (let tid = 0; tid < tc; tid++) {
|
|
4353
|
+
if (!prog.hasMatch[tid]) continue;
|
|
4354
|
+
const t = prog.compiled.transition(tid);
|
|
4355
|
+
const ms = t.matchSpec;
|
|
4356
|
+
if (!ms) continue;
|
|
4357
|
+
const requireds = [];
|
|
4358
|
+
let eligible = true;
|
|
4359
|
+
for (const mk of ms.keys) {
|
|
4360
|
+
const pid = prog.compiled.placeId(mk.place);
|
|
4361
|
+
const spec = t.inputSpecs.find((s) => s.place.name === mk.place.name);
|
|
4362
|
+
let required;
|
|
4363
|
+
if (spec?.type === "one") required = 1;
|
|
4364
|
+
else if (spec?.type === "exactly") required = spec.count;
|
|
4365
|
+
else {
|
|
4366
|
+
eligible = false;
|
|
4367
|
+
break;
|
|
4368
|
+
}
|
|
4369
|
+
const cons = inputConsumers[pid];
|
|
4370
|
+
if (resetTarget[pid] || cons.length !== 1 || cons[0] !== tid) {
|
|
4371
|
+
eligible = false;
|
|
4372
|
+
break;
|
|
4373
|
+
}
|
|
4374
|
+
requireds.push(required);
|
|
4375
|
+
}
|
|
4376
|
+
if (!eligible) continue;
|
|
4377
|
+
const matcher = new IncrementalMatcher(requireds);
|
|
4378
|
+
for (let keyIdx = 0; keyIdx < ms.keys.length; keyIdx++) {
|
|
4379
|
+
const mk = ms.keys[keyIdx];
|
|
4380
|
+
const pid = prog.compiled.placeId(mk.place);
|
|
4381
|
+
const guard = t.inputSpecs.find((s) => s.place.name === mk.place.name)?.guard;
|
|
4382
|
+
for (const token of this.tokenQueues[pid]) {
|
|
4383
|
+
if (guard && !guard(token.value)) continue;
|
|
4384
|
+
const name = mk.key(token.value);
|
|
4385
|
+
if (name !== void 0 && name !== null) matcher.add(keyIdx, name, token.createdAt);
|
|
4386
|
+
}
|
|
4387
|
+
this.placeMatchTargets[pid].push([tid, keyIdx]);
|
|
4388
|
+
}
|
|
4389
|
+
this.matchCaches[tid] = matcher;
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
/** Mirror a token added to correlated input `pid` into every fast-path matcher. */
|
|
4393
|
+
cacheAddToken(pid, token) {
|
|
4394
|
+
const targets = this.placeMatchTargets[pid];
|
|
4395
|
+
if (targets === void 0 || targets.length === 0) return;
|
|
4396
|
+
const prog = this.program;
|
|
4397
|
+
for (const [tid, keyIdx] of targets) {
|
|
4398
|
+
const cache = this.matchCaches[tid];
|
|
4399
|
+
if (cache == null) continue;
|
|
4400
|
+
const t = prog.compiled.transition(tid);
|
|
4401
|
+
const mk = t.matchSpec.keys[keyIdx];
|
|
4402
|
+
const guard = t.inputSpecs.find((s) => s.place.name === mk.place.name)?.guard;
|
|
4403
|
+
if (guard && !guard(token.value)) continue;
|
|
4404
|
+
const name = mk.key(token.value);
|
|
4405
|
+
if (name !== void 0 && name !== null) cache.add(keyIdx, name, token.createdAt);
|
|
4406
|
+
}
|
|
3872
4407
|
}
|
|
3873
4408
|
// ======================== Bitmap Helpers ========================
|
|
3874
4409
|
markTransitionDirty(tid) {
|
|
@@ -4079,12 +4614,22 @@ var PrecompiledNetExecutor = class {
|
|
|
4079
4614
|
}
|
|
4080
4615
|
if (prog.hasGuards[tid]) {
|
|
4081
4616
|
const t = prog.compiled.transition(tid);
|
|
4617
|
+
const cache = this.matchCaches[tid];
|
|
4618
|
+
const ms = t.matchSpec;
|
|
4082
4619
|
for (const spec of t.inputSpecs) {
|
|
4083
4620
|
if (!spec.guard) continue;
|
|
4621
|
+
if (cache != null && ms && keyForPlace(ms, spec.place.name) !== void 0) continue;
|
|
4084
4622
|
const required = spec.type === "one" ? 1 : spec.type === "exactly" ? spec.count : spec.type === "at-least" ? spec.minimum : 1;
|
|
4085
4623
|
if (this.countMatching(prog.compiled.placeId(spec.place), spec.guard) < required) return false;
|
|
4086
4624
|
}
|
|
4087
4625
|
}
|
|
4626
|
+
if (prog.hasMatch[tid]) {
|
|
4627
|
+
const cache = this.matchCaches[tid];
|
|
4628
|
+
const noBinding = cache != null ? cache.best() === null : findBinding(prog.compiled.transition(tid), (p) => this.tokenQueues[prog.compiled.placeId(p)]) === null;
|
|
4629
|
+
if (noBinding) {
|
|
4630
|
+
return false;
|
|
4631
|
+
}
|
|
4632
|
+
}
|
|
4088
4633
|
return true;
|
|
4089
4634
|
}
|
|
4090
4635
|
countMatching(pid, guard) {
|
|
@@ -4099,7 +4644,7 @@ var PrecompiledNetExecutor = class {
|
|
|
4099
4644
|
const q = this.tokenQueues[pid];
|
|
4100
4645
|
for (let i = 0; i < q.length; i++) {
|
|
4101
4646
|
if (guard(q[i].value)) {
|
|
4102
|
-
return q.splice(i, 1)[0];
|
|
4647
|
+
return i === 0 ? q.shift() : q.splice(i, 1)[0];
|
|
4103
4648
|
}
|
|
4104
4649
|
}
|
|
4105
4650
|
return null;
|
|
@@ -4174,7 +4719,9 @@ var PrecompiledNetExecutor = class {
|
|
|
4174
4719
|
const t = prog.compiled.transition(tid);
|
|
4175
4720
|
const consumed = [];
|
|
4176
4721
|
const inputs = new TokenInput();
|
|
4177
|
-
if (
|
|
4722
|
+
if (t.matchSpec) {
|
|
4723
|
+
this.fireTransitionMatched(tid, t, inputs, consumed);
|
|
4724
|
+
} else if (prog.hasGuards[tid]) {
|
|
4178
4725
|
this.fireTransitionGuarded(tid, t, inputs, consumed);
|
|
4179
4726
|
} else {
|
|
4180
4727
|
const ops = prog.consumeOps[tid];
|
|
@@ -4308,6 +4855,13 @@ var PrecompiledNetExecutor = class {
|
|
|
4308
4855
|
logFn,
|
|
4309
4856
|
t.placeAlias
|
|
4310
4857
|
);
|
|
4858
|
+
let freshNameSupplier = this.freshNameSuppliers[tid];
|
|
4859
|
+
if (freshNameSupplier === void 0) {
|
|
4860
|
+
const base = t.name;
|
|
4861
|
+
freshNameSupplier = () => nameId(`${base}#${this.freshNameCounter++}`);
|
|
4862
|
+
this.freshNameSuppliers[tid] = freshNameSupplier;
|
|
4863
|
+
}
|
|
4864
|
+
context.setFreshNameSupplier(freshNameSupplier);
|
|
4311
4865
|
let actionPromise = t.action(context);
|
|
4312
4866
|
if (t.hasActionTimeout()) {
|
|
4313
4867
|
const timeoutSpec = t.actionTimeout;
|
|
@@ -4361,6 +4915,65 @@ var PrecompiledNetExecutor = class {
|
|
|
4361
4915
|
this.enabledTransitionCount--;
|
|
4362
4916
|
this.enabledAtMs[tid] = -Infinity;
|
|
4363
4917
|
}
|
|
4918
|
+
/**
|
|
4919
|
+
* Consumes the name-matched tokens for a ν-net join (NU-020): correlated
|
|
4920
|
+
* inputs take tokens whose projected name equals the chosen binding (guard
|
|
4921
|
+
* first, then name equality — NU-021); other inputs consume FIFO. Reset arcs
|
|
4922
|
+
* are honoured as on the opcode path. Mirrors {@link fireTransitionGuarded}.
|
|
4923
|
+
*/
|
|
4924
|
+
fireTransitionMatched(tid, t, inputs, consumed) {
|
|
4925
|
+
const prog = this.program;
|
|
4926
|
+
const ms = t.matchSpec;
|
|
4927
|
+
const cache = this.matchCaches[tid];
|
|
4928
|
+
const chosen = cache != null ? cache.best() : findBinding(t, (p) => this.tokenQueues[prog.compiled.placeId(p)]);
|
|
4929
|
+
if (cache != null && chosen !== null) cache.consume(chosen);
|
|
4930
|
+
for (const inSpec of t.inputSpecs) {
|
|
4931
|
+
const pid = prog.compiled.placeId(inSpec.place);
|
|
4932
|
+
const keyFn = keyForPlace(ms, inSpec.place.name);
|
|
4933
|
+
const baseGuard = inSpec.guard;
|
|
4934
|
+
const pred = keyFn && chosen !== null ? (v) => (baseGuard ? baseGuard(v) : true) && keyFn(v) === chosen : baseGuard;
|
|
4935
|
+
let toConsume;
|
|
4936
|
+
switch (inSpec.type) {
|
|
4937
|
+
case "one":
|
|
4938
|
+
toConsume = 1;
|
|
4939
|
+
break;
|
|
4940
|
+
case "exactly":
|
|
4941
|
+
toConsume = inSpec.count;
|
|
4942
|
+
break;
|
|
4943
|
+
case "all":
|
|
4944
|
+
case "at-least":
|
|
4945
|
+
toConsume = pred ? this.countMatching(pid, pred) : this.tokenQueues[pid].length;
|
|
4946
|
+
break;
|
|
4947
|
+
}
|
|
4948
|
+
for (let i = 0; i < toConsume; i++) {
|
|
4949
|
+
const token = pred ? this.removeFirstMatching(pid, pred) : this.tokenQueues[pid].shift() ?? null;
|
|
4950
|
+
if (token === null) break;
|
|
4951
|
+
consumed.push(token);
|
|
4952
|
+
inputs.add(inSpec.place, token);
|
|
4953
|
+
this.emitEvent({
|
|
4954
|
+
type: "token-removed",
|
|
4955
|
+
timestamp: Date.now(),
|
|
4956
|
+
placeName: inSpec.place.name,
|
|
4957
|
+
token
|
|
4958
|
+
});
|
|
4959
|
+
}
|
|
4960
|
+
}
|
|
4961
|
+
for (const arc of t.resets) {
|
|
4962
|
+
const pid = prog.compiled.placeId(arc.place);
|
|
4963
|
+
const tokens = this.tokenQueues[pid].splice(0);
|
|
4964
|
+
this.pendingResetWords[pid >>> WORD_SHIFT] |= 1 << (pid & BIT_MASK);
|
|
4965
|
+
this.hasPendingResets = true;
|
|
4966
|
+
for (const token of tokens) {
|
|
4967
|
+
consumed.push(token);
|
|
4968
|
+
this.emitEvent({
|
|
4969
|
+
type: "token-removed",
|
|
4970
|
+
timestamp: Date.now(),
|
|
4971
|
+
placeName: arc.place.name,
|
|
4972
|
+
token
|
|
4973
|
+
});
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4976
|
+
}
|
|
4364
4977
|
fireTransitionGuarded(_tid, t, inputs, consumed) {
|
|
4365
4978
|
const prog = this.program;
|
|
4366
4979
|
for (const inSpec of t.inputSpecs) {
|
|
@@ -4475,6 +5088,7 @@ var PrecompiledNetExecutor = class {
|
|
|
4475
5088
|
const produced = [];
|
|
4476
5089
|
for (const entry of outputs.entries()) {
|
|
4477
5090
|
const pid = prog.compiled.placeId(entry.place);
|
|
5091
|
+
this.cacheAddToken(pid, entry.token);
|
|
4478
5092
|
this.tokenQueues[pid].push(entry.token);
|
|
4479
5093
|
produced.push(entry.token);
|
|
4480
5094
|
this.setMarkingBit(pid);
|
|
@@ -4519,6 +5133,7 @@ var PrecompiledNetExecutor = class {
|
|
|
4519
5133
|
const event = this.externalQueue[i];
|
|
4520
5134
|
try {
|
|
4521
5135
|
const pid = prog.compiled.placeId(event.place);
|
|
5136
|
+
this.cacheAddToken(pid, event.token);
|
|
4522
5137
|
this.tokenQueues[pid].push(event.token);
|
|
4523
5138
|
this.setMarkingBit(pid);
|
|
4524
5139
|
this.markDirty(pid);
|
|
@@ -4719,8 +5334,13 @@ export {
|
|
|
4719
5334
|
intersects,
|
|
4720
5335
|
isFailureEvent,
|
|
4721
5336
|
isUnit,
|
|
5337
|
+
keyForPlace,
|
|
4722
5338
|
latest,
|
|
5339
|
+
matchCorrelates,
|
|
5340
|
+
matchKey,
|
|
5341
|
+
matchSpec,
|
|
4723
5342
|
matchesGuard,
|
|
5343
|
+
nameId,
|
|
4724
5344
|
noopEventStore,
|
|
4725
5345
|
one,
|
|
4726
5346
|
openSubnet,
|