libpetri 1.2.0 → 1.3.1

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/dist/index.js CHANGED
@@ -1894,6 +1894,1215 @@ var TimeoutSentinel = class extends Error {
1894
1894
  this.name = "TimeoutSentinel";
1895
1895
  }
1896
1896
  };
1897
+
1898
+ // src/runtime/precompiled-net.ts
1899
+ var CONSUME_ONE = 0;
1900
+ var CONSUME_N = 1;
1901
+ var CONSUME_ALL = 2;
1902
+ var CONSUME_ATLEAST = 3;
1903
+ var RESET = 4;
1904
+ var SPARSE_EMPTY = -2;
1905
+ var SPARSE_MULTI = -1;
1906
+ var PrecompiledNet = class _PrecompiledNet {
1907
+ // ==================== Identity ====================
1908
+ compiled;
1909
+ placeCount;
1910
+ transitionCount;
1911
+ wordCount;
1912
+ // ==================== Place Cache ====================
1913
+ /** Places indexed by pid — avoids compiled.place(pid) indirection on hot path. */
1914
+ places;
1915
+ // ==================== Opcode Programs ====================
1916
+ /** Per-transition consume opcode sequences. */
1917
+ consumeOps;
1918
+ /** Per-transition read-arc place IDs. */
1919
+ readOps;
1920
+ // ==================== Enablement Masks ====================
1921
+ needsMask;
1922
+ inhibitorMask;
1923
+ // ==================== Sparse Enablement (PERF-042) ====================
1924
+ needsSingleWordIndex;
1925
+ needsSingleWordMask;
1926
+ needsSparseIndices;
1927
+ needsSparseMasks;
1928
+ inhibitorSingleWordIndex;
1929
+ inhibitorSingleWordMask;
1930
+ inhibitorSparseIndices;
1931
+ inhibitorSparseMasks;
1932
+ // ==================== Timing (CONC-024) ====================
1933
+ earliestMs;
1934
+ latestMs;
1935
+ hasDeadline;
1936
+ // ==================== Priority (CONC-023) ====================
1937
+ priorities;
1938
+ transitionToPriorityIndex;
1939
+ priorityLevels;
1940
+ distinctPriorityCount;
1941
+ // ==================== Output Fast Path ====================
1942
+ /** -2=no spec, -1=complex, >=0=single output place ID. */
1943
+ simpleOutputPlaceId;
1944
+ // ==================== Input Precomputation ====================
1945
+ inputPlaceCount;
1946
+ inputPlaceMaskWords;
1947
+ // ==================== Reverse Index ====================
1948
+ placeToTransitions;
1949
+ consumptionPlaceIds;
1950
+ // ==================== Cardinality & Guards ====================
1951
+ cardinalityChecks;
1952
+ hasGuards;
1953
+ // ==================== Global Flags ====================
1954
+ allImmediate;
1955
+ allSamePriority;
1956
+ anyDeadlines;
1957
+ constructor(compiled) {
1958
+ this.compiled = compiled;
1959
+ this.placeCount = compiled.placeCount;
1960
+ this.transitionCount = compiled.transitionCount;
1961
+ this.wordCount = compiled.wordCount;
1962
+ const tc = compiled.transitionCount;
1963
+ const wc = compiled.wordCount;
1964
+ const places = new Array(this.placeCount);
1965
+ for (let pid = 0; pid < this.placeCount; pid++) {
1966
+ places[pid] = compiled.place(pid);
1967
+ }
1968
+ this.places = places;
1969
+ const needsMask = new Array(tc);
1970
+ const inhibitorMask = new Array(tc);
1971
+ for (let tid = 0; tid < tc; tid++) {
1972
+ needsMask[tid] = new Uint32Array(wc);
1973
+ inhibitorMask[tid] = new Uint32Array(wc);
1974
+ }
1975
+ for (let tid = 0; tid < tc; tid++) {
1976
+ const t = compiled.transition(tid);
1977
+ const needs = needsMask[tid];
1978
+ const inhibitors = inhibitorMask[tid];
1979
+ for (const inSpec of t.inputSpecs) {
1980
+ const pid = compiled.placeId(inSpec.place);
1981
+ needs[pid >>> WORD_SHIFT] |= 1 << (pid & BIT_MASK);
1982
+ }
1983
+ for (const arc of t.reads) {
1984
+ const pid = compiled.placeId(arc.place);
1985
+ needs[pid >>> WORD_SHIFT] |= 1 << (pid & BIT_MASK);
1986
+ }
1987
+ for (const arc of t.inhibitors) {
1988
+ const pid = compiled.placeId(arc.place);
1989
+ inhibitors[pid >>> WORD_SHIFT] |= 1 << (pid & BIT_MASK);
1990
+ }
1991
+ }
1992
+ this.needsMask = needsMask;
1993
+ this.inhibitorMask = inhibitorMask;
1994
+ this.needsSingleWordIndex = new Int8Array(tc);
1995
+ this.needsSingleWordMask = new Uint32Array(tc);
1996
+ const needsSparseIndices = new Array(tc);
1997
+ const needsSparseMasks = new Array(tc);
1998
+ this.inhibitorSingleWordIndex = new Int8Array(tc);
1999
+ this.inhibitorSingleWordMask = new Uint32Array(tc);
2000
+ const inhibitorSparseIndices = new Array(tc);
2001
+ const inhibitorSparseMasks = new Array(tc);
2002
+ for (let tid = 0; tid < tc; tid++) {
2003
+ compileSparse(
2004
+ needsMask[tid],
2005
+ wc,
2006
+ this.needsSingleWordIndex,
2007
+ this.needsSingleWordMask,
2008
+ needsSparseIndices,
2009
+ needsSparseMasks,
2010
+ tid
2011
+ );
2012
+ compileSparse(
2013
+ inhibitorMask[tid],
2014
+ wc,
2015
+ this.inhibitorSingleWordIndex,
2016
+ this.inhibitorSingleWordMask,
2017
+ inhibitorSparseIndices,
2018
+ inhibitorSparseMasks,
2019
+ tid
2020
+ );
2021
+ }
2022
+ this.needsSparseIndices = needsSparseIndices;
2023
+ this.needsSparseMasks = needsSparseMasks;
2024
+ this.inhibitorSparseIndices = inhibitorSparseIndices;
2025
+ this.inhibitorSparseMasks = inhibitorSparseMasks;
2026
+ const consumeOps = new Array(tc);
2027
+ const readOps = new Array(tc);
2028
+ for (let tid = 0; tid < tc; tid++) {
2029
+ const t = compiled.transition(tid);
2030
+ consumeOps[tid] = compileConsumeProgram(t, compiled);
2031
+ readOps[tid] = compileReadProgram(t, compiled);
2032
+ }
2033
+ this.consumeOps = consumeOps;
2034
+ this.readOps = readOps;
2035
+ const placeToTransitions = new Array(this.placeCount);
2036
+ const consumptionPlaceIds = new Array(tc);
2037
+ for (let pid = 0; pid < this.placeCount; pid++) {
2038
+ placeToTransitions[pid] = [...compiled.affectedTransitions(pid)];
2039
+ }
2040
+ for (let tid = 0; tid < tc; tid++) {
2041
+ consumptionPlaceIds[tid] = [...compiled.consumptionPlaceIds(tid)];
2042
+ }
2043
+ this.placeToTransitions = placeToTransitions;
2044
+ this.consumptionPlaceIds = consumptionPlaceIds;
2045
+ const cardinalityChecks = new Array(tc);
2046
+ const hasGuards = new Array(tc);
2047
+ for (let tid = 0; tid < tc; tid++) {
2048
+ cardinalityChecks[tid] = compiled.cardinalityCheck(tid);
2049
+ hasGuards[tid] = compiled.hasGuards(tid);
2050
+ }
2051
+ this.cardinalityChecks = cardinalityChecks;
2052
+ this.hasGuards = hasGuards;
2053
+ this.earliestMs = new Float64Array(tc);
2054
+ this.latestMs = new Float64Array(tc);
2055
+ this.hasDeadline = new Uint8Array(tc);
2056
+ let anyDeadlines = false;
2057
+ let allImm = true;
2058
+ for (let tid = 0; tid < tc; tid++) {
2059
+ const t = compiled.transition(tid);
2060
+ this.earliestMs[tid] = earliest(t.timing);
2061
+ this.latestMs[tid] = latest(t.timing);
2062
+ if (hasDeadline(t.timing)) {
2063
+ this.hasDeadline[tid] = 1;
2064
+ anyDeadlines = true;
2065
+ }
2066
+ if (t.timing.type !== "immediate") allImm = false;
2067
+ }
2068
+ this.anyDeadlines = anyDeadlines;
2069
+ this.allImmediate = allImm;
2070
+ this.priorities = new Int32Array(tc);
2071
+ const prioritySet = /* @__PURE__ */ new Set();
2072
+ const firstPriority = tc > 0 ? compiled.transition(0).priority : 0;
2073
+ let samePrio = true;
2074
+ for (let tid = 0; tid < tc; tid++) {
2075
+ const p = compiled.transition(tid).priority;
2076
+ this.priorities[tid] = p;
2077
+ prioritySet.add(p);
2078
+ if (p !== firstPriority) samePrio = false;
2079
+ }
2080
+ this.allSamePriority = samePrio;
2081
+ const levels = [...prioritySet].sort((a, b) => b - a);
2082
+ this.priorityLevels = levels;
2083
+ this.distinctPriorityCount = levels.length;
2084
+ this.transitionToPriorityIndex = new Uint32Array(tc);
2085
+ const levelIndex = /* @__PURE__ */ new Map();
2086
+ for (let i = 0; i < levels.length; i++) {
2087
+ levelIndex.set(levels[i], i);
2088
+ }
2089
+ for (let tid = 0; tid < tc; tid++) {
2090
+ this.transitionToPriorityIndex[tid] = levelIndex.get(this.priorities[tid]);
2091
+ }
2092
+ this.simpleOutputPlaceId = new Int32Array(tc);
2093
+ for (let tid = 0; tid < tc; tid++) {
2094
+ const t = compiled.transition(tid);
2095
+ if (t.outputSpec === null) {
2096
+ this.simpleOutputPlaceId[tid] = -2;
2097
+ } else {
2098
+ const simplePid = simpleOutputPlace(t.outputSpec, compiled);
2099
+ this.simpleOutputPlaceId[tid] = simplePid;
2100
+ }
2101
+ }
2102
+ this.inputPlaceCount = new Uint32Array(tc);
2103
+ const inputPlaceMaskWords = new Array(tc);
2104
+ for (let tid = 0; tid < tc; tid++) {
2105
+ const t = compiled.transition(tid);
2106
+ this.inputPlaceCount[tid] = t.inputSpecs.length + t.reads.length;
2107
+ const mask = new Uint32Array(wc);
2108
+ for (const spec of t.inputSpecs) {
2109
+ const pid = compiled.placeId(spec.place);
2110
+ mask[pid >>> WORD_SHIFT] |= 1 << (pid & BIT_MASK);
2111
+ }
2112
+ inputPlaceMaskWords[tid] = mask;
2113
+ }
2114
+ this.inputPlaceMaskWords = inputPlaceMaskWords;
2115
+ }
2116
+ // ==================== Factory Methods ====================
2117
+ static compile(net) {
2118
+ return new _PrecompiledNet(CompiledNet.compile(net));
2119
+ }
2120
+ static compileFrom(compiled) {
2121
+ return new _PrecompiledNet(compiled);
2122
+ }
2123
+ // ==================== Sparse Enablement Check ====================
2124
+ /**
2125
+ * Three-case sparse enablement check:
2126
+ * 1. Empty mask (needsSingleWordIndex == -2): always passes
2127
+ * 2. Single-word mask (>=0): one comparison
2128
+ * 3. Multi-word mask (-1): iterate precomputed sparse indices
2129
+ */
2130
+ canEnableSparse(tid, snapshot) {
2131
+ const needsIdx = this.needsSingleWordIndex[tid];
2132
+ if (needsIdx === SPARSE_EMPTY) {
2133
+ } else if (needsIdx >= 0) {
2134
+ const m = this.needsSingleWordMask[tid];
2135
+ if ((snapshot[needsIdx] & m) !== m) return false;
2136
+ } else {
2137
+ const indices = this.needsSparseIndices[tid];
2138
+ const masks = this.needsSparseMasks[tid];
2139
+ for (let i = 0; i < indices.length; i++) {
2140
+ const m = masks[i];
2141
+ if ((snapshot[indices[i]] & m) !== m) return false;
2142
+ }
2143
+ }
2144
+ const inhIdx = this.inhibitorSingleWordIndex[tid];
2145
+ if (inhIdx === SPARSE_EMPTY) {
2146
+ return true;
2147
+ } else if (inhIdx >= 0) {
2148
+ return (snapshot[inhIdx] & this.inhibitorSingleWordMask[tid]) === 0;
2149
+ } else {
2150
+ const indices = this.inhibitorSparseIndices[tid];
2151
+ const masks = this.inhibitorSparseMasks[tid];
2152
+ for (let i = 0; i < indices.length; i++) {
2153
+ if ((snapshot[indices[i]] & masks[i]) !== 0) return false;
2154
+ }
2155
+ return true;
2156
+ }
2157
+ }
2158
+ };
2159
+ function compileSparse(mask, wordCount, singleWordIndex, singleWordMask, sparseIndices, sparseMasks, tid) {
2160
+ let nonZeroCount = 0;
2161
+ let lastNonZeroWord = -1;
2162
+ for (let w = 0; w < wordCount; w++) {
2163
+ if (mask[w] !== 0) {
2164
+ nonZeroCount++;
2165
+ lastNonZeroWord = w;
2166
+ }
2167
+ }
2168
+ if (nonZeroCount === 0) {
2169
+ singleWordIndex[tid] = SPARSE_EMPTY;
2170
+ singleWordMask[tid] = 0;
2171
+ sparseIndices[tid] = [];
2172
+ sparseMasks[tid] = [];
2173
+ } else if (nonZeroCount === 1) {
2174
+ singleWordIndex[tid] = lastNonZeroWord;
2175
+ singleWordMask[tid] = mask[lastNonZeroWord];
2176
+ sparseIndices[tid] = [];
2177
+ sparseMasks[tid] = [];
2178
+ } else {
2179
+ singleWordIndex[tid] = SPARSE_MULTI;
2180
+ singleWordMask[tid] = 0;
2181
+ const idx = [];
2182
+ const msk = [];
2183
+ for (let w = 0; w < wordCount; w++) {
2184
+ if (mask[w] !== 0) {
2185
+ idx.push(w);
2186
+ msk.push(mask[w]);
2187
+ }
2188
+ }
2189
+ sparseIndices[tid] = idx;
2190
+ sparseMasks[tid] = msk;
2191
+ }
2192
+ }
2193
+ function compileConsumeProgram(t, compiled) {
2194
+ const ops = [];
2195
+ for (const spec of t.inputSpecs) {
2196
+ const pid = compiled.placeId(spec.place);
2197
+ switch (spec.type) {
2198
+ case "one":
2199
+ ops.push(CONSUME_ONE, pid);
2200
+ break;
2201
+ case "exactly":
2202
+ ops.push(CONSUME_N, pid, spec.count);
2203
+ break;
2204
+ case "all":
2205
+ ops.push(CONSUME_ALL, pid);
2206
+ break;
2207
+ case "at-least":
2208
+ ops.push(CONSUME_ATLEAST, pid, spec.minimum);
2209
+ break;
2210
+ }
2211
+ }
2212
+ for (const arc of t.resets) {
2213
+ const pid = compiled.placeId(arc.place);
2214
+ ops.push(RESET, pid);
2215
+ }
2216
+ return ops;
2217
+ }
2218
+ function compileReadProgram(t, compiled) {
2219
+ const pids = [];
2220
+ for (const arc of t.reads) {
2221
+ pids.push(compiled.placeId(arc.place));
2222
+ }
2223
+ return pids;
2224
+ }
2225
+ function simpleOutputPlace(spec, compiled) {
2226
+ if (spec.type === "place") {
2227
+ return compiled.placeId(spec.place);
2228
+ }
2229
+ return -1;
2230
+ }
2231
+
2232
+ // src/runtime/precompiled-net-executor.ts
2233
+ var DEADLINE_TOLERANCE_MS2 = 5;
2234
+ var PrecompiledNetExecutor = class {
2235
+ program;
2236
+ eventStore;
2237
+ environmentPlaces;
2238
+ longRunning;
2239
+ executionContextProvider;
2240
+ skipOutputValidation;
2241
+ startMs;
2242
+ eventStoreEnabled;
2243
+ // ==================== Token Storage ====================
2244
+ /** Per-place token arrays, indexed by pid. */
2245
+ tokenQueues;
2246
+ // ==================== Marking Bitmap ====================
2247
+ markingBitmap;
2248
+ // ==================== Transition State ====================
2249
+ dirtyBitmap;
2250
+ dirtyScanBuffer;
2251
+ enabledAtMs;
2252
+ inFlightFlags;
2253
+ enabledFlags;
2254
+ transitionWords;
2255
+ // ==================== Enabled Count ====================
2256
+ enabledTransitionCount = 0;
2257
+ // ==================== In-Flight Tracking ====================
2258
+ inFlightPromises;
2259
+ inFlightContexts;
2260
+ inFlightConsumed;
2261
+ inFlightStartMs;
2262
+ inFlightResolves;
2263
+ inFlightErrors;
2264
+ inFlightCount = 0;
2265
+ // ==================== Reset-Clock Detection ====================
2266
+ pendingResetWords;
2267
+ hasPendingResets = false;
2268
+ // ==================== Queues ====================
2269
+ completionQueue = [];
2270
+ externalQueue = [];
2271
+ wakeUpResolve = null;
2272
+ // ==================== Reusable Buffers ====================
2273
+ markingSnapBuffer;
2274
+ firingSnapBuffer;
2275
+ awaitPromises = [];
2276
+ racePromises = [];
2277
+ // Pre-allocated buffer for fireReadyGeneral()
2278
+ readyBuffer = [];
2279
+ // ==================== Lifecycle ====================
2280
+ running = false;
2281
+ closed = false;
2282
+ // ==================== Lazy Marking ====================
2283
+ marking = null;
2284
+ constructor(net, initialTokens, options = {}) {
2285
+ this.program = options.program ?? PrecompiledNet.compile(net);
2286
+ this.eventStore = options.eventStore ?? noopEventStore();
2287
+ this.environmentPlaces = new Set(
2288
+ [...options.environmentPlaces ?? []].map((ep) => ep.place.name)
2289
+ );
2290
+ this.longRunning = options.longRunning ?? false;
2291
+ this.executionContextProvider = options.executionContextProvider;
2292
+ this.skipOutputValidation = options.skipOutputValidation ?? false;
2293
+ this.startMs = performance.now();
2294
+ this.eventStoreEnabled = this.eventStore.isEnabled();
2295
+ const prog = this.program;
2296
+ const pc = prog.placeCount;
2297
+ const tc = prog.transitionCount;
2298
+ const wc = prog.wordCount;
2299
+ this.tokenQueues = new Array(pc);
2300
+ for (let pid = 0; pid < pc; pid++) {
2301
+ this.tokenQueues[pid] = [];
2302
+ }
2303
+ for (const [place2, tokens] of initialTokens) {
2304
+ const pid = prog.compiled.placeId(place2);
2305
+ const q = this.tokenQueues[pid];
2306
+ for (const token of tokens) {
2307
+ q.push(token);
2308
+ }
2309
+ }
2310
+ this.markingBitmap = new Uint32Array(wc);
2311
+ this.transitionWords = tc + BIT_MASK >>> WORD_SHIFT;
2312
+ this.dirtyBitmap = new Uint32Array(this.transitionWords);
2313
+ this.dirtyScanBuffer = new Uint32Array(this.transitionWords);
2314
+ this.enabledAtMs = new Float64Array(tc);
2315
+ this.enabledAtMs.fill(-Infinity);
2316
+ this.inFlightFlags = new Uint8Array(tc);
2317
+ this.enabledFlags = new Uint8Array(tc);
2318
+ this.inFlightPromises = new Array(tc).fill(null);
2319
+ this.inFlightContexts = new Array(tc).fill(null);
2320
+ this.inFlightConsumed = new Array(tc).fill(null);
2321
+ this.inFlightStartMs = new Float64Array(tc);
2322
+ this.inFlightResolves = new Array(tc).fill(null);
2323
+ this.inFlightErrors = new Array(tc).fill(null);
2324
+ this.pendingResetWords = new Uint32Array(wc);
2325
+ this.markingSnapBuffer = new Uint32Array(wc);
2326
+ this.firingSnapBuffer = new Uint32Array(wc);
2327
+ }
2328
+ // ======================== Bitmap Helpers ========================
2329
+ markTransitionDirty(tid) {
2330
+ this.dirtyBitmap[tid >>> WORD_SHIFT] |= 1 << (tid & BIT_MASK);
2331
+ }
2332
+ markDirty(pid) {
2333
+ const tids = this.program.placeToTransitions[pid];
2334
+ for (let i = 0; i < tids.length; i++) {
2335
+ this.markTransitionDirty(tids[i]);
2336
+ }
2337
+ }
2338
+ setMarkingBit(pid) {
2339
+ this.markingBitmap[pid >>> WORD_SHIFT] |= 1 << (pid & BIT_MASK);
2340
+ }
2341
+ clearMarkingBit(pid) {
2342
+ this.markingBitmap[pid >>> WORD_SHIFT] &= ~(1 << (pid & BIT_MASK));
2343
+ }
2344
+ // ======================== Execution ========================
2345
+ async run(timeoutMs) {
2346
+ if (timeoutMs !== void 0) {
2347
+ let timer;
2348
+ const timeoutPromise = new Promise((_, reject) => {
2349
+ timer = setTimeout(() => reject(new Error("Execution timed out")), timeoutMs);
2350
+ });
2351
+ try {
2352
+ return await Promise.race([this.executeLoop(), timeoutPromise]);
2353
+ } finally {
2354
+ if (timer !== void 0) clearTimeout(timer);
2355
+ }
2356
+ }
2357
+ return this.executeLoop();
2358
+ }
2359
+ async executeLoop() {
2360
+ this.running = true;
2361
+ const prog = this.program;
2362
+ this.emitEvent({
2363
+ type: "execution-started",
2364
+ timestamp: Date.now(),
2365
+ netName: prog.compiled.net.name,
2366
+ executionId: this.executionId()
2367
+ });
2368
+ this.initializeMarkingBitmap();
2369
+ this.markAllDirty();
2370
+ this.emitEvent({
2371
+ type: "marking-snapshot",
2372
+ timestamp: Date.now(),
2373
+ marking: this.snapshotMarking()
2374
+ });
2375
+ while (this.running && !this.closed) {
2376
+ this.processCompletedTransitions();
2377
+ this.processExternalEvents();
2378
+ this.updateDirtyTransitions();
2379
+ const cycleNowMs = performance.now();
2380
+ if (prog.anyDeadlines) this.enforceDeadlines(cycleNowMs);
2381
+ if (this.shouldTerminate()) break;
2382
+ this.fireReadyTransitions(cycleNowMs);
2383
+ if (this.hasDirtyBits()) continue;
2384
+ await this.awaitWork();
2385
+ }
2386
+ this.running = false;
2387
+ this.drainPendingExternalEvents();
2388
+ this.emitEvent({
2389
+ type: "marking-snapshot",
2390
+ timestamp: Date.now(),
2391
+ marking: this.snapshotMarking()
2392
+ });
2393
+ this.emitEvent({
2394
+ type: "execution-completed",
2395
+ timestamp: Date.now(),
2396
+ netName: prog.compiled.net.name,
2397
+ executionId: this.executionId(),
2398
+ totalDurationMs: performance.now() - this.startMs
2399
+ });
2400
+ return this.syncMarkingFromQueues();
2401
+ }
2402
+ // ======================== Environment Place API ========================
2403
+ async inject(envPlace, token) {
2404
+ if (!this.environmentPlaces.has(envPlace.place.name)) {
2405
+ throw new Error(`Place ${envPlace.place.name} is not registered as an environment place`);
2406
+ }
2407
+ if (this.closed) return false;
2408
+ return new Promise((resolve, reject) => {
2409
+ this.externalQueue.push({
2410
+ place: envPlace.place,
2411
+ token,
2412
+ resolve,
2413
+ reject
2414
+ });
2415
+ this.wakeUp();
2416
+ });
2417
+ }
2418
+ async injectValue(envPlace, value) {
2419
+ return this.inject(envPlace, tokenOf(value));
2420
+ }
2421
+ // ======================== Initialize ========================
2422
+ initializeMarkingBitmap() {
2423
+ for (let pid = 0; pid < this.program.placeCount; pid++) {
2424
+ if (this.tokenQueues[pid].length > 0) {
2425
+ this.setMarkingBit(pid);
2426
+ }
2427
+ }
2428
+ }
2429
+ markAllDirty() {
2430
+ const tw = this.transitionWords;
2431
+ const tc = this.program.transitionCount;
2432
+ for (let w = 0; w < tw - 1; w++) {
2433
+ this.dirtyBitmap[w] = 4294967295;
2434
+ }
2435
+ if (tw > 0) {
2436
+ const lastBits = tc & BIT_MASK;
2437
+ this.dirtyBitmap[tw - 1] = lastBits === 0 ? 4294967295 : (1 << lastBits) - 1;
2438
+ }
2439
+ }
2440
+ shouldTerminate() {
2441
+ if (this.longRunning) return this.closed;
2442
+ return this.enabledTransitionCount === 0 && this.inFlightCount === 0 && this.completionQueue.length === 0;
2443
+ }
2444
+ // ======================== Dirty Set Processing ========================
2445
+ updateDirtyTransitions() {
2446
+ const nowMs = performance.now();
2447
+ const prog = this.program;
2448
+ const tc = prog.transitionCount;
2449
+ const markingSnap = this.markingSnapBuffer;
2450
+ markingSnap.set(this.markingBitmap);
2451
+ const tw = this.transitionWords;
2452
+ const dirtySnap = this.dirtyScanBuffer;
2453
+ for (let w = 0; w < tw; w++) {
2454
+ dirtySnap[w] = this.dirtyBitmap[w];
2455
+ this.dirtyBitmap[w] = 0;
2456
+ }
2457
+ for (let w = 0; w < tw; w++) {
2458
+ let word = dirtySnap[w];
2459
+ if (word === 0) continue;
2460
+ dirtySnap[w] = 0;
2461
+ while (word !== 0) {
2462
+ const bit = Math.clz32(word & -word) ^ 31;
2463
+ const tid = w << WORD_SHIFT | bit;
2464
+ word &= word - 1;
2465
+ if (tid >= tc) break;
2466
+ if (this.inFlightFlags[tid]) continue;
2467
+ const wasEnabled = this.enabledFlags[tid] !== 0;
2468
+ const canNow = this.canEnable(tid, markingSnap);
2469
+ if (canNow && !wasEnabled) {
2470
+ this.enabledFlags[tid] = 1;
2471
+ this.enabledTransitionCount++;
2472
+ this.enabledAtMs[tid] = nowMs;
2473
+ this.emitEvent({
2474
+ type: "transition-enabled",
2475
+ timestamp: Date.now(),
2476
+ transitionName: prog.compiled.transition(tid).name
2477
+ });
2478
+ } else if (!canNow && wasEnabled) {
2479
+ this.enabledFlags[tid] = 0;
2480
+ this.enabledTransitionCount--;
2481
+ this.enabledAtMs[tid] = -Infinity;
2482
+ } else if (canNow && wasEnabled && this.hasInputFromResetPlace(tid)) {
2483
+ this.enabledAtMs[tid] = nowMs;
2484
+ this.emitEvent({
2485
+ type: "transition-clock-restarted",
2486
+ timestamp: Date.now(),
2487
+ transitionName: prog.compiled.transition(tid).name
2488
+ });
2489
+ }
2490
+ }
2491
+ }
2492
+ if (this.hasPendingResets) {
2493
+ this.pendingResetWords.fill(0);
2494
+ this.hasPendingResets = false;
2495
+ }
2496
+ }
2497
+ enforceDeadlines(nowMs) {
2498
+ const prog = this.program;
2499
+ const tc = prog.transitionCount;
2500
+ for (let tid = 0; tid < tc; tid++) {
2501
+ if (!prog.hasDeadline[tid]) continue;
2502
+ if (!this.enabledFlags[tid] || this.inFlightFlags[tid]) continue;
2503
+ const elapsed = nowMs - this.enabledAtMs[tid];
2504
+ const latestMs = prog.latestMs[tid];
2505
+ if (elapsed > latestMs + DEADLINE_TOLERANCE_MS2) {
2506
+ this.enabledFlags[tid] = 0;
2507
+ this.enabledTransitionCount--;
2508
+ this.enabledAtMs[tid] = -Infinity;
2509
+ this.emitEvent({
2510
+ type: "transition-timed-out",
2511
+ timestamp: Date.now(),
2512
+ transitionName: prog.compiled.transition(tid).name,
2513
+ deadlineMs: latestMs,
2514
+ actualDurationMs: elapsed
2515
+ });
2516
+ }
2517
+ }
2518
+ }
2519
+ canEnable(tid, markingSnap) {
2520
+ const prog = this.program;
2521
+ if (!prog.canEnableSparse(tid, markingSnap)) return false;
2522
+ const cardCheck = prog.cardinalityChecks[tid] ?? null;
2523
+ if (cardCheck !== null) {
2524
+ for (let i = 0; i < cardCheck.placeIds.length; i++) {
2525
+ const pid = cardCheck.placeIds[i];
2526
+ if (this.tokenQueues[pid].length < cardCheck.requiredCounts[i]) return false;
2527
+ }
2528
+ }
2529
+ if (prog.hasGuards[tid]) {
2530
+ const t = prog.compiled.transition(tid);
2531
+ for (const spec of t.inputSpecs) {
2532
+ if (!spec.guard) continue;
2533
+ const required = spec.type === "one" ? 1 : spec.type === "exactly" ? spec.count : spec.type === "at-least" ? spec.minimum : 1;
2534
+ if (this.countMatching(prog.compiled.placeId(spec.place), spec.guard) < required) return false;
2535
+ }
2536
+ }
2537
+ return true;
2538
+ }
2539
+ countMatching(pid, guard) {
2540
+ const q = this.tokenQueues[pid];
2541
+ let matching = 0;
2542
+ for (let i = 0; i < q.length; i++) {
2543
+ if (guard(q[i].value)) matching++;
2544
+ }
2545
+ return matching;
2546
+ }
2547
+ removeFirstMatching(pid, guard) {
2548
+ const q = this.tokenQueues[pid];
2549
+ for (let i = 0; i < q.length; i++) {
2550
+ if (guard(q[i].value)) {
2551
+ return q.splice(i, 1)[0];
2552
+ }
2553
+ }
2554
+ return null;
2555
+ }
2556
+ hasInputFromResetPlace(tid) {
2557
+ if (!this.hasPendingResets) return false;
2558
+ const inputMask = this.program.inputPlaceMaskWords[tid];
2559
+ for (let w = 0; w < inputMask.length; w++) {
2560
+ if ((inputMask[w] & this.pendingResetWords[w]) !== 0) return true;
2561
+ }
2562
+ return false;
2563
+ }
2564
+ // ======================== Firing ========================
2565
+ fireReadyTransitions(nowMs) {
2566
+ if (this.program.allImmediate && this.program.allSamePriority) {
2567
+ this.fireReadyImmediate();
2568
+ return;
2569
+ }
2570
+ this.fireReadyGeneral(nowMs);
2571
+ }
2572
+ /**
2573
+ * Fast path for nets where all transitions are immediate and same priority.
2574
+ * Simple linear scan matching BitmapNetExecutor's pattern.
2575
+ */
2576
+ fireReadyImmediate() {
2577
+ const tc = this.program.transitionCount;
2578
+ for (let tid = 0; tid < tc; tid++) {
2579
+ if (!this.enabledFlags[tid] || this.inFlightFlags[tid]) continue;
2580
+ if (this.canEnable(tid, this.markingBitmap)) {
2581
+ this.fireTransition(tid);
2582
+ } else {
2583
+ this.enabledFlags[tid] = 0;
2584
+ this.enabledTransitionCount--;
2585
+ this.enabledAtMs[tid] = -Infinity;
2586
+ }
2587
+ }
2588
+ }
2589
+ fireReadyGeneral(nowMs) {
2590
+ const prog = this.program;
2591
+ const tc = prog.transitionCount;
2592
+ const ready = this.readyBuffer;
2593
+ ready.length = 0;
2594
+ for (let tid = 0; tid < tc; tid++) {
2595
+ if (!this.enabledFlags[tid] || this.inFlightFlags[tid]) continue;
2596
+ const elapsedMs = nowMs - this.enabledAtMs[tid];
2597
+ if (prog.earliestMs[tid] <= elapsedMs) {
2598
+ ready.push({ tid, priority: prog.priorities[tid], enabledAtMs: this.enabledAtMs[tid] });
2599
+ }
2600
+ }
2601
+ if (ready.length === 0) return;
2602
+ ready.sort((a, b) => {
2603
+ const prioCmp = b.priority - a.priority;
2604
+ if (prioCmp !== 0) return prioCmp;
2605
+ return a.enabledAtMs - b.enabledAtMs;
2606
+ });
2607
+ const freshSnap = this.firingSnapBuffer;
2608
+ freshSnap.set(this.markingBitmap);
2609
+ for (const entry of ready) {
2610
+ const { tid } = entry;
2611
+ if (this.enabledFlags[tid] && this.canEnable(tid, freshSnap)) {
2612
+ this.fireTransition(tid);
2613
+ freshSnap.set(this.markingBitmap);
2614
+ } else {
2615
+ this.enabledFlags[tid] = 0;
2616
+ this.enabledTransitionCount--;
2617
+ this.enabledAtMs[tid] = -Infinity;
2618
+ }
2619
+ }
2620
+ }
2621
+ fireTransition(tid) {
2622
+ const prog = this.program;
2623
+ const t = prog.compiled.transition(tid);
2624
+ const consumed = [];
2625
+ const inputs = new TokenInput();
2626
+ if (prog.hasGuards[tid]) {
2627
+ this.fireTransitionGuarded(tid, t, inputs, consumed);
2628
+ } else {
2629
+ const ops = prog.consumeOps[tid];
2630
+ let pc = 0;
2631
+ while (pc < ops.length) {
2632
+ const opcode = ops[pc++];
2633
+ switch (opcode) {
2634
+ case CONSUME_ONE: {
2635
+ const pid = ops[pc++];
2636
+ const token = this.tokenQueues[pid].shift();
2637
+ consumed.push(token);
2638
+ inputs.add(prog.places[pid], token);
2639
+ this.emitEvent({
2640
+ type: "token-removed",
2641
+ timestamp: Date.now(),
2642
+ placeName: prog.places[pid].name,
2643
+ token
2644
+ });
2645
+ break;
2646
+ }
2647
+ case CONSUME_N: {
2648
+ const pid = ops[pc++];
2649
+ const count = ops[pc++];
2650
+ const place2 = prog.places[pid];
2651
+ for (let i = 0; i < count; i++) {
2652
+ const token = this.tokenQueues[pid].shift();
2653
+ consumed.push(token);
2654
+ inputs.add(place2, token);
2655
+ this.emitEvent({
2656
+ type: "token-removed",
2657
+ timestamp: Date.now(),
2658
+ placeName: place2.name,
2659
+ token
2660
+ });
2661
+ }
2662
+ break;
2663
+ }
2664
+ case CONSUME_ALL: {
2665
+ const pid = ops[pc++];
2666
+ const place2 = prog.places[pid];
2667
+ const q = this.tokenQueues[pid];
2668
+ const count = q.length;
2669
+ for (let i = 0; i < count; i++) {
2670
+ const token = q.shift();
2671
+ consumed.push(token);
2672
+ inputs.add(place2, token);
2673
+ this.emitEvent({
2674
+ type: "token-removed",
2675
+ timestamp: Date.now(),
2676
+ placeName: place2.name,
2677
+ token
2678
+ });
2679
+ }
2680
+ break;
2681
+ }
2682
+ case CONSUME_ATLEAST: {
2683
+ const pid = ops[pc++];
2684
+ pc++;
2685
+ const place2 = prog.places[pid];
2686
+ const q = this.tokenQueues[pid];
2687
+ const count = q.length;
2688
+ for (let i = 0; i < count; i++) {
2689
+ const token = q.shift();
2690
+ consumed.push(token);
2691
+ inputs.add(place2, token);
2692
+ this.emitEvent({
2693
+ type: "token-removed",
2694
+ timestamp: Date.now(),
2695
+ placeName: place2.name,
2696
+ token
2697
+ });
2698
+ }
2699
+ break;
2700
+ }
2701
+ case RESET: {
2702
+ const pid = ops[pc++];
2703
+ const place2 = prog.places[pid];
2704
+ const tokens = this.tokenQueues[pid].splice(0);
2705
+ this.pendingResetWords[pid >>> WORD_SHIFT] |= 1 << (pid & BIT_MASK);
2706
+ this.hasPendingResets = true;
2707
+ for (const token of tokens) {
2708
+ consumed.push(token);
2709
+ this.emitEvent({
2710
+ type: "token-removed",
2711
+ timestamp: Date.now(),
2712
+ placeName: place2.name,
2713
+ token
2714
+ });
2715
+ }
2716
+ break;
2717
+ }
2718
+ }
2719
+ }
2720
+ }
2721
+ const readPids = prog.readOps[tid];
2722
+ for (let i = 0; i < readPids.length; i++) {
2723
+ const pid = readPids[i];
2724
+ const q = this.tokenQueues[pid];
2725
+ if (q.length > 0) {
2726
+ inputs.add(prog.places[pid], q[0]);
2727
+ }
2728
+ }
2729
+ this.updateBitmapAfterConsumption(tid);
2730
+ this.emitEvent({
2731
+ type: "transition-started",
2732
+ timestamp: Date.now(),
2733
+ transitionName: t.name,
2734
+ consumedTokens: consumed
2735
+ });
2736
+ const execCtx = this.executionContextProvider?.(t.name, consumed);
2737
+ const logFn = (level, message, error) => {
2738
+ this.emitEvent({
2739
+ type: "log-message",
2740
+ timestamp: Date.now(),
2741
+ transitionName: t.name,
2742
+ logger: t.name,
2743
+ level,
2744
+ message,
2745
+ error: error?.name ?? null,
2746
+ errorMessage: error?.message ?? null
2747
+ });
2748
+ };
2749
+ const context = new TransitionContext(
2750
+ t.name,
2751
+ inputs,
2752
+ new TokenOutput(),
2753
+ t.inputPlaces(),
2754
+ t.readPlaces(),
2755
+ t.outputPlaces(),
2756
+ execCtx,
2757
+ logFn
2758
+ );
2759
+ let actionPromise = t.action(context);
2760
+ if (t.hasActionTimeout()) {
2761
+ const timeoutSpec = t.actionTimeout;
2762
+ if (timeoutSpec === null) throw new Error(`Expected actionTimeout on ${t.name}`);
2763
+ const timeoutMs = timeoutSpec.afterMs;
2764
+ actionPromise = Promise.race([
2765
+ actionPromise,
2766
+ new Promise(
2767
+ (_, reject) => setTimeout(() => reject(new TimeoutSentinel2()), timeoutMs)
2768
+ )
2769
+ ]).catch((err) => {
2770
+ if (err instanceof TimeoutSentinel2) {
2771
+ produceTimeoutOutput(context, timeoutSpec.child);
2772
+ this.emitEvent({
2773
+ type: "action-timed-out",
2774
+ timestamp: Date.now(),
2775
+ transitionName: t.name,
2776
+ timeoutMs
2777
+ });
2778
+ return;
2779
+ }
2780
+ throw err;
2781
+ });
2782
+ }
2783
+ let resolveInFlight;
2784
+ const completionPromise = new Promise((r) => {
2785
+ resolveInFlight = r;
2786
+ });
2787
+ this.inFlightPromises[tid] = completionPromise;
2788
+ this.inFlightContexts[tid] = context;
2789
+ this.inFlightConsumed[tid] = consumed;
2790
+ this.inFlightStartMs[tid] = performance.now();
2791
+ this.inFlightResolves[tid] = resolveInFlight;
2792
+ this.inFlightErrors[tid] = null;
2793
+ actionPromise.then(
2794
+ () => {
2795
+ this.completionQueue.push(tid);
2796
+ this.wakeUp();
2797
+ resolveInFlight();
2798
+ },
2799
+ (err) => {
2800
+ this.inFlightErrors[tid] = err;
2801
+ this.completionQueue.push(tid);
2802
+ this.wakeUp();
2803
+ resolveInFlight();
2804
+ }
2805
+ );
2806
+ this.inFlightFlags[tid] = 1;
2807
+ this.inFlightCount++;
2808
+ this.enabledFlags[tid] = 0;
2809
+ this.enabledTransitionCount--;
2810
+ this.enabledAtMs[tid] = -Infinity;
2811
+ }
2812
+ fireTransitionGuarded(_tid, t, inputs, consumed) {
2813
+ const prog = this.program;
2814
+ for (const inSpec of t.inputSpecs) {
2815
+ const pid = prog.compiled.placeId(inSpec.place);
2816
+ let toConsume;
2817
+ switch (inSpec.type) {
2818
+ case "one":
2819
+ toConsume = 1;
2820
+ break;
2821
+ case "exactly":
2822
+ toConsume = inSpec.count;
2823
+ break;
2824
+ case "all":
2825
+ toConsume = inSpec.guard ? this.countMatching(pid, inSpec.guard) : this.tokenQueues[pid].length;
2826
+ break;
2827
+ case "at-least":
2828
+ toConsume = inSpec.guard ? this.countMatching(pid, inSpec.guard) : this.tokenQueues[pid].length;
2829
+ break;
2830
+ }
2831
+ const guardFn = inSpec.guard;
2832
+ for (let i = 0; i < toConsume; i++) {
2833
+ const token = guardFn ? this.removeFirstMatching(pid, guardFn) : this.tokenQueues[pid].shift() ?? null;
2834
+ if (token === null) break;
2835
+ consumed.push(token);
2836
+ inputs.add(inSpec.place, token);
2837
+ this.emitEvent({
2838
+ type: "token-removed",
2839
+ timestamp: Date.now(),
2840
+ placeName: inSpec.place.name,
2841
+ token
2842
+ });
2843
+ }
2844
+ }
2845
+ for (const arc of t.resets) {
2846
+ const pid = prog.compiled.placeId(arc.place);
2847
+ const tokens = this.tokenQueues[pid].splice(0);
2848
+ this.pendingResetWords[pid >>> WORD_SHIFT] |= 1 << (pid & BIT_MASK);
2849
+ this.hasPendingResets = true;
2850
+ for (const token of tokens) {
2851
+ consumed.push(token);
2852
+ this.emitEvent({
2853
+ type: "token-removed",
2854
+ timestamp: Date.now(),
2855
+ placeName: arc.place.name,
2856
+ token
2857
+ });
2858
+ }
2859
+ }
2860
+ }
2861
+ updateBitmapAfterConsumption(tid) {
2862
+ const pids = this.program.consumptionPlaceIds[tid];
2863
+ for (let i = 0; i < pids.length; i++) {
2864
+ const pid = pids[i];
2865
+ if (this.tokenQueues[pid].length === 0) {
2866
+ this.clearMarkingBit(pid);
2867
+ }
2868
+ this.markDirty(pid);
2869
+ }
2870
+ }
2871
+ // ======================== Completion Processing ========================
2872
+ processCompletedTransitions() {
2873
+ if (this.completionQueue.length === 0) return;
2874
+ const prog = this.program;
2875
+ const len = this.completionQueue.length;
2876
+ for (let i = 0; i < len; i++) {
2877
+ const tid = this.completionQueue[i];
2878
+ const context = this.inFlightContexts[tid];
2879
+ const error = this.inFlightErrors[tid];
2880
+ const startMs = this.inFlightStartMs[tid];
2881
+ const t = prog.compiled.transition(tid);
2882
+ this.inFlightFlags[tid] = 0;
2883
+ this.inFlightPromises[tid] = null;
2884
+ this.inFlightContexts[tid] = null;
2885
+ this.inFlightConsumed[tid] = null;
2886
+ this.inFlightResolves[tid] = null;
2887
+ this.inFlightErrors[tid] = null;
2888
+ this.inFlightCount--;
2889
+ if (error) {
2890
+ const err = error instanceof Error ? error : new Error(String(error));
2891
+ this.emitEvent({
2892
+ type: "transition-failed",
2893
+ timestamp: Date.now(),
2894
+ transitionName: t.name,
2895
+ errorMessage: err.message,
2896
+ exceptionType: err.name,
2897
+ stack: err.stack
2898
+ });
2899
+ this.markTransitionDirty(tid);
2900
+ continue;
2901
+ }
2902
+ try {
2903
+ const outputs = context.rawOutput();
2904
+ if (!this.skipOutputValidation && t.outputSpec !== null) {
2905
+ const simplePid = prog.simpleOutputPlaceId[tid];
2906
+ if (simplePid >= 0) {
2907
+ const produced2 = outputs.placesWithTokens();
2908
+ if (!produced2.has(prog.places[simplePid].name)) {
2909
+ throw new OutViolationError(
2910
+ `'${t.name}': output does not satisfy declared spec`
2911
+ );
2912
+ }
2913
+ } else if (simplePid === -1) {
2914
+ const produced2 = outputs.placesWithTokens();
2915
+ const result = validateOutSpec(t.name, t.outputSpec, produced2);
2916
+ if (result === null) {
2917
+ throw new OutViolationError(
2918
+ `'${t.name}': output does not satisfy declared spec`
2919
+ );
2920
+ }
2921
+ }
2922
+ }
2923
+ const produced = [];
2924
+ for (const entry of outputs.entries()) {
2925
+ const pid = prog.compiled.placeId(entry.place);
2926
+ this.tokenQueues[pid].push(entry.token);
2927
+ produced.push(entry.token);
2928
+ this.setMarkingBit(pid);
2929
+ this.markDirty(pid);
2930
+ this.emitEvent({
2931
+ type: "token-added",
2932
+ timestamp: Date.now(),
2933
+ placeName: entry.place.name,
2934
+ token: entry.token
2935
+ });
2936
+ }
2937
+ this.markTransitionDirty(tid);
2938
+ this.emitEvent({
2939
+ type: "transition-completed",
2940
+ timestamp: Date.now(),
2941
+ transitionName: t.name,
2942
+ producedTokens: produced,
2943
+ durationMs: performance.now() - startMs
2944
+ });
2945
+ } catch (e) {
2946
+ const err = e instanceof Error ? e : new Error(String(e));
2947
+ this.emitEvent({
2948
+ type: "transition-failed",
2949
+ timestamp: Date.now(),
2950
+ transitionName: t.name,
2951
+ errorMessage: err.message,
2952
+ exceptionType: err.name,
2953
+ stack: err.stack
2954
+ });
2955
+ this.markTransitionDirty(tid);
2956
+ }
2957
+ }
2958
+ this.completionQueue.length = 0;
2959
+ }
2960
+ // ======================== External Events ========================
2961
+ processExternalEvents() {
2962
+ if (this.externalQueue.length === 0) return;
2963
+ const prog = this.program;
2964
+ const len = this.externalQueue.length;
2965
+ for (let i = 0; i < len; i++) {
2966
+ const event = this.externalQueue[i];
2967
+ try {
2968
+ const pid = prog.compiled.placeId(event.place);
2969
+ this.tokenQueues[pid].push(event.token);
2970
+ this.setMarkingBit(pid);
2971
+ this.markDirty(pid);
2972
+ this.emitEvent({
2973
+ type: "token-added",
2974
+ timestamp: Date.now(),
2975
+ placeName: event.place.name,
2976
+ token: event.token
2977
+ });
2978
+ event.resolve(true);
2979
+ } catch (e) {
2980
+ event.reject(e instanceof Error ? e : new Error(String(e)));
2981
+ }
2982
+ }
2983
+ this.externalQueue.length = 0;
2984
+ }
2985
+ drainPendingExternalEvents() {
2986
+ while (this.externalQueue.length > 0) {
2987
+ this.externalQueue.shift().resolve(false);
2988
+ }
2989
+ }
2990
+ // ======================== Await Work ========================
2991
+ async awaitWork() {
2992
+ if (this.completionQueue.length > 0 || this.externalQueue.length > 0) return;
2993
+ await Promise.resolve();
2994
+ if (this.completionQueue.length > 0 || this.externalQueue.length > 0 || this.closed) return;
2995
+ const promises = this.awaitPromises;
2996
+ promises.length = 0;
2997
+ if (this.inFlightCount > 0) {
2998
+ const arr = this.racePromises;
2999
+ arr.length = 0;
3000
+ for (let tid = 0; tid < this.program.transitionCount; tid++) {
3001
+ if (this.inFlightPromises[tid] !== null) {
3002
+ arr.push(this.inFlightPromises[tid]);
3003
+ }
3004
+ }
3005
+ if (arr.length > 0) {
3006
+ promises.push(Promise.race(arr));
3007
+ }
3008
+ }
3009
+ promises.push(new Promise((resolve) => {
3010
+ this.wakeUpResolve = resolve;
3011
+ }));
3012
+ const timerMs = this.millisUntilNextTimedTransition();
3013
+ if (timerMs > 0 && timerMs < Infinity) {
3014
+ promises.push(new Promise((r) => setTimeout(r, timerMs)));
3015
+ }
3016
+ if (promises.length > 0) {
3017
+ await Promise.race(promises);
3018
+ }
3019
+ this.wakeUpResolve = null;
3020
+ }
3021
+ millisUntilNextTimedTransition() {
3022
+ const nowMs = performance.now();
3023
+ const prog = this.program;
3024
+ const tc = prog.transitionCount;
3025
+ let minWaitMs = Infinity;
3026
+ for (let tid = 0; tid < tc; tid++) {
3027
+ if (!this.enabledFlags[tid]) continue;
3028
+ const enabledMs = this.enabledAtMs[tid];
3029
+ const elapsedMs = nowMs - enabledMs;
3030
+ const eMs = prog.earliestMs[tid];
3031
+ const remainingEarliest = eMs - elapsedMs;
3032
+ if (remainingEarliest <= 0) return 0;
3033
+ minWaitMs = Math.min(minWaitMs, remainingEarliest);
3034
+ if (prog.hasDeadline[tid]) {
3035
+ const lMs = prog.latestMs[tid];
3036
+ const remainingDeadline = lMs - elapsedMs;
3037
+ if (remainingDeadline <= 0) return 0;
3038
+ minWaitMs = Math.min(minWaitMs, remainingDeadline);
3039
+ }
3040
+ }
3041
+ return minWaitMs;
3042
+ }
3043
+ wakeUp() {
3044
+ this.wakeUpResolve?.();
3045
+ }
3046
+ // ======================== Dirty Set Helpers ========================
3047
+ hasDirtyBits() {
3048
+ for (let w = 0; w < this.transitionWords; w++) {
3049
+ if (this.dirtyBitmap[w] !== 0) return true;
3050
+ }
3051
+ return false;
3052
+ }
3053
+ // ======================== Lazy Marking Sync ========================
3054
+ syncMarkingFromQueues() {
3055
+ const prog = this.program;
3056
+ const m = Marking.empty();
3057
+ for (let pid = 0; pid < prog.placeCount; pid++) {
3058
+ const q = this.tokenQueues[pid];
3059
+ if (q.length === 0) continue;
3060
+ const place2 = prog.places[pid];
3061
+ for (let i = 0; i < q.length; i++) {
3062
+ m.addToken(place2, q[i]);
3063
+ }
3064
+ }
3065
+ this.marking = m;
3066
+ return m;
3067
+ }
3068
+ // ======================== State Inspection ========================
3069
+ getMarking() {
3070
+ return this.marking ?? this.syncMarkingFromQueues();
3071
+ }
3072
+ snapshotMarking() {
3073
+ const prog = this.program;
3074
+ const snap = /* @__PURE__ */ new Map();
3075
+ for (let pid = 0; pid < prog.placeCount; pid++) {
3076
+ const q = this.tokenQueues[pid];
3077
+ if (q.length === 0) continue;
3078
+ snap.set(prog.places[pid].name, [...q]);
3079
+ }
3080
+ return snap;
3081
+ }
3082
+ isQuiescent() {
3083
+ return this.enabledTransitionCount === 0 && this.inFlightCount === 0;
3084
+ }
3085
+ executionId() {
3086
+ return this.startMs.toString(16);
3087
+ }
3088
+ close() {
3089
+ this.running = false;
3090
+ this.closed = true;
3091
+ this.wakeUp();
3092
+ }
3093
+ // ======================== Event Emission ========================
3094
+ emitEvent(event) {
3095
+ if (this.eventStoreEnabled) {
3096
+ this.eventStore.append(event);
3097
+ }
3098
+ }
3099
+ };
3100
+ var TimeoutSentinel2 = class extends Error {
3101
+ constructor() {
3102
+ super("action timeout");
3103
+ this.name = "TimeoutSentinel";
3104
+ }
3105
+ };
1897
3106
  export {
1898
3107
  BitmapNetExecutor,
1899
3108
  CompiledNet,
@@ -1903,6 +3112,8 @@ export {
1903
3112
  OutViolationError,
1904
3113
  PetriNet,
1905
3114
  PetriNetBuilder,
3115
+ PrecompiledNet,
3116
+ PrecompiledNetExecutor,
1906
3117
  TokenInput,
1907
3118
  TokenOutput,
1908
3119
  Transition,