@motionscript/browser 0.0.0-stage → 0.1.0-alpha.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.
Files changed (120) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +201 -0
  3. package/README.md +68 -3
  4. package/dist/asset-bytes.d.ts +50 -0
  5. package/dist/asset-bytes.d.ts.map +1 -0
  6. package/dist/asset-bytes.js +60 -0
  7. package/dist/asset-bytes.js.map +1 -0
  8. package/dist/audio/bus-graph.d.ts +43 -0
  9. package/dist/audio/bus-graph.d.ts.map +1 -0
  10. package/dist/audio/bus-graph.js +104 -0
  11. package/dist/audio/bus-graph.js.map +1 -0
  12. package/dist/audio/filter-graph.d.ts +58 -0
  13. package/dist/audio/filter-graph.d.ts.map +1 -0
  14. package/dist/audio/filter-graph.js +235 -0
  15. package/dist/audio/filter-graph.js.map +1 -0
  16. package/dist/audio/index.d.ts +5 -0
  17. package/dist/audio/index.d.ts.map +1 -0
  18. package/dist/audio/index.js +12 -0
  19. package/dist/audio/index.js.map +1 -0
  20. package/dist/audio/mixer.d.ts +59 -0
  21. package/dist/audio/mixer.d.ts.map +1 -0
  22. package/dist/audio/mixer.js +193 -0
  23. package/dist/audio/mixer.js.map +1 -0
  24. package/dist/audio/player.d.ts +49 -0
  25. package/dist/audio/player.d.ts.map +1 -0
  26. package/dist/audio/player.js +226 -0
  27. package/dist/audio/player.js.map +1 -0
  28. package/dist/browser/audio.js +2 -0
  29. package/dist/browser/audio.js.map +7 -0
  30. package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js +2 -0
  31. package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js.map +7 -0
  32. package/dist/browser/chunks/SkeletonUtils-XYDMTGTM.js +2 -0
  33. package/dist/browser/chunks/SkeletonUtils-XYDMTGTM.js.map +7 -0
  34. package/dist/browser/chunks/chunk-CTLBXZSU.js +4119 -0
  35. package/dist/browser/chunks/chunk-CTLBXZSU.js.map +7 -0
  36. package/dist/browser/chunks/chunk-FQ32PDIU.js +2 -0
  37. package/dist/browser/chunks/chunk-FQ32PDIU.js.map +7 -0
  38. package/dist/browser/chunks/chunk-FY3BTKY4.js +2 -0
  39. package/dist/browser/chunks/chunk-FY3BTKY4.js.map +7 -0
  40. package/dist/browser/chunks/chunk-NSFLRI4P.js +2 -0
  41. package/dist/browser/chunks/chunk-NSFLRI4P.js.map +7 -0
  42. package/dist/browser/chunks/three.module-Z4EOVUWF.js +2 -0
  43. package/dist/browser/chunks/three.module-Z4EOVUWF.js.map +7 -0
  44. package/dist/browser/esbuild.wasm +0 -0
  45. package/dist/browser/index.js +2172 -0
  46. package/dist/browser/index.js.map +7 -0
  47. package/dist/browser/manifest.json +15 -0
  48. package/dist/concat.d.ts +95 -0
  49. package/dist/concat.d.ts.map +1 -0
  50. package/dist/concat.js +174 -0
  51. package/dist/concat.js.map +1 -0
  52. package/dist/decode/image-levels.d.ts +137 -0
  53. package/dist/decode/image-levels.d.ts.map +1 -0
  54. package/dist/decode/image-levels.js +477 -0
  55. package/dist/decode/image-levels.js.map +1 -0
  56. package/dist/decode/levels.d.ts +37 -0
  57. package/dist/decode/levels.d.ts.map +1 -0
  58. package/dist/decode/levels.js +68 -0
  59. package/dist/decode/levels.js.map +1 -0
  60. package/dist/decode/video-levels.d.ts +37 -0
  61. package/dist/decode/video-levels.d.ts.map +1 -0
  62. package/dist/decode/video-levels.js +95 -0
  63. package/dist/decode/video-levels.js.map +1 -0
  64. package/dist/dynamic-nodes.d.ts +9 -0
  65. package/dist/dynamic-nodes.d.ts.map +1 -0
  66. package/dist/dynamic-nodes.js +56 -0
  67. package/dist/dynamic-nodes.js.map +1 -0
  68. package/dist/engine.d.ts +59 -0
  69. package/dist/engine.d.ts.map +1 -0
  70. package/dist/engine.js +171 -0
  71. package/dist/engine.js.map +1 -0
  72. package/dist/exporter.d.ts +163 -0
  73. package/dist/exporter.d.ts.map +1 -0
  74. package/dist/exporter.js +263 -0
  75. package/dist/exporter.js.map +1 -0
  76. package/dist/getter.d.ts +10 -0
  77. package/dist/getter.d.ts.map +1 -0
  78. package/dist/getter.js +38 -0
  79. package/dist/getter.js.map +1 -0
  80. package/dist/index.d.ts +22 -0
  81. package/dist/index.d.ts.map +1 -0
  82. package/dist/index.js +72 -0
  83. package/dist/index.js.map +1 -0
  84. package/dist/measure-context.d.ts +12 -0
  85. package/dist/measure-context.d.ts.map +1 -0
  86. package/dist/measure-context.js +12 -0
  87. package/dist/measure-context.js.map +1 -0
  88. package/dist/mix-timeline-audio.d.ts +42 -0
  89. package/dist/mix-timeline-audio.d.ts.map +1 -0
  90. package/dist/mix-timeline-audio.js +63 -0
  91. package/dist/mix-timeline-audio.js.map +1 -0
  92. package/dist/output.d.ts +7 -0
  93. package/dist/output.d.ts.map +1 -0
  94. package/dist/output.js +6 -0
  95. package/dist/output.js.map +1 -0
  96. package/dist/render-context.d.ts +30 -0
  97. package/dist/render-context.d.ts.map +1 -0
  98. package/dist/render-context.js +55 -0
  99. package/dist/render-context.js.map +1 -0
  100. package/dist/screenshot.d.ts +45 -0
  101. package/dist/screenshot.d.ts.map +1 -0
  102. package/dist/screenshot.js +32 -0
  103. package/dist/screenshot.js.map +1 -0
  104. package/dist/still.d.ts +151 -0
  105. package/dist/still.d.ts.map +1 -0
  106. package/dist/still.js +222 -0
  107. package/dist/still.js.map +1 -0
  108. package/dist/storage-adapter.d.ts +367 -0
  109. package/dist/storage-adapter.d.ts.map +1 -0
  110. package/dist/storage-adapter.js +1278 -0
  111. package/dist/storage-adapter.js.map +1 -0
  112. package/dist/three/renderer.d.ts +48 -0
  113. package/dist/three/renderer.d.ts.map +1 -0
  114. package/dist/three/renderer.js +177 -0
  115. package/dist/three/renderer.js.map +1 -0
  116. package/dist/ticker.d.ts +42 -0
  117. package/dist/ticker.d.ts.map +1 -0
  118. package/dist/ticker.js +143 -0
  119. package/dist/ticker.js.map +1 -0
  120. package/package.json +74 -3
@@ -0,0 +1,477 @@
1
+ import { floorLevel, levelCap, levelFor } from "./levels";
2
+ export const IMAGE_DECODE_BUDGET_BYTES = 512 * 1024 * 1024;
3
+ /** Unselected levels older than this go, whatever the budget — bar a {@link RETAINED_LEVEL_BYTES} largest level. */
4
+ export const LEVEL_AGE_OUT_MS = 2_000;
5
+ /** A preview drawing a level at twice what it needs for this long decodes the smaller one. */
6
+ export const DOWNGRADE_AFTER_MS = 5_000;
7
+ export const IDLE_TO_FLOOR_MS = 30_000;
8
+ export const IDLE_DROP_SOURCE_MS = 60_000;
9
+ export const MAX_CONCURRENT_DECODES = 3;
10
+ /** An interactive decode covers this multiple of the demand, so a growing draw re-decodes per octave, not per rung. */
11
+ export const UPGRADE_HEADROOM = 2;
12
+ /**
13
+ * A largest level no bigger than this is neither aged out nor downgraded until
14
+ * the budget needs the room: decoding it again the next time a draw grows costs
15
+ * more, in main-thread copies and a blurry frame, than holding it.
16
+ */
17
+ export const RETAINED_LEVEL_BYTES = 16 * 1024 * 1024;
18
+ export class ImageLevelCache {
19
+ _host;
20
+ _budget;
21
+ _policy = "interactive";
22
+ _entries = new Map();
23
+ _pass = 0;
24
+ _inflight = 0;
25
+ constructor(_host, _budget = IMAGE_DECODE_BUDGET_BYTES) {
26
+ this._host = _host;
27
+ this._budget = _budget;
28
+ }
29
+ get policy() {
30
+ return this._policy;
31
+ }
32
+ setPolicy(policy) {
33
+ this._policy = policy;
34
+ }
35
+ has(src) {
36
+ return (this._entries.get(src)?.levels.length ?? 0) > 0;
37
+ }
38
+ intrinsicSize(src) {
39
+ return this._entries.get(src)?.intrinsic ?? null;
40
+ }
41
+ /** Make sure `src` has a level to draw with. First loads pick their rung by policy. */
42
+ load(src) {
43
+ const entry = this._entry(src);
44
+ if (entry.levels.length > 0)
45
+ return Promise.resolve();
46
+ entry.loading ??= this._host.decode(src, (intrinsic, vector) => {
47
+ this._learn(entry, intrinsic, vector);
48
+ if (this._policy === "exact")
49
+ return floorLevel(entry.cap);
50
+ if (entry.lastDemand !== undefined)
51
+ return this._interactiveEdge(entry, entry.lastDemand);
52
+ return levelFor(Math.min(Math.max(intrinsic.width, intrinsic.height), this._host.surfaceEdge()), entry.cap);
53
+ }).then((decoded) => {
54
+ this._learn(entry, decoded.intrinsic, decoded.vector);
55
+ this._add(entry, decoded);
56
+ this._enforceBudget();
57
+ }).finally(() => {
58
+ entry.loading = null;
59
+ this._pump();
60
+ });
61
+ return entry.loading;
62
+ }
63
+ beginPass() {
64
+ this._pass++;
65
+ }
66
+ /**
67
+ * The level one draw paints with. `footprint` is the long edge in device px
68
+ * the draw needs; absent means the source's own resolution.
69
+ */
70
+ select(src, footprint) {
71
+ const entry = this._entry(src);
72
+ const now = this._host.now();
73
+ const demand = footprint !== undefined && Number.isFinite(footprint) && footprint > 0 ? footprint : Infinity;
74
+ if (entry.levels.length === 0) {
75
+ // Remembered so the load this miss triggers starts at the right rung.
76
+ if (Number.isFinite(demand))
77
+ entry.lastDemand = Math.max(entry.lastDemand ?? 0, demand);
78
+ entry.missedPass = this._pass;
79
+ return null;
80
+ }
81
+ const need = Math.min(demand, entry.cap);
82
+ entry.passNeed = Math.max(entry.passNeed, need);
83
+ entry.passDraws++;
84
+ entry.lastUsed = now;
85
+ let level;
86
+ if (this._policy === "exact") {
87
+ const target = levelFor(need, entry.cap);
88
+ const exact = entry.levels.find((l) => l.edge === target);
89
+ if (!exact && !entry.failed.has(target))
90
+ entry.wanted.add(target);
91
+ level = exact ?? nearest(entry.levels, target);
92
+ }
93
+ else {
94
+ level = entry.levels.find((l) => l.edge >= need) ?? entry.levels[entry.levels.length - 1];
95
+ if (level.edge < 2 * need)
96
+ entry.passAllOver = false;
97
+ }
98
+ level.lastSelected = now;
99
+ level.selectedPass = this._pass;
100
+ return level;
101
+ }
102
+ /** Decide this pass's level changes, now that every draw has reported. */
103
+ endPass() {
104
+ const now = this._host.now();
105
+ for (const entry of this._entries.values()) {
106
+ const top = entry.levels[entry.levels.length - 1];
107
+ if (entry.passDraws > 0 && top) {
108
+ entry.lastDemand = entry.passNeed;
109
+ if (this._policy === "exact") {
110
+ for (const edge of entry.wanted)
111
+ this._request(entry, edge, false);
112
+ entry.wanted.clear();
113
+ }
114
+ else {
115
+ entry.short = top.edge < entry.passNeed;
116
+ if (entry.short) {
117
+ this._request(entry, this._interactiveEdge(entry, entry.passNeed), true);
118
+ }
119
+ else {
120
+ // Demand fell back under what is resident: a queued upgrade is no longer worth starting.
121
+ entry.queue = entry.queue.filter((edge) => edge <= top.edge);
122
+ }
123
+ if (entry.passAllOver && !retained(top)) {
124
+ entry.overSince ??= now;
125
+ if (now - entry.overSince >= DOWNGRADE_AFTER_MS) {
126
+ this._request(entry, this._interactiveEdge(entry, entry.passNeed), true);
127
+ }
128
+ }
129
+ else {
130
+ entry.overSince = null;
131
+ }
132
+ }
133
+ }
134
+ else {
135
+ entry.short = false;
136
+ }
137
+ entry.passNeed = 0;
138
+ entry.passDraws = 0;
139
+ entry.passAllOver = true;
140
+ this._ageOut(entry, now);
141
+ }
142
+ this._enforceBudget();
143
+ this._pump();
144
+ }
145
+ /**
146
+ * What a settling caller must wait for before the frame is right: under
147
+ * `exact`, every decode queued or running; under `interactive`, only a source
148
+ * the last pass asked for with nothing resident. An upgrade is {@link hasUpgrades}.
149
+ */
150
+ hasPending() {
151
+ for (const entry of this._entries.values()) {
152
+ if (this._blocks(entry))
153
+ return true;
154
+ }
155
+ return false;
156
+ }
157
+ /** Await what {@link hasPending} reports, and report whether there was any. */
158
+ async warm() {
159
+ if (this._policy === "interactive") {
160
+ let did = false;
161
+ for (let guard = 0; guard < 64; guard++) {
162
+ const loading = [...this._entries.values()]
163
+ .filter((e) => this._blocks(e))
164
+ .map((e) => e.loading);
165
+ if (loading.length === 0)
166
+ break;
167
+ did = true;
168
+ await Promise.all(loading.map((job) => job.catch(() => undefined)));
169
+ }
170
+ return did;
171
+ }
172
+ let did = false;
173
+ for (const entry of this._entries.values()) {
174
+ for (const edge of entry.wanted)
175
+ this._request(entry, edge, false);
176
+ entry.wanted.clear();
177
+ }
178
+ this._pump();
179
+ for (let guard = 0; guard < 64; guard++) {
180
+ const running = [...this._entries.values()]
181
+ .map((e) => e.job ?? e.loading)
182
+ .filter((job) => job !== null);
183
+ if (running.length === 0)
184
+ break;
185
+ did = true;
186
+ await Promise.all(running.map((job) => job.catch(() => undefined)));
187
+ this._pump();
188
+ }
189
+ return did;
190
+ }
191
+ /** Whether, under `interactive`, a source the last pass drew below demand has a better level on its way. */
192
+ hasUpgrades() {
193
+ if (this._policy !== "interactive")
194
+ return false;
195
+ for (const entry of this._entries.values()) {
196
+ if (this._upgrading(entry))
197
+ return true;
198
+ }
199
+ return false;
200
+ }
201
+ /** Await the upgrades {@link hasUpgrades} reports; whether one landed. */
202
+ async warmUpgrades() {
203
+ let did = false;
204
+ for (let guard = 0; guard < 64 && this._policy === "interactive"; guard++) {
205
+ this._pump();
206
+ const waiting = [...this._entries.values()].filter((e) => this._upgrading(e));
207
+ if (waiting.length === 0)
208
+ break;
209
+ const own = waiting.flatMap((e) => (e.job ? [e.job] : []));
210
+ // Queued behind other sources' decodes: those free the slot it needs.
211
+ const running = own.length > 0 ? own : [...this._entries.values()].flatMap((e) => (e.job ? [e.job] : []));
212
+ if (running.length === 0)
213
+ break;
214
+ await Promise.all(running.map((job) => job.catch(() => undefined)));
215
+ if (own.length > 0)
216
+ did = true;
217
+ }
218
+ return did;
219
+ }
220
+ stats() {
221
+ let levels = 0;
222
+ let levelBytes = 0;
223
+ let sourceBytes = 0;
224
+ for (const entry of this._entries.values()) {
225
+ levels += entry.levels.length;
226
+ for (const level of entry.levels)
227
+ levelBytes += bytesOf(level);
228
+ sourceBytes += this._host.sourceBytes(entry.src);
229
+ }
230
+ return { sources: this._entries.size, levels, levelBytes, sourceBytes, inflight: this._inflight };
231
+ }
232
+ /** Every level held for `src`, ascending — for tests and stats. */
233
+ levelsOf(src) {
234
+ return this._entries.get(src)?.levels ?? [];
235
+ }
236
+ dispose() {
237
+ for (const entry of this._entries.values()) {
238
+ for (const level of entry.levels)
239
+ this._host.released(entry.src, level);
240
+ entry.levels = [];
241
+ entry.queue = [];
242
+ }
243
+ this._entries.clear();
244
+ }
245
+ _entry(src) {
246
+ let entry = this._entries.get(src);
247
+ if (!entry) {
248
+ entry = {
249
+ src, intrinsic: null, vector: false, cap: 1, levels: [],
250
+ lastDemand: undefined, lastUsed: this._host.now(),
251
+ passNeed: 0, passDraws: 0, passAllOver: true, overSince: null,
252
+ wanted: new Set(), queue: [], job: null, failed: new Set(), loading: null,
253
+ missedPass: -1, short: false,
254
+ };
255
+ this._entries.set(src, entry);
256
+ }
257
+ return entry;
258
+ }
259
+ _blocks(entry) {
260
+ if (this._policy === "exact") {
261
+ return entry.job !== null || entry.queue.length > 0 || entry.wanted.size > 0 || entry.loading !== null;
262
+ }
263
+ return entry.levels.length === 0 && entry.missedPass === this._pass && entry.loading !== null;
264
+ }
265
+ _upgrading(entry) {
266
+ return entry.short && entry.levels.length > 0 && (entry.job !== null || entry.queue.length > 0);
267
+ }
268
+ /** The rung an interactive decode for `demand` asks for: with headroom, capped at the source. */
269
+ _interactiveEdge(entry, demand) {
270
+ return levelFor(Math.min(demand * UPGRADE_HEADROOM, entry.cap), entry.cap);
271
+ }
272
+ _learn(entry, intrinsic, vector) {
273
+ entry.intrinsic = { width: intrinsic.width, height: intrinsic.height };
274
+ entry.vector = vector;
275
+ entry.cap = levelCap(intrinsic, vector);
276
+ }
277
+ _add(entry, decoded) {
278
+ const existing = entry.levels.findIndex((l) => l.edge === decoded.edge);
279
+ if (existing >= 0) {
280
+ this._host.released(entry.src, entry.levels[existing]);
281
+ entry.levels.splice(existing, 1);
282
+ }
283
+ const level = {
284
+ edge: decoded.edge,
285
+ width: decoded.width,
286
+ height: decoded.height,
287
+ pixels: decoded.pixels,
288
+ gpu: null,
289
+ // Counts as selected now, so a level a settle pass decoded is not
290
+ // evicted before the render that asked for it has run.
291
+ lastSelected: this._host.now(),
292
+ selectedPass: this._pass,
293
+ };
294
+ entry.levels.push(level);
295
+ entry.levels.sort((a, b) => a.edge - b.edge);
296
+ }
297
+ /**
298
+ * Queue a decode of `edge`. An interactive request replaces what is queued,
299
+ * since only the latest demand matters; exact requests accumulate, since a
300
+ * frame may need several rungs of one source.
301
+ */
302
+ _request(entry, edge, replace) {
303
+ if (entry.failed.has(edge) || !entry.intrinsic)
304
+ return;
305
+ if (entry.levels.some((l) => l.edge === edge))
306
+ return;
307
+ if (replace) {
308
+ entry.queue = [edge];
309
+ }
310
+ else if (!entry.queue.includes(edge)) {
311
+ entry.queue.push(edge);
312
+ }
313
+ }
314
+ _pump() {
315
+ for (const entry of this._entries.values()) {
316
+ if (this._inflight >= MAX_CONCURRENT_DECODES)
317
+ return;
318
+ if (entry.job || entry.loading)
319
+ continue;
320
+ const edge = this._nextEdge(entry);
321
+ if (edge === undefined)
322
+ continue;
323
+ const target = edge;
324
+ this._inflight++;
325
+ entry.job = this._host.decode(entry.src, () => target)
326
+ .then((decoded) => {
327
+ this._learn(entry, decoded.intrinsic, decoded.vector);
328
+ if (!this._entries.has(entry.src)) {
329
+ this._host.released(entry.src, { ...decoded, gpu: null, lastSelected: 0, selectedPass: 0 });
330
+ return;
331
+ }
332
+ this._add(entry, decoded);
333
+ this._enforceBudget();
334
+ }, (error) => {
335
+ entry.failed.add(target);
336
+ this._host.onError?.(entry.src, error);
337
+ })
338
+ .finally(() => {
339
+ this._inflight--;
340
+ entry.job = null;
341
+ this._pump();
342
+ });
343
+ }
344
+ }
345
+ /**
346
+ * The next queued rung worth decoding. Under `interactive` never one below the
347
+ * latest demand, and no upgrade once what landed meanwhile already covers it.
348
+ */
349
+ _nextEdge(entry) {
350
+ for (let edge = entry.queue.shift(); edge !== undefined; edge = entry.queue.shift()) {
351
+ if (this._policy === "interactive" && entry.lastDemand !== undefined) {
352
+ const demand = Math.min(entry.lastDemand, entry.cap);
353
+ const top = entry.levels[entry.levels.length - 1];
354
+ if (top && edge > top.edge && top.edge >= demand)
355
+ continue;
356
+ if (edge < demand)
357
+ edge = this._interactiveEdge(entry, demand);
358
+ }
359
+ if (!entry.failed.has(edge) && !entry.levels.some((l) => l.edge === edge))
360
+ return edge;
361
+ }
362
+ return undefined;
363
+ }
364
+ _ageOut(entry, now) {
365
+ if (entry.levels.length <= 1)
366
+ return;
367
+ const top = entry.levels[entry.levels.length - 1];
368
+ const keep = entry.levels.filter((l) => now - l.lastSelected < LEVEL_AGE_OUT_MS || (l === top && retained(l)));
369
+ if (keep.length === entry.levels.length)
370
+ return;
371
+ // Never the last one: fall back to the most recently used.
372
+ if (keep.length === 0)
373
+ keep.push(entry.levels.reduce((a, b) => (b.lastSelected > a.lastSelected ? b : a)));
374
+ for (const level of entry.levels)
375
+ if (!keep.includes(level))
376
+ this._host.released(entry.src, level);
377
+ entry.levels = keep;
378
+ }
379
+ _total() {
380
+ let total = 0;
381
+ for (const entry of this._entries.values()) {
382
+ for (const level of entry.levels)
383
+ total += bytesOf(level);
384
+ total += this._host.sourceBytes(entry.src);
385
+ }
386
+ return total;
387
+ }
388
+ /**
389
+ * Cheapest loss first: levels nothing drew this pass, then levels a preview
390
+ * holds bigger than it needs, then sources nobody has looked at in a while,
391
+ * then their compressed bytes. A source's last level is never evicted — a
392
+ * draw finding nothing would throw rather than degrade.
393
+ */
394
+ _enforceBudget() {
395
+ let total = this._total();
396
+ if (total <= this._budget)
397
+ return;
398
+ const now = this._host.now();
399
+ const unselected = [];
400
+ for (const entry of this._entries.values()) {
401
+ const drawn = entry.levels.some((l) => l.selectedPass >= this._pass);
402
+ entry.levels.forEach((level, index) => {
403
+ if (level.selectedPass >= this._pass)
404
+ return;
405
+ // A source nothing drew keeps its smallest level as the one that
406
+ // survives, not whichever happened to be used last.
407
+ if (!drawn && index === 0)
408
+ return;
409
+ unselected.push({ entry, level });
410
+ });
411
+ }
412
+ unselected.sort((a, b) => a.level.lastSelected - b.level.lastSelected || bytesOf(b.level) - bytesOf(a.level));
413
+ for (const { entry, level } of unselected) {
414
+ if (total <= this._budget)
415
+ return;
416
+ if (entry.levels.length <= 1)
417
+ continue;
418
+ entry.levels = entry.levels.filter((l) => l !== level);
419
+ this._host.released(entry.src, level);
420
+ total -= bytesOf(level);
421
+ }
422
+ if (this._policy === "interactive") {
423
+ for (const entry of this._entries.values()) {
424
+ if (entry.lastDemand === undefined)
425
+ continue;
426
+ const target = levelFor(entry.lastDemand, entry.cap);
427
+ const top = entry.levels[entry.levels.length - 1];
428
+ if (top && top.edge > target)
429
+ this._request(entry, target, true);
430
+ }
431
+ }
432
+ for (const entry of this._entries.values()) {
433
+ if (total <= this._budget)
434
+ break;
435
+ if (now - entry.lastUsed < IDLE_TO_FLOOR_MS || entry.levels.length === 0)
436
+ continue;
437
+ const smallest = entry.levels[0];
438
+ for (const level of entry.levels.slice(1)) {
439
+ this._host.released(entry.src, level);
440
+ total -= bytesOf(level);
441
+ }
442
+ entry.levels = [smallest];
443
+ const floor = floorLevel(entry.cap);
444
+ if (smallest.edge > floor)
445
+ this._request(entry, floor, true);
446
+ }
447
+ for (const entry of this._entries.values()) {
448
+ if (total <= this._budget)
449
+ break;
450
+ if (now - entry.lastUsed < IDLE_DROP_SOURCE_MS)
451
+ continue;
452
+ const bytes = this._host.sourceBytes(entry.src);
453
+ if (bytes === 0)
454
+ continue;
455
+ this._host.dropSource(entry.src);
456
+ total -= bytes;
457
+ }
458
+ }
459
+ }
460
+ function bytesOf(level) {
461
+ return level.width * level.height * 4 * (level.gpu ? 2 : 1);
462
+ }
463
+ function retained(level) {
464
+ return level.width * level.height * 4 <= RETAINED_LEVEL_BYTES;
465
+ }
466
+ /** The resident level closest to `edge`, preferring the larger on a tie. */
467
+ function nearest(levels, edge) {
468
+ let best = levels[0];
469
+ for (const level of levels) {
470
+ const d = Math.abs(Math.log(level.edge / edge));
471
+ const b = Math.abs(Math.log(best.edge / edge));
472
+ if (d < b || (d === b && level.edge > best.edge))
473
+ best = level;
474
+ }
475
+ return best;
476
+ }
477
+ //# sourceMappingURL=image-levels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-levels.js","sourceRoot":"","sources":["../../src/decode/image-levels.ts"],"names":[],"mappings":"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","sourcesContent":["import { floorLevel, levelCap, levelFor, type PixelSize } from \"./levels\";\n\n/**\n * Which level a draw is served, and when levels are decoded and released.\n *\n * Browser-free: decoding, uploading and fetching are the host's, so every\n * decision here runs under a plain test runner with a fake clock.\n */\n\n/** Mirrors core's `DecodePolicy`, which its barrel does not export yet. */\nexport type DecodePolicy = \"exact\" | \"interactive\";\n\nexport const IMAGE_DECODE_BUDGET_BYTES = 512 * 1024 * 1024;\n/** Unselected levels older than this go, whatever the budget — bar a {@link RETAINED_LEVEL_BYTES} largest level. */\nexport const LEVEL_AGE_OUT_MS = 2_000;\n/** A preview drawing a level at twice what it needs for this long decodes the smaller one. */\nexport const DOWNGRADE_AFTER_MS = 5_000;\nexport const IDLE_TO_FLOOR_MS = 30_000;\nexport const IDLE_DROP_SOURCE_MS = 60_000;\nexport const MAX_CONCURRENT_DECODES = 3;\n/** An interactive decode covers this multiple of the demand, so a growing draw re-decodes per octave, not per rung. */\nexport const UPGRADE_HEADROOM = 2;\n/**\n * A largest level no bigger than this is neither aged out nor downgraded until\n * the budget needs the room: decoding it again the next time a draw grows costs\n * more, in main-thread copies and a blurry frame, than holding it.\n */\nexport const RETAINED_LEVEL_BYTES = 16 * 1024 * 1024;\n\nexport interface ImageLevel<G = unknown> {\n /** The ladder rung this level was decoded for. */\n readonly edge: number;\n readonly width: number;\n readonly height: number;\n readonly pixels: Uint8Array;\n /** The host's GPU copy, counted against the budget alongside the pixels. */\n gpu: G | null;\n lastSelected: number;\n /** The pass that last selected it — \"unselected\" is judged by pass, since several can share a clock tick. */\n selectedPass: number;\n}\n\nexport interface DecodedImage {\n pixels: Uint8Array;\n width: number;\n height: number;\n intrinsic: PixelSize;\n vector: boolean;\n}\n\nexport interface ImageLevelHost<G> {\n now(): number;\n /**\n * Decode `src` at the rung `chooseEdge` picks once the source's intrinsic\n * size is known — the host may have to fetch or even decode the source to\n * learn it, which is why the choice is a callback rather than an argument.\n */\n decode(src: string, chooseEdge: (intrinsic: PixelSize, vector: boolean) => number): Promise<DecodedImage & { edge: number }>;\n /** A level left the cache. Free what was built from it. */\n released(src: string, level: ImageLevel<G>): void;\n /** Kept compressed bytes for `src`, so an upgrade needs no refetch; 0 when none. */\n sourceBytes(src: string): number;\n dropSource(src: string): void;\n /** Long edge of the surface in device px, for sizing a first interactive load. */\n surfaceEdge(): number;\n onError?(src: string, error: unknown): void;\n}\n\ninterface Entry<G> {\n readonly src: string;\n intrinsic: PixelSize | null;\n vector: boolean;\n cap: number;\n /** Ascending by edge. */\n levels: ImageLevel<G>[];\n lastDemand: number | undefined;\n lastUsed: number;\n passNeed: number;\n passDraws: number;\n passAllOver: boolean;\n overSince: number | null;\n /** Exact rungs this pass drew without. */\n wanted: Set<number>;\n queue: number[];\n job: Promise<void> | null;\n failed: Set<number>;\n loading: Promise<void> | null;\n /** The pass that asked for this source with nothing resident. */\n missedPass: number;\n /** The last pass to draw it was served below demand, so an upgrade landing changes the picture. */\n short: boolean;\n}\n\nexport class ImageLevelCache<G = unknown> {\n private _policy: DecodePolicy = \"interactive\";\n private readonly _entries = new Map<string, Entry<G>>();\n private _pass = 0;\n private _inflight = 0;\n\n constructor(\n private readonly _host: ImageLevelHost<G>,\n private readonly _budget = IMAGE_DECODE_BUDGET_BYTES,\n ) { }\n\n get policy(): DecodePolicy {\n return this._policy;\n }\n\n setPolicy(policy: DecodePolicy): void {\n this._policy = policy;\n }\n\n has(src: string): boolean {\n return (this._entries.get(src)?.levels.length ?? 0) > 0;\n }\n\n intrinsicSize(src: string): PixelSize | null {\n return this._entries.get(src)?.intrinsic ?? null;\n }\n\n /** Make sure `src` has a level to draw with. First loads pick their rung by policy. */\n load(src: string): Promise<void> {\n const entry = this._entry(src);\n if (entry.levels.length > 0) return Promise.resolve();\n entry.loading ??= this._host.decode(src, (intrinsic, vector) => {\n this._learn(entry, intrinsic, vector);\n if (this._policy === \"exact\") return floorLevel(entry.cap);\n if (entry.lastDemand !== undefined) return this._interactiveEdge(entry, entry.lastDemand);\n return levelFor(Math.min(Math.max(intrinsic.width, intrinsic.height), this._host.surfaceEdge()), entry.cap);\n }).then((decoded) => {\n this._learn(entry, decoded.intrinsic, decoded.vector);\n this._add(entry, decoded);\n this._enforceBudget();\n }).finally(() => {\n entry.loading = null;\n this._pump();\n });\n return entry.loading;\n }\n\n beginPass(): void {\n this._pass++;\n }\n\n /**\n * The level one draw paints with. `footprint` is the long edge in device px\n * the draw needs; absent means the source's own resolution.\n */\n select(src: string, footprint?: number): ImageLevel<G> | null {\n const entry = this._entry(src);\n const now = this._host.now();\n const demand = footprint !== undefined && Number.isFinite(footprint) && footprint > 0 ? footprint : Infinity;\n if (entry.levels.length === 0) {\n // Remembered so the load this miss triggers starts at the right rung.\n if (Number.isFinite(demand)) entry.lastDemand = Math.max(entry.lastDemand ?? 0, demand);\n entry.missedPass = this._pass;\n return null;\n }\n\n const need = Math.min(demand, entry.cap);\n entry.passNeed = Math.max(entry.passNeed, need);\n entry.passDraws++;\n entry.lastUsed = now;\n\n let level: ImageLevel<G>;\n if (this._policy === \"exact\") {\n const target = levelFor(need, entry.cap);\n const exact = entry.levels.find((l) => l.edge === target);\n if (!exact && !entry.failed.has(target)) entry.wanted.add(target);\n level = exact ?? nearest(entry.levels, target);\n } else {\n level = entry.levels.find((l) => l.edge >= need) ?? entry.levels[entry.levels.length - 1];\n if (level.edge < 2 * need) entry.passAllOver = false;\n }\n level.lastSelected = now;\n level.selectedPass = this._pass;\n return level;\n }\n\n /** Decide this pass's level changes, now that every draw has reported. */\n endPass(): void {\n const now = this._host.now();\n for (const entry of this._entries.values()) {\n const top = entry.levels[entry.levels.length - 1];\n if (entry.passDraws > 0 && top) {\n entry.lastDemand = entry.passNeed;\n if (this._policy === \"exact\") {\n for (const edge of entry.wanted) this._request(entry, edge, false);\n entry.wanted.clear();\n } else {\n entry.short = top.edge < entry.passNeed;\n if (entry.short) {\n this._request(entry, this._interactiveEdge(entry, entry.passNeed), true);\n } else {\n // Demand fell back under what is resident: a queued upgrade is no longer worth starting.\n entry.queue = entry.queue.filter((edge) => edge <= top.edge);\n }\n if (entry.passAllOver && !retained(top)) {\n entry.overSince ??= now;\n if (now - entry.overSince >= DOWNGRADE_AFTER_MS) {\n this._request(entry, this._interactiveEdge(entry, entry.passNeed), true);\n }\n } else {\n entry.overSince = null;\n }\n }\n } else {\n entry.short = false;\n }\n entry.passNeed = 0;\n entry.passDraws = 0;\n entry.passAllOver = true;\n this._ageOut(entry, now);\n }\n this._enforceBudget();\n this._pump();\n }\n\n /**\n * What a settling caller must wait for before the frame is right: under\n * `exact`, every decode queued or running; under `interactive`, only a source\n * the last pass asked for with nothing resident. An upgrade is {@link hasUpgrades}.\n */\n hasPending(): boolean {\n for (const entry of this._entries.values()) {\n if (this._blocks(entry)) return true;\n }\n return false;\n }\n\n /** Await what {@link hasPending} reports, and report whether there was any. */\n async warm(): Promise<boolean> {\n if (this._policy === \"interactive\") {\n let did = false;\n for (let guard = 0; guard < 64; guard++) {\n const loading = [...this._entries.values()]\n .filter((e) => this._blocks(e))\n .map((e) => e.loading!);\n if (loading.length === 0) break;\n did = true;\n await Promise.all(loading.map((job) => job.catch(() => undefined)));\n }\n return did;\n }\n\n let did = false;\n for (const entry of this._entries.values()) {\n for (const edge of entry.wanted) this._request(entry, edge, false);\n entry.wanted.clear();\n }\n this._pump();\n for (let guard = 0; guard < 64; guard++) {\n const running = [...this._entries.values()]\n .map((e) => e.job ?? e.loading)\n .filter((job): job is Promise<void> => job !== null);\n if (running.length === 0) break;\n did = true;\n await Promise.all(running.map((job) => job.catch(() => undefined)));\n this._pump();\n }\n return did;\n }\n\n /** Whether, under `interactive`, a source the last pass drew below demand has a better level on its way. */\n hasUpgrades(): boolean {\n if (this._policy !== \"interactive\") return false;\n for (const entry of this._entries.values()) {\n if (this._upgrading(entry)) return true;\n }\n return false;\n }\n\n /** Await the upgrades {@link hasUpgrades} reports; whether one landed. */\n async warmUpgrades(): Promise<boolean> {\n let did = false;\n for (let guard = 0; guard < 64 && this._policy === \"interactive\"; guard++) {\n this._pump();\n const waiting = [...this._entries.values()].filter((e) => this._upgrading(e));\n if (waiting.length === 0) break;\n const own = waiting.flatMap((e) => (e.job ? [e.job] : []));\n // Queued behind other sources' decodes: those free the slot it needs.\n const running = own.length > 0 ? own : [...this._entries.values()].flatMap((e) => (e.job ? [e.job] : []));\n if (running.length === 0) break;\n await Promise.all(running.map((job) => job.catch(() => undefined)));\n if (own.length > 0) did = true;\n }\n return did;\n }\n\n stats(): { sources: number; levels: number; levelBytes: number; sourceBytes: number; inflight: number } {\n let levels = 0;\n let levelBytes = 0;\n let sourceBytes = 0;\n for (const entry of this._entries.values()) {\n levels += entry.levels.length;\n for (const level of entry.levels) levelBytes += bytesOf(level);\n sourceBytes += this._host.sourceBytes(entry.src);\n }\n return { sources: this._entries.size, levels, levelBytes, sourceBytes, inflight: this._inflight };\n }\n\n /** Every level held for `src`, ascending — for tests and stats. */\n levelsOf(src: string): readonly ImageLevel<G>[] {\n return this._entries.get(src)?.levels ?? [];\n }\n\n dispose(): void {\n for (const entry of this._entries.values()) {\n for (const level of entry.levels) this._host.released(entry.src, level);\n entry.levels = [];\n entry.queue = [];\n }\n this._entries.clear();\n }\n\n private _entry(src: string): Entry<G> {\n let entry = this._entries.get(src);\n if (!entry) {\n entry = {\n src, intrinsic: null, vector: false, cap: 1, levels: [],\n lastDemand: undefined, lastUsed: this._host.now(),\n passNeed: 0, passDraws: 0, passAllOver: true, overSince: null,\n wanted: new Set(), queue: [], job: null, failed: new Set(), loading: null,\n missedPass: -1, short: false,\n };\n this._entries.set(src, entry);\n }\n return entry;\n }\n\n private _blocks(entry: Entry<G>): boolean {\n if (this._policy === \"exact\") {\n return entry.job !== null || entry.queue.length > 0 || entry.wanted.size > 0 || entry.loading !== null;\n }\n return entry.levels.length === 0 && entry.missedPass === this._pass && entry.loading !== null;\n }\n\n private _upgrading(entry: Entry<G>): boolean {\n return entry.short && entry.levels.length > 0 && (entry.job !== null || entry.queue.length > 0);\n }\n\n /** The rung an interactive decode for `demand` asks for: with headroom, capped at the source. */\n private _interactiveEdge(entry: Entry<G>, demand: number): number {\n return levelFor(Math.min(demand * UPGRADE_HEADROOM, entry.cap), entry.cap);\n }\n\n private _learn(entry: Entry<G>, intrinsic: PixelSize, vector: boolean): void {\n entry.intrinsic = { width: intrinsic.width, height: intrinsic.height };\n entry.vector = vector;\n entry.cap = levelCap(intrinsic, vector);\n }\n\n private _add(entry: Entry<G>, decoded: { pixels: Uint8Array; width: number; height: number; edge: number }): void {\n const existing = entry.levels.findIndex((l) => l.edge === decoded.edge);\n if (existing >= 0) {\n this._host.released(entry.src, entry.levels[existing]);\n entry.levels.splice(existing, 1);\n }\n const level: ImageLevel<G> = {\n edge: decoded.edge,\n width: decoded.width,\n height: decoded.height,\n pixels: decoded.pixels,\n gpu: null,\n // Counts as selected now, so a level a settle pass decoded is not\n // evicted before the render that asked for it has run.\n lastSelected: this._host.now(),\n selectedPass: this._pass,\n };\n entry.levels.push(level);\n entry.levels.sort((a, b) => a.edge - b.edge);\n }\n\n /**\n * Queue a decode of `edge`. An interactive request replaces what is queued,\n * since only the latest demand matters; exact requests accumulate, since a\n * frame may need several rungs of one source.\n */\n private _request(entry: Entry<G>, edge: number, replace: boolean): void {\n if (entry.failed.has(edge) || !entry.intrinsic) return;\n if (entry.levels.some((l) => l.edge === edge)) return;\n if (replace) {\n entry.queue = [edge];\n } else if (!entry.queue.includes(edge)) {\n entry.queue.push(edge);\n }\n }\n\n private _pump(): void {\n for (const entry of this._entries.values()) {\n if (this._inflight >= MAX_CONCURRENT_DECODES) return;\n if (entry.job || entry.loading) continue;\n const edge = this._nextEdge(entry);\n if (edge === undefined) continue;\n const target = edge;\n this._inflight++;\n entry.job = this._host.decode(entry.src, () => target)\n .then((decoded) => {\n this._learn(entry, decoded.intrinsic, decoded.vector);\n if (!this._entries.has(entry.src)) {\n this._host.released(entry.src, { ...decoded, gpu: null, lastSelected: 0, selectedPass: 0 });\n return;\n }\n this._add(entry, decoded);\n this._enforceBudget();\n }, (error) => {\n entry.failed.add(target);\n this._host.onError?.(entry.src, error);\n })\n .finally(() => {\n this._inflight--;\n entry.job = null;\n this._pump();\n });\n }\n }\n\n /**\n * The next queued rung worth decoding. Under `interactive` never one below the\n * latest demand, and no upgrade once what landed meanwhile already covers it.\n */\n private _nextEdge(entry: Entry<G>): number | undefined {\n for (let edge = entry.queue.shift(); edge !== undefined; edge = entry.queue.shift()) {\n if (this._policy === \"interactive\" && entry.lastDemand !== undefined) {\n const demand = Math.min(entry.lastDemand, entry.cap);\n const top = entry.levels[entry.levels.length - 1];\n if (top && edge > top.edge && top.edge >= demand) continue;\n if (edge < demand) edge = this._interactiveEdge(entry, demand);\n }\n if (!entry.failed.has(edge) && !entry.levels.some((l) => l.edge === edge)) return edge;\n }\n return undefined;\n }\n\n private _ageOut(entry: Entry<G>, now: number): void {\n if (entry.levels.length <= 1) return;\n const top = entry.levels[entry.levels.length - 1];\n const keep = entry.levels.filter((l) => now - l.lastSelected < LEVEL_AGE_OUT_MS || (l === top && retained(l)));\n if (keep.length === entry.levels.length) return;\n // Never the last one: fall back to the most recently used.\n if (keep.length === 0) keep.push(entry.levels.reduce((a, b) => (b.lastSelected > a.lastSelected ? b : a)));\n for (const level of entry.levels) if (!keep.includes(level)) this._host.released(entry.src, level);\n entry.levels = keep;\n }\n\n private _total(): number {\n let total = 0;\n for (const entry of this._entries.values()) {\n for (const level of entry.levels) total += bytesOf(level);\n total += this._host.sourceBytes(entry.src);\n }\n return total;\n }\n\n /**\n * Cheapest loss first: levels nothing drew this pass, then levels a preview\n * holds bigger than it needs, then sources nobody has looked at in a while,\n * then their compressed bytes. A source's last level is never evicted — a\n * draw finding nothing would throw rather than degrade.\n */\n private _enforceBudget(): void {\n let total = this._total();\n if (total <= this._budget) return;\n const now = this._host.now();\n\n const unselected: { entry: Entry<G>; level: ImageLevel<G> }[] = [];\n for (const entry of this._entries.values()) {\n const drawn = entry.levels.some((l) => l.selectedPass >= this._pass);\n entry.levels.forEach((level, index) => {\n if (level.selectedPass >= this._pass) return;\n // A source nothing drew keeps its smallest level as the one that\n // survives, not whichever happened to be used last.\n if (!drawn && index === 0) return;\n unselected.push({ entry, level });\n });\n }\n unselected.sort((a, b) => a.level.lastSelected - b.level.lastSelected || bytesOf(b.level) - bytesOf(a.level));\n for (const { entry, level } of unselected) {\n if (total <= this._budget) return;\n if (entry.levels.length <= 1) continue;\n entry.levels = entry.levels.filter((l) => l !== level);\n this._host.released(entry.src, level);\n total -= bytesOf(level);\n }\n\n if (this._policy === \"interactive\") {\n for (const entry of this._entries.values()) {\n if (entry.lastDemand === undefined) continue;\n const target = levelFor(entry.lastDemand, entry.cap);\n const top = entry.levels[entry.levels.length - 1];\n if (top && top.edge > target) this._request(entry, target, true);\n }\n }\n\n for (const entry of this._entries.values()) {\n if (total <= this._budget) break;\n if (now - entry.lastUsed < IDLE_TO_FLOOR_MS || entry.levels.length === 0) continue;\n const smallest = entry.levels[0];\n for (const level of entry.levels.slice(1)) {\n this._host.released(entry.src, level);\n total -= bytesOf(level);\n }\n entry.levels = [smallest];\n const floor = floorLevel(entry.cap);\n if (smallest.edge > floor) this._request(entry, floor, true);\n }\n\n for (const entry of this._entries.values()) {\n if (total <= this._budget) break;\n if (now - entry.lastUsed < IDLE_DROP_SOURCE_MS) continue;\n const bytes = this._host.sourceBytes(entry.src);\n if (bytes === 0) continue;\n this._host.dropSource(entry.src);\n total -= bytes;\n }\n }\n}\n\nfunction bytesOf(level: ImageLevel<unknown>): number {\n return level.width * level.height * 4 * (level.gpu ? 2 : 1);\n}\n\nfunction retained(level: ImageLevel<unknown>): boolean {\n return level.width * level.height * 4 <= RETAINED_LEVEL_BYTES;\n}\n\n/** The resident level closest to `edge`, preferring the larger on a tie. */\nfunction nearest<G>(levels: ImageLevel<G>[], edge: number): ImageLevel<G> {\n let best = levels[0];\n for (const level of levels) {\n const d = Math.abs(Math.log(level.edge / edge));\n const b = Math.abs(Math.log(best.edge / edge));\n if (d < b || (d === b && level.edge > best.edge)) best = level;\n }\n return best;\n}\n"]}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The resolutions a source may be decoded at.
3
+ *
4
+ * Levels sit half an octave apart, so a decode is never more than √2 larger than
5
+ * what a draw asked for, and a slow zoom re-decodes a dozen times across the
6
+ * whole range rather than once per frame.
7
+ */
8
+ /**
9
+ * Longest edge any level is decoded at. The conventional maximum GPU texture
10
+ * edge, so pixels past it could not be sampled in one draw anyway; a 4096²
11
+ * RGBA level is 67 MB resident.
12
+ */
13
+ export declare const MAX_IMAGE_DECODE_EDGE = 4096;
14
+ export declare const MIN_LEVEL_EDGE = 64;
15
+ /** `64·2^(k/2)` up to {@link MAX_IMAGE_DECODE_EDGE}. */
16
+ export declare const LEVEL_LADDER: readonly number[];
17
+ export interface PixelSize {
18
+ width: number;
19
+ height: number;
20
+ }
21
+ /**
22
+ * The largest level worth holding: the source's own long edge for a raster
23
+ * (there is no more detail to reveal), the ceiling for a vector.
24
+ */
25
+ export declare function levelCap(intrinsic: PixelSize, vector: boolean): number;
26
+ /**
27
+ * The level for a draw that needs `demand` device px along the source's long
28
+ * edge: the smallest rung at or above it, snapped to `cap` once a rung is
29
+ * within half an octave of it — a level at 0.8× the source costs almost what
30
+ * the source does and looks worse.
31
+ */
32
+ export declare function levelFor(demand: number, cap: number): number;
33
+ /** The smallest level a source can be held at. */
34
+ export declare function floorLevel(cap: number): number;
35
+ /** Pixel dimensions of `edge`, keeping the intrinsic aspect. */
36
+ export declare function levelSize(edge: number, intrinsic: PixelSize): PixelSize;
37
+ //# sourceMappingURL=levels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"levels.d.ts","sourceRoot":"","sources":["../../src/decode/levels.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,wDAAwD;AACxD,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAQtC,CAAC;AAEL,MAAM,WAAW,SAAS;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAItE;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAS5D;AAED,kDAAkD;AAClD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,gEAAgE;AAChE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,SAAS,CAOvE"}