fireworks-engine 1.0.1 → 1.0.2
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 +47 -0
- package/index.d.ts +18 -1
- package/package.json +2 -2
- package/src/burstEvents.js +6 -3
- package/src/burstSequence.js +67 -0
- package/src/burstSpawn.js +5 -4
- package/src/rocketLaunch.js +33 -12
- package/src/runtime.js +14 -0
package/README.md
CHANGED
|
@@ -85,6 +85,53 @@ for both construction and start/update configuration.
|
|
|
85
85
|
|
|
86
86
|
## Public API
|
|
87
87
|
|
|
88
|
+
### Tracked victory boundaries
|
|
89
|
+
|
|
90
|
+
For the supported game victory configuration, opt in per launch:
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
const fireworks = createFireworksEngine({
|
|
94
|
+
onBurst: ({ x, y, sequence }) => {
|
|
95
|
+
// sequence is available on tracked victory bursts only.
|
|
96
|
+
// It describes this explosion now, never a delayed completion signal.
|
|
97
|
+
if (sequence?.isFirst || sequence?.isLast) {
|
|
98
|
+
// Trigger a prepared sound here.
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
fireworks.launchPresetAt('victory-finale', 210, 90, {
|
|
103
|
+
trackBurstSequence: true,
|
|
104
|
+
sizeScale: 1.05,
|
|
105
|
+
timeScale: 0.95,
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`sequence` contains an opaque symbol `id`, zero-based materialization `index`,
|
|
110
|
+
and boolean `isFirst`/`isLast`. Each additive launch has its own identity and
|
|
111
|
+
boundaries. Future rocket and delayed cascade bursts are reserved before the
|
|
112
|
+
first explosion; particles fading after their first appearance do not count
|
|
113
|
+
as future bursts. A final burst is flagged during its own normal `onBurst`
|
|
114
|
+
callback, not when the animation later becomes idle. A one-burst sequence has
|
|
115
|
+
both flags; no extra callbacks are generated for cancellation or empty work.
|
|
116
|
+
|
|
117
|
+
This deliberately narrow opt-in accepts only `victory-finale`, `count` omitted
|
|
118
|
+
or exactly `1`, `sizeScale: 1.05`, `timeScale: 0.95`, and no other launch keys.
|
|
119
|
+
Effective engine visual and performance settings must match defaults (including
|
|
120
|
+
`sizeScale: 1`, burst components, rocket/tail settings and performance budgets;
|
|
121
|
+
default queue throttling is **off**). Constructor canvas/runtime references and
|
|
122
|
+
callbacks may be customized. Engine timeScale may differ because the launch
|
|
123
|
+
explicitly sets it. Other tracking configurations, including `count: 0/2` and
|
|
124
|
+
queued engines, return `false` before creating a canvas, scheduling work,
|
|
125
|
+
changing timeScale, drawing random numbers or notifying callbacks. Calls without
|
|
126
|
+
`trackBurstSequence: true` keep their existing behavior and `{ x, y }` payload.
|
|
127
|
+
|
|
128
|
+
Changing effective visual/performance configuration via `updateOptions` during
|
|
129
|
+
a sequence interrupts its tracking permanently, even if defaults are restored.
|
|
130
|
+
The visual effect continues normally, with subsequent callbacks lacking
|
|
131
|
+
`sequence`. `stop`, `destroy` and replacement starts invalidate all old tracking;
|
|
132
|
+
no synthetic final sound should be added. Do not mutate engine internals directly.
|
|
133
|
+
This is a component-creation synchronization signal, not an audio-output timestamp.
|
|
134
|
+
|
|
88
135
|
Primary exports:
|
|
89
136
|
|
|
90
137
|
- `FireworksEngine`
|
package/index.d.ts
CHANGED
|
@@ -192,6 +192,23 @@ export interface FireworksRocketLaunchOptions extends FireworksBurstOverrideOpti
|
|
|
192
192
|
export interface FireworksBurstEvent {
|
|
193
193
|
x: number;
|
|
194
194
|
y: number;
|
|
195
|
+
/** Present only for an opted-in, supported victory-finale launch. */
|
|
196
|
+
sequence?: {
|
|
197
|
+
/** Opaque identity, unique per launch within this engine. */
|
|
198
|
+
id: symbol;
|
|
199
|
+
index: number;
|
|
200
|
+
isFirst: boolean;
|
|
201
|
+
isLast: boolean;
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export interface FireworksPresetLaunchOptions extends FireworksRocketLaunchOptions {
|
|
206
|
+
/** Only victory-finale with count=1, sizeScale=1.05, timeScale=0.95 and
|
|
207
|
+
* default effective visual/performance engine configuration is supported.
|
|
208
|
+
* Unsupported tracking requests return false before launch side effects.
|
|
209
|
+
* Changing that configuration during a launch interrupts its tracking.
|
|
210
|
+
*/
|
|
211
|
+
trackBurstSequence?: boolean;
|
|
195
212
|
}
|
|
196
213
|
|
|
197
214
|
export type FireworksBurstCallback = (event: FireworksBurstEvent) => void;
|
|
@@ -636,7 +653,7 @@ export declare class FireworksEngine {
|
|
|
636
653
|
name: FireworksEnginePresetName | FireworksLaunchPresetName | string,
|
|
637
654
|
targetX: number,
|
|
638
655
|
targetY: number,
|
|
639
|
-
options?:
|
|
656
|
+
options?: FireworksPresetLaunchOptions,
|
|
640
657
|
): boolean;
|
|
641
658
|
/**
|
|
642
659
|
* Launches a rocket from a specific start point toward a target point.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fireworks-engine",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"license": "BSD-2-Clause",
|
|
5
5
|
"description": "Reusable canvas fireworks runtime package for standalone integrations and demos.",
|
|
6
6
|
"author": "coinexplain",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"scripts": {
|
|
42
42
|
"smoke": "node --check src/index.js && node --input-type=module -e \"const mod = await import('./src/index.js'); if (typeof mod.createFireworksEngine !== 'function' || typeof mod.resolveFireworksEnginePreset !== 'function' || typeof mod.default !== 'function') throw new Error('package smoke failed'); console.log('package smoke ok');\"",
|
|
43
43
|
"typecheck": "tsc --noEmit --project tsconfig.types.json",
|
|
44
|
-
"test": "npm run smoke && npm run typecheck && node --test test/burst-events.test.js"
|
|
44
|
+
"test": "npm run smoke && npm run typecheck && node --test test/burst-events.test.js test/burst-sequence.test.js"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"typescript": "^5.9.3"
|
package/src/burstEvents.js
CHANGED
|
@@ -4,15 +4,18 @@ export function isCurrentRun(engine, generation) {
|
|
|
4
4
|
return engine.runGeneration === generation;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import { materializeBurstWork } from './burstSequence.js';
|
|
8
|
+
|
|
9
|
+
export function createBurstEvent(engine, x, y, work) {
|
|
10
|
+
return { generation: engine.runGeneration, x, y, notified: false, work };
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export function notifyBurst(engine, burst) {
|
|
12
14
|
if (burst.notified || !isCurrentRun(engine, burst.generation)) return;
|
|
13
15
|
burst.notified = true;
|
|
16
|
+
const sequence = materializeBurstWork(engine, burst.work);
|
|
14
17
|
try {
|
|
15
|
-
engine.onBurstCallback?.({ x: burst.x, y: burst.y });
|
|
18
|
+
engine.onBurstCallback?.({ x: burst.x, y: burst.y, ...(sequence ? { sequence } : {}) });
|
|
16
19
|
} catch {
|
|
17
20
|
// Consumer code must not interrupt rendering or lifecycle cleanup.
|
|
18
21
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { buildEngineConfig } from './configBuilder.js';
|
|
2
|
+
|
|
3
|
+
// Internal launch context; never normalized into public visual options.
|
|
4
|
+
export const BURST_SEQUENCE = Symbol('burstSequence');
|
|
5
|
+
export const BURST_WORK = Symbol('burstWork');
|
|
6
|
+
const defaults = buildEngineConfig();
|
|
7
|
+
const groups = ['rocket', 'rocketTailEmbers', 'burstForms', 'burstSparks',
|
|
8
|
+
'burstCenterFlash', 'burstTwinkleCloud', 'performance'];
|
|
9
|
+
const allowed = new Set(['trackBurstSequence', 'sizeScale', 'timeScale', 'count']);
|
|
10
|
+
const sequencesByEngine = new WeakMap();
|
|
11
|
+
|
|
12
|
+
export function invalidateBurstSequences(engine) {
|
|
13
|
+
const sequences = sequencesByEngine.get(engine);
|
|
14
|
+
if (!sequences) return;
|
|
15
|
+
for (const sequence of sequences) sequence.active = false;
|
|
16
|
+
sequences.clear();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function supportsSequenceConfig(engine) {
|
|
20
|
+
return engine.sizeScale === defaults.sizeScale
|
|
21
|
+
&& engine.enableBurstQueueThrottling === defaults.toggles.enableBurstQueueThrottling
|
|
22
|
+
&& engine.maxBurstParticlesPerFrame === defaults.performance.maxBurstParticlesPerFrame
|
|
23
|
+
&& groups.every((key) => JSON.stringify(engine[key]) === JSON.stringify(defaults[key]));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function supportsSequenceLaunch(engine, name, options) {
|
|
27
|
+
return name === 'victory-finale' && options.sizeScale === 1.05 && options.timeScale === 0.95
|
|
28
|
+
&& (options.count === undefined || options.count === 1)
|
|
29
|
+
&& Object.keys(options).every((key) => allowed.has(key))
|
|
30
|
+
&& supportsSequenceConfig(engine);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createBurstSequence(engine) {
|
|
34
|
+
const sequence = { id: Symbol('victory-finale'), generation: engine.runGeneration,
|
|
35
|
+
pending: 0, index: 0, active: true };
|
|
36
|
+
if (!sequencesByEngine.has(engine)) sequencesByEngine.set(engine, new Set());
|
|
37
|
+
sequencesByEngine.get(engine).add(sequence);
|
|
38
|
+
return sequence;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function discardBurstSequence(engine, sequence) {
|
|
42
|
+
if (!sequence) return;
|
|
43
|
+
sequence.active = false;
|
|
44
|
+
sequencesByEngine.get(engine)?.delete(sequence);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function reserveBurstWork(sequence, children = []) {
|
|
48
|
+
sequence.pending += 1;
|
|
49
|
+
return { sequence, children, materialized: false };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function materializeBurstWork(engine, work) {
|
|
53
|
+
if (!work || work.materialized) return null;
|
|
54
|
+
const sequence = work.sequence;
|
|
55
|
+
if (!sequence.active || engine.runGeneration !== sequence.generation) return null;
|
|
56
|
+
// Configuration changes during a launch interrupt its tracking, not its visuals.
|
|
57
|
+
if (!supportsSequenceConfig(engine)) {
|
|
58
|
+
sequence.active = false;
|
|
59
|
+
sequencesByEngine.get(engine)?.delete(sequence);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
work.materialized = true;
|
|
63
|
+
sequence.pending -= 1;
|
|
64
|
+
const index = sequence.index++;
|
|
65
|
+
if (sequence.pending === 0) sequencesByEngine.get(engine)?.delete(sequence);
|
|
66
|
+
return { id: sequence.id, index, isFirst: index === 0, isLast: sequence.pending === 0 };
|
|
67
|
+
}
|
package/src/burstSpawn.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createBurstEvent, isCurrentRun, notifyBurst } from './burstEvents.js';
|
|
2
|
+
import { BURST_WORK } from './burstSequence.js';
|
|
2
3
|
import {
|
|
3
4
|
BURST_FLASH_ATTACK_UNIT,
|
|
4
5
|
DEFAULT_BURST_SINGLE_COLOR,
|
|
@@ -264,7 +265,7 @@ function spawnBurstExtras(engine, burst, flashOptions, cloudOptions) {
|
|
|
264
265
|
return isCurrentRun(engine, burst.generation);
|
|
265
266
|
}
|
|
266
267
|
|
|
267
|
-
function enqueueBurst(engine, x, y, options = {}) {
|
|
268
|
+
function enqueueBurst(engine, x, y, options = {}, work) {
|
|
268
269
|
if (!engine.running) {
|
|
269
270
|
return false;
|
|
270
271
|
}
|
|
@@ -274,7 +275,7 @@ function enqueueBurst(engine, x, y, options = {}) {
|
|
|
274
275
|
resolveBurstCurrentConfig(engine),
|
|
275
276
|
);
|
|
276
277
|
const requests = createBurstRequests(x, y, burstOptions, options);
|
|
277
|
-
const burst = createBurstEvent(engine, x, y);
|
|
278
|
+
const burst = createBurstEvent(engine, x, y, work);
|
|
278
279
|
requests.forEach((request) => { request.burstEvent = burst; });
|
|
279
280
|
|
|
280
281
|
if (requests.length === 0) {
|
|
@@ -611,7 +612,7 @@ export function drainBurstQueue(engine) {
|
|
|
611
612
|
export function launchBurst(engine, x, y, options = {}) {
|
|
612
613
|
const normalizedOptions = normalizeGroupedPublicOptions(options);
|
|
613
614
|
if (engine.enableBurstQueueThrottling) {
|
|
614
|
-
return enqueueBurst(engine, x, y, normalizedOptions);
|
|
615
|
+
return enqueueBurst(engine, x, y, normalizedOptions, options[BURST_WORK]);
|
|
615
616
|
}
|
|
616
617
|
|
|
617
618
|
if (!engine.running) {
|
|
@@ -622,7 +623,7 @@ export function launchBurst(engine, x, y, options = {}) {
|
|
|
622
623
|
normalizedOptions,
|
|
623
624
|
resolveBurstCurrentConfig(engine),
|
|
624
625
|
);
|
|
625
|
-
const burst = createBurstEvent(engine, x, y);
|
|
626
|
+
const burst = createBurstEvent(engine, x, y, options[BURST_WORK]);
|
|
626
627
|
if (!spawnBurstExtras(engine, burst, normalizedOptions, normalizedOptions)) return true;
|
|
627
628
|
const requests = createBurstRequests(x, y, burstOptions);
|
|
628
629
|
for (const request of requests) {
|
package/src/rocketLaunch.js
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
normalizeHexColor,
|
|
21
21
|
} from './colorUtils.js';
|
|
22
22
|
import { buildBurstOptions } from './burstConfigBuilder.js';
|
|
23
|
+
import { BURST_SEQUENCE, BURST_WORK, reserveBurstWork } from './burstSequence.js';
|
|
23
24
|
import {
|
|
24
25
|
launchBurst,
|
|
25
26
|
resolveBurstBaseColor,
|
|
@@ -333,6 +334,23 @@ function launchRocketWithExplicitTarget(engine, options, explicitTarget) {
|
|
|
333
334
|
}
|
|
334
335
|
|
|
335
336
|
|
|
337
|
+
function resolveCascadeCount(count, sizeScale) {
|
|
338
|
+
return clamp(Math.floor((count ?? 0) * sizeScale), 0, ROCKET_CASCADE_LIMITS.burstCountMax);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Only the validated victory recipe reaches this boundary: rocket children,
|
|
342
|
+
// followed by their finite direct-burst cascades. Reserve before any callback.
|
|
343
|
+
function reserveVictoryWork(sequence, rocket) {
|
|
344
|
+
const childOptions = resolveDefaultCascadeChildLaunchOptions(rocket, rocket.cascade);
|
|
345
|
+
const normalizedChild = normalizeGroupedPublicOptions(childOptions);
|
|
346
|
+
const leafCount = resolveCascadeCount(normalizedChild.rocket.cascade.bursts, normalizedChild.sizeScale);
|
|
347
|
+
const children = Array.from({ length: rocket.cascade.bursts }, () => {
|
|
348
|
+
const leaves = Array.from({ length: leafCount }, () => reserveBurstWork(sequence));
|
|
349
|
+
return reserveBurstWork(sequence, leaves);
|
|
350
|
+
});
|
|
351
|
+
return reserveBurstWork(sequence, children);
|
|
352
|
+
}
|
|
353
|
+
|
|
336
354
|
function createRocket(engine, options = {}, explicitTarget = null) {
|
|
337
355
|
const normalizedOptions = normalizeGroupedPublicOptions(options);
|
|
338
356
|
const {
|
|
@@ -520,16 +538,8 @@ function createRocket(engine, options = {}, explicitTarget = null) {
|
|
|
520
538
|
burstCurrentConfig,
|
|
521
539
|
)
|
|
522
540
|
: null;
|
|
523
|
-
const cascadeBurstCount =
|
|
524
|
-
|
|
525
|
-
0,
|
|
526
|
-
ROCKET_CASCADE_LIMITS.burstCountMax,
|
|
527
|
-
);
|
|
528
|
-
const cascadeChildBurstCount = clamp(
|
|
529
|
-
Math.floor((rocketCascadeInput.childBursts ?? rocketCascadeInput.bursts ?? 0) * sizeScale),
|
|
530
|
-
0,
|
|
531
|
-
ROCKET_CASCADE_LIMITS.burstCountMax,
|
|
532
|
-
);
|
|
541
|
+
const cascadeBurstCount = resolveCascadeCount(rocketCascadeInput.bursts, sizeScale);
|
|
542
|
+
const cascadeChildBurstCount = resolveCascadeCount(rocketCascadeInput.childBursts ?? rocketCascadeInput.bursts, sizeScale);
|
|
533
543
|
const burstCascadeDelayMs = resolveScaledOverride(
|
|
534
544
|
rocketCascadeInput.delay,
|
|
535
545
|
ROCKET_CASCADE_LIMITS.defaultDelayMs,
|
|
@@ -618,6 +628,12 @@ function createRocket(engine, options = {}, explicitTarget = null) {
|
|
|
618
628
|
simple: !!normalizedOptions.simple,
|
|
619
629
|
};
|
|
620
630
|
|
|
631
|
+
const work = options[BURST_WORK]
|
|
632
|
+
?? (options[BURST_SEQUENCE] ? reserveVictoryWork(options[BURST_SEQUENCE], rocket) : null);
|
|
633
|
+
if (work) {
|
|
634
|
+
rocket.sequenceWork = work;
|
|
635
|
+
rocket.burst = { ...rocket.burst, [BURST_WORK]: work };
|
|
636
|
+
}
|
|
621
637
|
engine.rockets.push(rocket);
|
|
622
638
|
return rocket;
|
|
623
639
|
}
|
|
@@ -696,7 +712,10 @@ export function scheduleCascadeBursts(engine, rocket) {
|
|
|
696
712
|
cascade,
|
|
697
713
|
distance,
|
|
698
714
|
);
|
|
699
|
-
launchRocketFromTo(engine, originX, originY, x, y,
|
|
715
|
+
launchRocketFromTo(engine, originX, originY, x, y, {
|
|
716
|
+
...childLaunchOptions,
|
|
717
|
+
...(rocket.sequenceWork ? { [BURST_WORK]: rocket.sequenceWork.children[index] } : {}),
|
|
718
|
+
});
|
|
700
719
|
if (engine.runGeneration !== generation) return;
|
|
701
720
|
}
|
|
702
721
|
return;
|
|
@@ -716,7 +735,9 @@ export function scheduleCascadeBursts(engine, rocket) {
|
|
|
716
735
|
spreadY,
|
|
717
736
|
);
|
|
718
737
|
|
|
719
|
-
launchBurst(engine, x, y,
|
|
738
|
+
launchBurst(engine, x, y, rocket.sequenceWork
|
|
739
|
+
? { ...burstOptions, [BURST_WORK]: rocket.sequenceWork.children[index] }
|
|
740
|
+
: burstOptions);
|
|
720
741
|
}, resolveScaledDelay(engine, delay * (index + 1)));
|
|
721
742
|
}
|
|
722
743
|
}
|
package/src/runtime.js
CHANGED
|
@@ -4,6 +4,10 @@ import {
|
|
|
4
4
|
normalizeGroupedPublicOptions,
|
|
5
5
|
} from './valueNormalizers.js';
|
|
6
6
|
import { buildEngineConfig } from './configBuilder.js';
|
|
7
|
+
import {
|
|
8
|
+
BURST_SEQUENCE, createBurstSequence, discardBurstSequence, invalidateBurstSequences,
|
|
9
|
+
supportsSequenceConfig, supportsSequenceLaunch,
|
|
10
|
+
} from './burstSequence.js';
|
|
7
11
|
import {
|
|
8
12
|
resolveFireworksEnginePreset,
|
|
9
13
|
resolveFireworksLaunchPreset,
|
|
@@ -422,6 +426,9 @@ export class FireworksEngine {
|
|
|
422
426
|
}
|
|
423
427
|
|
|
424
428
|
launchPresetAt(name, targetX, targetY, options = {}) {
|
|
429
|
+
if (options?.trackBurstSequence === true && !supportsSequenceLaunch(this, name, options)) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
425
432
|
const generation = this.runGeneration;
|
|
426
433
|
if (shouldSkipHiddenLaunch(this)) {
|
|
427
434
|
return false;
|
|
@@ -446,6 +453,10 @@ export class FireworksEngine {
|
|
|
446
453
|
|
|
447
454
|
const launchOptions = { ...mergedOptions };
|
|
448
455
|
delete launchOptions.count;
|
|
456
|
+
delete launchOptions.trackBurstSequence;
|
|
457
|
+
if (options.trackBurstSequence === true) {
|
|
458
|
+
launchOptions[BURST_SEQUENCE] = createBurstSequence(this);
|
|
459
|
+
}
|
|
449
460
|
|
|
450
461
|
applyTimeScaleFromLaunchOptions(this, launchOptions);
|
|
451
462
|
|
|
@@ -460,6 +471,7 @@ export class FireworksEngine {
|
|
|
460
471
|
}
|
|
461
472
|
}
|
|
462
473
|
|
|
474
|
+
if (!launched) discardBurstSequence(this, launchOptions[BURST_SEQUENCE]);
|
|
463
475
|
return launched;
|
|
464
476
|
}
|
|
465
477
|
|
|
@@ -611,6 +623,7 @@ export class FireworksEngine {
|
|
|
611
623
|
this.maxBurstParticlesPerFrame = this.performance.maxBurstParticlesPerFrame;
|
|
612
624
|
this.particlePressureThreshold = this.performance.particlePressureThreshold;
|
|
613
625
|
this.fastRectRadiusThreshold = this.performance.fastRectRadiusThreshold;
|
|
626
|
+
if (!supportsSequenceConfig(this)) invalidateBurstSequences(this);
|
|
614
627
|
}
|
|
615
628
|
|
|
616
629
|
if (
|
|
@@ -732,6 +745,7 @@ export class FireworksEngine {
|
|
|
732
745
|
}
|
|
733
746
|
|
|
734
747
|
stop(clearCanvas = true) {
|
|
748
|
+
invalidateBurstSequences(this);
|
|
735
749
|
this.runGeneration += 1;
|
|
736
750
|
const wasRunning = this.running;
|
|
737
751
|
this.running = false;
|