@grundyjs/algiviz 0.9.0 → 0.11.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/CHANGELOG.md +21 -1
- package/README.md +10 -9
- package/dist/array/player.d.ts +3 -3
- package/dist/array/player.js +1 -1
- package/dist/playback/index.d.ts +1 -0
- package/dist/playback/index.js +1 -0
- package/dist/playback/player.d.ts +6 -2
- package/dist/playback/player.js +31 -10
- package/dist/playback/state-player.d.ts +9 -0
- package/dist/playback/state-player.js +36 -0
- package/package.json +1 -1
- package/ru/README.md +10 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.11.0 — 2026-11-02
|
|
4
|
+
|
|
5
|
+
- Add createStatePlayer() for lazy playback of plain immutable states without
|
|
6
|
+
step wrappers, event fields or an isTerminal predicate.
|
|
7
|
+
- Consume one state per request without lookahead. A request after the final
|
|
8
|
+
state detects exhaustion, preserves the last frame and index, and completes
|
|
9
|
+
playback or starts an explicitly configured final hold.
|
|
10
|
+
- Support manual and timed playback, infinite sources, disposal and source errors.
|
|
11
|
+
- Simplify the interactive minimal example and English/Russian READMEs; update
|
|
12
|
+
the highlighted GIF to show the extra completion request.
|
|
13
|
+
|
|
14
|
+
## 0.10.0 — 2026-10-27
|
|
15
|
+
|
|
16
|
+
- Default finalHoldMs to zero for generic and array players.
|
|
17
|
+
- Make stepDurationMs optional for manual nextStep playback; advance(0) redraws,
|
|
18
|
+
while positive timed advancement requires an explicit duration.
|
|
19
|
+
- Allow array players to omit the options object entirely.
|
|
20
|
+
- Simplify the minimal example and both READMEs; refresh the highlighted GIF.
|
|
21
|
+
- Preserve explicit timing behavior and timeline requirements.
|
|
22
|
+
|
|
23
|
+
## 0.9.0 — 2026-10-27
|
|
4
24
|
|
|
5
25
|
- Add player.nextStep() for exact manual stepping in generic and array players.
|
|
6
26
|
- Finish an in-progress transition without reading another snapshot; at a boundary,
|
package/README.md
CHANGED
|
@@ -55,14 +55,14 @@ module through a bundler that resolves imports from the installed npm package.
|
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
```js
|
|
58
|
-
import {
|
|
58
|
+
import { createStatePlayer } from '@grundyjs/algiviz/playback';
|
|
59
59
|
import { createSceneRenderer } from '@grundyjs/algiviz/scene';
|
|
60
60
|
|
|
61
61
|
const ctx = document.querySelector('canvas').getContext('2d');
|
|
62
|
-
function*
|
|
63
|
-
for (const
|
|
62
|
+
function* states() {
|
|
63
|
+
for (const x of [40, 200, 360, 560]) {
|
|
64
64
|
const state = { objects: [{ id: 'circle', type: 'dot', data: { x } }] };
|
|
65
|
-
yield
|
|
65
|
+
yield state;
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
const renderer = createSceneRenderer({
|
|
@@ -73,9 +73,7 @@ const renderer = createSceneRenderer({
|
|
|
73
73
|
ctx.fill();
|
|
74
74
|
},
|
|
75
75
|
});
|
|
76
|
-
const player =
|
|
77
|
-
stepDurationMs: 1000, finalHoldMs: 0,
|
|
78
|
-
isTerminal: step => step.event === 'done',
|
|
76
|
+
const player = createStatePlayer(states(), {
|
|
79
77
|
render(frame) {
|
|
80
78
|
ctx.clearRect(0, 0, 600, 160);
|
|
81
79
|
renderer.render(ctx, frame);
|
|
@@ -84,7 +82,9 @@ const player = createPlayer(steps(), {
|
|
|
84
82
|
document.querySelector('#next').onclick = () => player.nextStep();
|
|
85
83
|
```
|
|
86
84
|
|
|
87
|
-
|
|
85
|
+
After the last state, one more click detects generator exhaustion and keeps the final position. No lookahead is performed.
|
|
86
|
+
|
|
87
|
+
The first frame (`x = 40`) is drawn immediately inside `createStatePlayer(...)`. Each
|
|
88
88
|
click requests the next state: the player resumes the loop until the next
|
|
89
89
|
`yield`, then renders. There is no interpolation here: the handler draws only
|
|
90
90
|
`current`. Each snapshot is created afresh and never mutated. Call
|
|
@@ -108,7 +108,7 @@ scheduling belong to the application.
|
|
|
108
108
|
|
|
109
109
|
## Playback modes
|
|
110
110
|
|
|
111
|
-
| | Generator: `createPlayer` | History: `createTimeline` |
|
|
111
|
+
| | Generator: `createPlayer` / `createStatePlayer` | History: `createTimeline` |
|
|
112
112
|
| --- | --- | --- |
|
|
113
113
|
| Steps | Consumed on demand | Collected up front |
|
|
114
114
|
| Retention | Adjacent snapshots | Complete history |
|
|
@@ -142,6 +142,7 @@ Generators reduce retained history, not the cost of producing each snapshot.
|
|
|
142
142
|
| [Binary search](https://github.com/urffin/algiviz/blob/master/examples/binary-search.html) | First occurrence in sorted input, closed search intervals and lazy playback |
|
|
143
143
|
| [Graph BFS](https://github.com/urffin/algiviz/blob/master/examples/graph.html) | Queue, layers, shortest paths, unreachable targets and history/generator playback |
|
|
144
144
|
| [Graph DFS](https://github.com/urffin/algiviz/blob/master/examples/dfs.html) | Recursive call frames, backtracking, tree depth and paths; history/generator playback |
|
|
145
|
+
| [Maze](https://github.com/urffin/algiviz/blob/master/examples/maze.html) | Randomized DFS generation, BFS wave, DFS backtracking, cycles and unreachable exits; history/generator playback |
|
|
145
146
|
| [Tree traversal](https://github.com/urffin/algiviz/blob/master/examples/tree.html) | Custom nodes, edges and pointer with Canvas and retained SVG backends |
|
|
146
147
|
|
|
147
148
|
[Full catalog, local setup and algorithm details](https://github.com/urffin/algiviz/blob/master/docs/examples.md).
|
package/dist/array/player.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import type { ArrayStep, ArraySnapshot, ArrayEvent } from "./types.js";
|
|
|
3
3
|
import type { ArrayFrame } from "./timeline.js";
|
|
4
4
|
export type ArrayPlayer = Player<ArraySnapshot, ArrayEvent>;
|
|
5
5
|
/** Array playback adapter for array steps ending in done. */
|
|
6
|
-
export declare function createArrayPlayer(steps: Iterable<ArrayStep>, options
|
|
7
|
-
stepDurationMs
|
|
8
|
-
finalHoldMs
|
|
6
|
+
export declare function createArrayPlayer(steps: Iterable<ArrayStep>, options?: {
|
|
7
|
+
stepDurationMs?: number | undefined;
|
|
8
|
+
finalHoldMs?: number | undefined;
|
|
9
9
|
render?: (frame: ArrayFrame) => void;
|
|
10
10
|
}): ArrayPlayer;
|
package/dist/array/player.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createPlayer } from "../playback/index.js";
|
|
2
2
|
/** Array playback adapter for array steps ending in done. */
|
|
3
|
-
export function createArrayPlayer(steps, options) {
|
|
3
|
+
export function createArrayPlayer(steps, options = {}) {
|
|
4
4
|
return createPlayer(steps, { ...options, isTerminal: step => step.event.type === "done" });
|
|
5
5
|
}
|
package/dist/playback/index.d.ts
CHANGED
package/dist/playback/index.js
CHANGED
|
@@ -13,9 +13,13 @@ export interface Player<S, E> {
|
|
|
13
13
|
}
|
|
14
14
|
/** Forward-only playback. The source must reach a step accepted by isTerminal. */
|
|
15
15
|
export declare function createPlayer<S, E>(steps: Iterable<Step<S, E>>, options: {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/** Omit for manual playback; positive advance requires a duration. */
|
|
17
|
+
stepDurationMs?: number | undefined;
|
|
18
|
+
/** Final hold duration, default 0. */
|
|
19
|
+
finalHoldMs?: number | undefined;
|
|
18
20
|
isTerminal: (step: Step<S, E>) => boolean;
|
|
19
21
|
/** Called initially and on each advance or nextStep, including after completion. */
|
|
20
22
|
render?: (frame: Frame<S, E>) => void;
|
|
21
23
|
}): Player<S, E>;
|
|
24
|
+
/** Internal engine shared by explicit-step and exhaustion-based sources. */
|
|
25
|
+
export declare function createPlayback<S, E>(steps: Iterable<Step<S, E>>, options: Parameters<typeof createPlayer<S, E>>[1], finishOnExhaustion?: boolean): Player<S, E>;
|
package/dist/playback/player.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/** Forward-only playback. The source must reach a step accepted by isTerminal. */
|
|
2
2
|
export function createPlayer(steps, options) {
|
|
3
|
-
|
|
3
|
+
return createPlayback(steps, options);
|
|
4
|
+
}
|
|
5
|
+
/** Internal engine shared by explicit-step and exhaustion-based sources. */
|
|
6
|
+
export function createPlayback(steps, options, finishOnExhaustion = false) {
|
|
7
|
+
const manual = options.stepDurationMs === undefined;
|
|
8
|
+
// Unit endpoint for manual steps; positive timed advancement is disallowed.
|
|
9
|
+
const { stepDurationMs = 1, finalHoldMs = 0 } = options;
|
|
4
10
|
if (!Number.isFinite(stepDurationMs) || stepDurationMs <= 0 ||
|
|
5
11
|
!Number.isFinite(finalHoldMs) || finalHoldMs < 0) {
|
|
6
12
|
throw new RangeError("Expected positive step duration and non-negative final hold");
|
|
@@ -36,8 +42,10 @@ export function createPlayer(steps, options) {
|
|
|
36
42
|
let hold = 0;
|
|
37
43
|
let disposed = false;
|
|
38
44
|
let finished = false;
|
|
45
|
+
let exhausted = false;
|
|
46
|
+
const terminal = () => exhausted || options.isTerminal(current);
|
|
39
47
|
const complete = () => {
|
|
40
|
-
if (
|
|
48
|
+
if (terminal() && elapsed === stepDurationMs) {
|
|
41
49
|
previous = current;
|
|
42
50
|
close();
|
|
43
51
|
finished = hold >= finalHoldMs;
|
|
@@ -65,15 +73,20 @@ export function createPlayer(steps, options) {
|
|
|
65
73
|
try {
|
|
66
74
|
if (!finished) {
|
|
67
75
|
if (elapsed === stepDurationMs) {
|
|
68
|
-
if (
|
|
76
|
+
if (terminal()) {
|
|
69
77
|
hold = finalHoldMs;
|
|
70
78
|
}
|
|
71
79
|
else {
|
|
72
80
|
const next = iterator.next();
|
|
73
|
-
if (next.done)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
if (next.done) {
|
|
82
|
+
if (!finishOnExhaustion)
|
|
83
|
+
throw new RangeError("Step source ended without a terminal step");
|
|
84
|
+
exhausted = true;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
previous = current;
|
|
88
|
+
current = next.value;
|
|
89
|
+
}
|
|
77
90
|
}
|
|
78
91
|
}
|
|
79
92
|
// Assign the endpoint directly: time arithmetic can undershoot or
|
|
@@ -97,18 +110,26 @@ export function createPlayer(steps, options) {
|
|
|
97
110
|
if (!Number.isFinite(deltaMs) || deltaMs < 0) {
|
|
98
111
|
throw new RangeError("Expected finite non-negative playback time");
|
|
99
112
|
}
|
|
113
|
+
if (manual && deltaMs > 0) {
|
|
114
|
+
throw new Error("Set stepDurationMs to use advance with positive time; use nextStep for manual playback");
|
|
115
|
+
}
|
|
100
116
|
try {
|
|
101
117
|
let remaining = deltaMs;
|
|
102
118
|
while (remaining > 0 && !finished) {
|
|
103
119
|
if (elapsed === stepDurationMs) {
|
|
104
|
-
if (
|
|
120
|
+
if (terminal()) {
|
|
105
121
|
hold += Math.min(remaining, finalHoldMs - hold);
|
|
106
122
|
complete();
|
|
107
123
|
break;
|
|
108
124
|
}
|
|
109
125
|
const next = iterator.next();
|
|
110
|
-
if (next.done)
|
|
111
|
-
|
|
126
|
+
if (next.done) {
|
|
127
|
+
if (!finishOnExhaustion)
|
|
128
|
+
throw new RangeError("Step source ended without a terminal step");
|
|
129
|
+
exhausted = true;
|
|
130
|
+
complete();
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
112
133
|
previous = current;
|
|
113
134
|
current = next.value;
|
|
114
135
|
elapsed = 0;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Player } from './player.js';
|
|
2
|
+
import type { Frame } from './types.js';
|
|
3
|
+
/** Lazy state playback. Exhaustion is detected on the request after the last state.
|
|
4
|
+
* States must be immutable snapshots. Generator return values are ignored. */
|
|
5
|
+
export declare function createStatePlayer<S>(states: Iterable<S>, options?: {
|
|
6
|
+
stepDurationMs?: number | undefined;
|
|
7
|
+
finalHoldMs?: number | undefined;
|
|
8
|
+
render?: (frame: Frame<S, undefined>) => void;
|
|
9
|
+
}): Player<S, undefined>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createPlayback } from './player.js';
|
|
2
|
+
/** Lazy state playback. Exhaustion is detected on the request after the last state.
|
|
3
|
+
* States must be immutable snapshots. Generator return values are ignored. */
|
|
4
|
+
export function createStatePlayer(states, options = {}) {
|
|
5
|
+
function* steps() {
|
|
6
|
+
const iterator = states[Symbol.iterator]();
|
|
7
|
+
let index = 0, exhausted = false, failed = false;
|
|
8
|
+
try {
|
|
9
|
+
while (true) {
|
|
10
|
+
const item = iterator.next();
|
|
11
|
+
if (item.done) {
|
|
12
|
+
exhausted = true;
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
yield { index: index++, state: item.value, event: undefined };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
failed = true;
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
if (!exhausted) {
|
|
24
|
+
if (failed) {
|
|
25
|
+
try {
|
|
26
|
+
iterator.return?.();
|
|
27
|
+
}
|
|
28
|
+
catch { /* Preserve source failure. */ }
|
|
29
|
+
}
|
|
30
|
+
else
|
|
31
|
+
iterator.return?.();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return createPlayback(steps(), { ...options, isTerminal: () => false }, true);
|
|
36
|
+
}
|
package/package.json
CHANGED
package/ru/README.md
CHANGED
|
@@ -54,14 +54,14 @@ npm install @grundyjs/algiviz
|
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
```js
|
|
57
|
-
import {
|
|
57
|
+
import { createStatePlayer } from '@grundyjs/algiviz/playback';
|
|
58
58
|
import { createSceneRenderer } from '@grundyjs/algiviz/scene';
|
|
59
59
|
|
|
60
60
|
const ctx = document.querySelector('canvas').getContext('2d');
|
|
61
|
-
function*
|
|
62
|
-
for (const
|
|
61
|
+
function* states() {
|
|
62
|
+
for (const x of [40, 200, 360, 560]) {
|
|
63
63
|
const state = { objects: [{ id: 'circle', type: 'dot', data: { x } }] };
|
|
64
|
-
yield
|
|
64
|
+
yield state;
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
const renderer = createSceneRenderer({
|
|
@@ -72,9 +72,7 @@ const renderer = createSceneRenderer({
|
|
|
72
72
|
ctx.fill();
|
|
73
73
|
},
|
|
74
74
|
});
|
|
75
|
-
const player =
|
|
76
|
-
stepDurationMs: 1000, finalHoldMs: 0,
|
|
77
|
-
isTerminal: step => step.event === 'done',
|
|
75
|
+
const player = createStatePlayer(states(), {
|
|
78
76
|
render(frame) {
|
|
79
77
|
ctx.clearRect(0, 0, 600, 160);
|
|
80
78
|
renderer.render(ctx, frame);
|
|
@@ -83,7 +81,9 @@ const player = createPlayer(steps(), {
|
|
|
83
81
|
document.querySelector('#next').onclick = () => player.nextStep();
|
|
84
82
|
```
|
|
85
83
|
|
|
86
|
-
|
|
84
|
+
После последнего состояния ещё один клик обнаруживает конец генератора и сохраняет позицию круга. Чтения вперёд нет.
|
|
85
|
+
|
|
86
|
+
Первый кадр (`x = 40`) рисуется сразу внутри `createStatePlayer(...)`. Каждый клик
|
|
87
87
|
запрашивает следующее состояние: плеер возобновляет цикл до следующего
|
|
88
88
|
`yield`, затем вызывает отрисовку. Здесь нет плавного перехода: обработчик рисует
|
|
89
89
|
только `current`. Каждый снимок создаётся заново и больше не изменяется. При
|
|
@@ -106,7 +106,7 @@ document.querySelector('#next').onclick = () => player.nextStep();
|
|
|
106
106
|
|
|
107
107
|
## Режимы воспроизведения
|
|
108
108
|
|
|
109
|
-
| | Генератор: `createPlayer` | История: `createTimeline` |
|
|
109
|
+
| | Генератор: `createPlayer` / `createStatePlayer` | История: `createTimeline` |
|
|
110
110
|
| --- | --- | --- |
|
|
111
111
|
| Получение шагов | По мере продвижения | Все шаги заранее |
|
|
112
112
|
| Хранение | Соседние снимки | Вся история |
|
|
@@ -140,6 +140,7 @@ document.querySelector('#next').onclick = () => player.nextStep();
|
|
|
140
140
|
| [Бинарный поиск](https://github.com/urffin/algiviz/blob/master/examples/binary-search.html) | Первое вхождение в отсортированном массиве, замкнутый интервал поиска и ленивое воспроизведение |
|
|
141
141
|
| [BFS графа](https://github.com/urffin/algiviz/blob/master/examples/graph.html) | Очередь, слои, кратчайшие пути, недостижимая цель и история/генератор |
|
|
142
142
|
| [DFS графа](https://github.com/urffin/algiviz/blob/master/examples/dfs.html) | Стек вызовов, возвраты, глубина и путь дерева; история/генератор |
|
|
143
|
+
| [Лабиринт](https://github.com/urffin/algiviz/blob/master/examples/maze.html) | Генерация рандомизированным DFS, волна BFS, поиск DFS, циклы и недостижимый выход; история/генератор |
|
|
143
144
|
| [Обход дерева](https://github.com/urffin/algiviz/blob/master/examples/tree.html) | Собственные узлы, рёбра и указатель с Canvas и SVG с сохранением элементов |
|
|
144
145
|
|
|
145
146
|
[Полный каталог, локальный запуск и особенности алгоритмов](https://github.com/urffin/algiviz/blob/master/docs/ru/examples.md).
|