spine-html 0.5.0 → 0.5.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.
@@ -30,14 +30,37 @@ export interface RegionImage {
30
30
  * rendering is pure DOM (one <img> per slot, one CSS matrix write per frame).
31
31
  *
32
32
  * A region that covers its whole page unrotated skips the cut and reuses the
33
- * page image URL — see the pass-through below.
33
+ * page image URL — see the pass-through in cutRegion.
34
+ *
35
+ * **The cuts run concurrently.** PNG encoding is asynchronous and the browser
36
+ * does it off the main thread, so the encodes are started together and awaited
37
+ * together instead of one `toBlob` per iteration: awaited in series a load
38
+ * costs the *sum* of every encode, which a throttled document turns into one
39
+ * full throttle period per region (measured: a single 8×8 `toBlob` took 7.5 s
40
+ * to call back in a hidden desktop Chromium pane). How many run at once is
41
+ * bounded by CUT_BACKING_BUDGET_PX above, not by an option.
42
+ *
43
+ * Concurrency changes nothing a caller can observe:
44
+ *
45
+ * - **Order.** The returned map is keyed in `atlas.regions` order whatever
46
+ * order the blobs arrive in — the map is built after the cuts settle, from
47
+ * the region list, not from the arrivals.
48
+ * - **Duplicate names.** The last region of a given name in atlas order wins,
49
+ * and the shadowed URL is revoked (and dropped from the ledger) rather than
50
+ * stranded.
51
+ * - **Failure.** Every URL this call minted is revoked before the error
52
+ * propagates, *including cuts that were still in flight when the first
53
+ * failure happened* — their blobs arrive later, and a load that walked away
54
+ * from them would mint owned URLs nobody could ever revoke. So a failure
55
+ * stops new cuts from starting but still waits for the started ones, and the
56
+ * error it raises is the one from the earliest region in atlas order (what
57
+ * the serial loop used to throw). Nothing here rejects unobserved.
34
58
  *
35
59
  * The blob URLs stay alive until revokeRegions() frees them — a document-wide
36
60
  * allocation the GC cannot reclaim on its own. Callers that load and unload
37
61
  * skeletons repeatedly (cutscenes, level transitions) must pair every
38
62
  * unpackRegions() with a revokeRegions(); a caller that loads once for the
39
- * page lifetime can ignore it. If this function throws part-way through,
40
- * everything it already minted is revoked before the error propagates.
63
+ * page lifetime can ignore it.
41
64
  */
42
65
  export declare function unpackRegions(atlas: TextureAtlas, pageImages: Map<string, HTMLImageElement>): Promise<Map<string, RegionImage>>;
43
66
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"DomTexture.d.ts","sourceRoot":"","sources":["../src/DomTexture.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,YAAY,EAEZ,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,8BAA8B,CAAC;AAEtC;;;;GAIG;AACH,qBAAa,UAAW,SAAQ,OAAO;IACrC,UAAU,CAAC,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,aAAa,GAAG,IAAI;IACtE,QAAQ,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI;IACxD,OAAO,IAAI,IAAI;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;CAChB;AAkBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,YAAY,EACnB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,GACxC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAqEnC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,IAAI,CAEpE"}
1
+ {"version":3,"file":"DomTexture.d.ts","sourceRoot":"","sources":["../src/DomTexture.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,YAAY,EAEZ,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,8BAA8B,CAAC;AAEtC;;;;GAIG;AACH,qBAAa,UAAW,SAAQ,OAAO;IACrC,UAAU,CAAC,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,aAAa,GAAG,IAAI;IACtE,QAAQ,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI;IACxD,OAAO,IAAI,IAAI;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;CAChB;AAsGD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,YAAY,EACnB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,GACxC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CA0EnC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,IAAI,CAEpE"}
@@ -24,6 +24,81 @@ function revokeOwned(url) {
24
24
  return;
25
25
  URL.revokeObjectURL(url);
26
26
  }
27
+ /**
28
+ * Backing pixels the cuts still waiting for their PNG encode may hold at once.
29
+ *
30
+ * A started cut keeps its canvas alive until `toBlob` calls back, so starting
31
+ * every region at once peaks at Σ(region area) of canvas backing — on top of
32
+ * the decoded pages, which stay resident throughout. Measured by counting the
33
+ * simultaneously-alive cut canvases and their backing at `toBlob` (chromium):
34
+ * the demo's 40-region 1024×256 atlas peaks at all 40 cuts and 194,742 px
35
+ * (0.19 Mpx ≈ 0.74 MiB), which is nothing; a synthetic 256-region atlas on
36
+ * four 2048×2048 pages peaks at 16.78 Mpx ≈ 64 MiB, which doubles that
37
+ * atlas's resident cost for the length of the load.
38
+ *
39
+ * So the cuts are started against a budget rather than all at once. 4 Mpx is
40
+ * one 2048×2048 page's worth (≈ 16 MiB at 4 bytes/px): small atlases still run
41
+ * every region concurrently (spineboy: all 40, budget never engaged), and the
42
+ * synthetic one runs 64 cuts at a time — 4 waves instead of 256 serial
43
+ * encodes, for a quarter of the peak. A single region bigger than the budget
44
+ * is started anyway when nothing else is in flight, so the budget can never
45
+ * starve a cut.
46
+ */
47
+ const CUT_BACKING_BUDGET_PX = 4 * 1024 * 1024;
48
+ /**
49
+ * One region's bitmap: the whole-page pass-through, or a cut canvas encoded to
50
+ * a blob URL this module then owns.
51
+ *
52
+ * Everything before the `toBlob` await runs synchronously, so calling this is
53
+ * what "starts" a cut: the canvas is painted and the encode is handed to the
54
+ * browser before control comes back.
55
+ */
56
+ async function cutRegion(region, pageImages) {
57
+ const image = pageImages.get(region.page.name);
58
+ if (!image)
59
+ throw new Error(`Missing page image: ${region.page.name}`);
60
+ const w = region.width;
61
+ const h = region.height;
62
+ // Whole-page pass-through: an unrotated region covering its entire page
63
+ // would be cut into a pixel-for-pixel copy of the page image, so reuse
64
+ // the page URL instead — no canvas, no PNG re-encode, no second decoded
65
+ // copy in memory, and nothing to revoke afterwards. Atlases written as
66
+ // one part per page (the loose-part-PNG workflow, where every part is
67
+ // declared its own page) hit this for every single region.
68
+ //
69
+ // The test is against the image, not the atlas `size:` line: a page
70
+ // declared at the wrong size still goes through the cut, since the cut
71
+ // is what the region's coordinates actually describe.
72
+ if (region.degrees === 0 &&
73
+ region.x === 0 &&
74
+ region.y === 0 &&
75
+ w === image.naturalWidth &&
76
+ h === image.naturalHeight) {
77
+ return { url: image.src, width: w, height: h };
78
+ }
79
+ const canvas = document.createElement('canvas');
80
+ canvas.width = w;
81
+ canvas.height = h;
82
+ const ctx = canvas.getContext('2d');
83
+ if (!ctx)
84
+ throw new Error('2d context unavailable');
85
+ if (region.degrees === 90) {
86
+ // The region is packed rotated: it occupies an h×w rect in the page.
87
+ // Rotate it back so the bitmap is in artwork orientation.
88
+ ctx.translate(0, h);
89
+ ctx.rotate(-Math.PI / 2);
90
+ ctx.drawImage(image, region.x, region.y, h, w, 0, 0, h, w);
91
+ }
92
+ else {
93
+ ctx.drawImage(image, region.x, region.y, w, h, 0, 0, w, h);
94
+ }
95
+ const blob = await new Promise((resolve, reject) => {
96
+ canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('toBlob failed'))), 'image/png');
97
+ });
98
+ const url = URL.createObjectURL(blob);
99
+ ownedUrls.add(url);
100
+ return { url, width: w, height: h };
101
+ }
27
102
  /**
28
103
  * Cuts every atlas region out of the page image into its own bitmap once at
29
104
  * load time, restoring 90° packing rotation, so the per-frame path never
@@ -33,78 +108,106 @@ function revokeOwned(url) {
33
108
  * rendering is pure DOM (one <img> per slot, one CSS matrix write per frame).
34
109
  *
35
110
  * A region that covers its whole page unrotated skips the cut and reuses the
36
- * page image URL — see the pass-through below.
111
+ * page image URL — see the pass-through in cutRegion.
112
+ *
113
+ * **The cuts run concurrently.** PNG encoding is asynchronous and the browser
114
+ * does it off the main thread, so the encodes are started together and awaited
115
+ * together instead of one `toBlob` per iteration: awaited in series a load
116
+ * costs the *sum* of every encode, which a throttled document turns into one
117
+ * full throttle period per region (measured: a single 8×8 `toBlob` took 7.5 s
118
+ * to call back in a hidden desktop Chromium pane). How many run at once is
119
+ * bounded by CUT_BACKING_BUDGET_PX above, not by an option.
120
+ *
121
+ * Concurrency changes nothing a caller can observe:
122
+ *
123
+ * - **Order.** The returned map is keyed in `atlas.regions` order whatever
124
+ * order the blobs arrive in — the map is built after the cuts settle, from
125
+ * the region list, not from the arrivals.
126
+ * - **Duplicate names.** The last region of a given name in atlas order wins,
127
+ * and the shadowed URL is revoked (and dropped from the ledger) rather than
128
+ * stranded.
129
+ * - **Failure.** Every URL this call minted is revoked before the error
130
+ * propagates, *including cuts that were still in flight when the first
131
+ * failure happened* — their blobs arrive later, and a load that walked away
132
+ * from them would mint owned URLs nobody could ever revoke. So a failure
133
+ * stops new cuts from starting but still waits for the started ones, and the
134
+ * error it raises is the one from the earliest region in atlas order (what
135
+ * the serial loop used to throw). Nothing here rejects unobserved.
37
136
  *
38
137
  * The blob URLs stay alive until revokeRegions() frees them — a document-wide
39
138
  * allocation the GC cannot reclaim on its own. Callers that load and unload
40
139
  * skeletons repeatedly (cutscenes, level transitions) must pair every
41
140
  * unpackRegions() with a revokeRegions(); a caller that loads once for the
42
- * page lifetime can ignore it. If this function throws part-way through,
43
- * everything it already minted is revoked before the error propagates.
141
+ * page lifetime can ignore it.
44
142
  */
45
143
  export async function unpackRegions(atlas, pageImages) {
46
- const result = new Map();
47
- // Duplicate region names would otherwise strand the shadowed URL in the
48
- // ledger with nothing left pointing at it.
49
- const put = (name, entry) => {
50
- const shadowed = result.get(name);
51
- if (shadowed)
52
- revokeOwned(shadowed.url);
53
- result.set(name, entry);
54
- };
55
- try {
56
- for (const region of atlas.regions) {
57
- const atlasRegion = region;
58
- const image = pageImages.get(atlasRegion.page.name);
59
- if (!image)
60
- throw new Error(`Missing page image: ${atlasRegion.page.name}`);
61
- const w = region.width;
62
- const h = region.height;
63
- // Whole-page pass-through: an unrotated region covering its entire page
64
- // would be cut into a pixel-for-pixel copy of the page image, so reuse
65
- // the page URL instead — no canvas, no PNG re-encode, no second decoded
66
- // copy in memory, and nothing to revoke afterwards. Atlases written as
67
- // one part per page (the loose-part-PNG workflow, where every part is
68
- // declared its own page) hit this for every single region.
69
- //
70
- // The test is against the image, not the atlas `size:` line: a page
71
- // declared at the wrong size still goes through the cut, since the cut
72
- // is what the region's coordinates actually describe.
73
- if (region.degrees === 0 &&
74
- atlasRegion.x === 0 &&
75
- atlasRegion.y === 0 &&
76
- w === image.naturalWidth &&
77
- h === image.naturalHeight) {
78
- put(atlasRegion.name, { url: image.src, width: w, height: h });
79
- continue;
144
+ const regions = atlas.regions;
145
+ const cuts = new Array(regions.length);
146
+ // The earliest-in-atlas-order failure is the one that propagates, so that a
147
+ // broken atlas reports the same region whichever encode happens to land
148
+ // first.
149
+ let failedIndex = -1;
150
+ let failure;
151
+ let inFlight = [];
152
+ let inFlightPixels = 0;
153
+ for (let index = 0; index < regions.length && failedIndex === -1; index++) {
154
+ const region = regions[index];
155
+ if (!region)
156
+ continue;
157
+ // A pass-through allocates no canvas, but it also resolves without waiting
158
+ // for anything, so charging it the region's area costs at most one drain.
159
+ const pixels = region.width * region.height;
160
+ while (inFlight.length > 0 && inFlightPixels + pixels > CUT_BACKING_BUDGET_PX) {
161
+ await Promise.race(inFlight.map((job) => job.promise));
162
+ inFlight = inFlight.filter((job) => !job.done);
163
+ if (failedIndex !== -1)
164
+ break;
165
+ }
166
+ if (failedIndex !== -1)
167
+ break;
168
+ inFlightPixels += pixels;
169
+ const job = { done: false, promise: Promise.resolve() };
170
+ // Jobs settle, they never reject: the failure is recorded here so that
171
+ // nothing is left for an unhandled-rejection handler to find.
172
+ job.promise = (async () => {
173
+ try {
174
+ cuts[index] = await cutRegion(region, pageImages);
80
175
  }
81
- const canvas = document.createElement('canvas');
82
- canvas.width = w;
83
- canvas.height = h;
84
- const ctx = canvas.getContext('2d');
85
- if (!ctx)
86
- throw new Error('2d context unavailable');
87
- if (region.degrees === 90) {
88
- // The region is packed rotated: it occupies an h×w rect in the page.
89
- // Rotate it back so the bitmap is in artwork orientation.
90
- ctx.translate(0, h);
91
- ctx.rotate(-Math.PI / 2);
92
- ctx.drawImage(image, atlasRegion.x, atlasRegion.y, h, w, 0, 0, h, w);
176
+ catch (error) {
177
+ if (failedIndex === -1 || index < failedIndex) {
178
+ failedIndex = index;
179
+ failure = error;
180
+ }
93
181
  }
94
- else {
95
- ctx.drawImage(image, atlasRegion.x, atlasRegion.y, w, h, 0, 0, w, h);
182
+ finally {
183
+ inFlightPixels -= pixels;
184
+ job.done = true;
96
185
  }
97
- const blob = await new Promise((resolve, reject) => {
98
- canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('toBlob failed'))), 'image/png');
99
- });
100
- const url = URL.createObjectURL(blob);
101
- ownedUrls.add(url);
102
- put(atlasRegion.name, { url, width: w, height: h });
103
- }
186
+ })();
187
+ inFlight.push(job);
188
+ }
189
+ // Every cut this call started is settled after this, so the cleanup below
190
+ // reaches the blobs that arrived after the first failure too.
191
+ await Promise.all(inFlight.map((job) => job.promise));
192
+ if (failedIndex !== -1) {
193
+ for (const cut of cuts)
194
+ if (cut)
195
+ revokeOwned(cut.url);
196
+ throw failure;
104
197
  }
105
- catch (error) {
106
- revokeRegions(result);
107
- throw error;
198
+ const result = new Map();
199
+ for (let index = 0; index < regions.length; index++) {
200
+ const region = regions[index];
201
+ const cut = cuts[index];
202
+ if (!region || !cut)
203
+ continue;
204
+ // Duplicate region names would otherwise strand the shadowed URL in the
205
+ // ledger with nothing left pointing at it. Map.set keeps the key where it
206
+ // first appeared, so atlas order survives the overwrite.
207
+ const shadowed = result.get(region.name);
208
+ if (shadowed)
209
+ revokeOwned(shadowed.url);
210
+ result.set(region.name, cut);
108
211
  }
109
212
  return result;
110
213
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DomTexture.js","sourceRoot":"","sources":["../src/DomTexture.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DomTexture.js","sourceRoot":"","sources":["../src/DomTexture.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,GAKR,MAAM,8BAA8B,CAAC;AAEtC;;;;GAIG;AACH,MAAM,OAAO,UAAW,SAAQ,OAAO;IACrC,UAAU,CAAC,UAAyB,EAAE,UAAyB,IAAS,CAAC;IACzE,QAAQ,CAAC,MAAmB,EAAE,MAAmB,IAAS,CAAC;IAC3D,OAAO,KAAU,CAAC;CACnB;AAeD;;;;;;;GAOG;AACH,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;AAEpC,yEAAyE;AACzE,SAAS,WAAW,CAAC,GAAW;IAC9B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC;QAAE,OAAO;IACnC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,qBAAqB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAE9C;;;;;;;GAOG;AACH,KAAK,UAAU,SAAS,CACtB,MAA0B,EAC1B,UAAyC;IAEzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAEvE,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC;IACvB,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;IAExB,wEAAwE;IACxE,uEAAuE;IACvE,wEAAwE;IACxE,uEAAuE;IACvE,sEAAsE;IACtE,2DAA2D;IAC3D,EAAE;IACF,oEAAoE;IACpE,uEAAuE;IACvE,sDAAsD;IACtD,IACE,MAAM,CAAC,OAAO,KAAK,CAAC;QACpB,MAAM,CAAC,CAAC,KAAK,CAAC;QACd,MAAM,CAAC,CAAC,KAAK,CAAC;QACd,CAAC,KAAK,KAAK,CAAC,YAAY;QACxB,CAAC,KAAK,KAAK,CAAC,aAAa,EACzB,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACjD,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC;IACjB,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAClB,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAEpD,IAAI,MAAM,CAAC,OAAO,KAAK,EAAE,EAAE,CAAC;QAC1B,qEAAqE;QACrE,0DAA0D;QAC1D,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpB,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACzB,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7D,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACvD,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IAC3F,CAAC,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACtC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;AACtC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAAmB,EACnB,UAAyC;IAEzC,MAAM,OAAO,GAAyB,KAAK,CAAC,OAAO,CAAC;IACpD,MAAM,IAAI,GAAG,IAAI,KAAK,CAA0B,OAAO,CAAC,MAAM,CAAC,CAAC;IAEhE,4EAA4E;IAC5E,wEAAwE;IACxE,SAAS;IACT,IAAI,WAAW,GAAG,CAAC,CAAC,CAAC;IACrB,IAAI,OAAgB,CAAC;IAOrB,IAAI,QAAQ,GAAU,EAAE,CAAC;IACzB,IAAI,cAAc,GAAG,CAAC,CAAC;IAEvB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,IAAI,WAAW,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;QAC1E,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,2EAA2E;QAC3E,0EAA0E;QAC1E,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC;QAE5C,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,cAAc,GAAG,MAAM,GAAG,qBAAqB,EAAE,CAAC;YAC9E,MAAM,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;YACvD,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC/C,IAAI,WAAW,KAAK,CAAC,CAAC;gBAAE,MAAM;QAChC,CAAC;QACD,IAAI,WAAW,KAAK,CAAC,CAAC;YAAE,MAAM;QAE9B,cAAc,IAAI,MAAM,CAAC;QACzB,MAAM,GAAG,GAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QAC7D,uEAAuE;QACvE,8DAA8D;QAC9D,GAAG,CAAC,OAAO,GAAG,CAAC,KAAK,IAAI,EAAE;YACxB,IAAI,CAAC;gBACH,IAAI,CAAC,KAAK,CAAC,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;YACpD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,WAAW,KAAK,CAAC,CAAC,IAAI,KAAK,GAAG,WAAW,EAAE,CAAC;oBAC9C,WAAW,GAAG,KAAK,CAAC;oBACpB,OAAO,GAAG,KAAK,CAAC;gBAClB,CAAC;YACH,CAAC;oBAAS,CAAC;gBACT,cAAc,IAAI,MAAM,CAAC;gBACzB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;YAClB,CAAC;QACH,CAAC,CAAC,EAAE,CAAC;QACL,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAED,0EAA0E;IAC1E,8DAA8D;IAC9D,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAEtD,IAAI,WAAW,KAAK,CAAC,CAAC,EAAE,CAAC;QACvB,KAAK,MAAM,GAAG,IAAI,IAAI;YAAE,IAAI,GAAG;gBAAE,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACtD,MAAM,OAAO,CAAC;IAChB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC9C,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACpD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG;YAAE,SAAS;QAC9B,wEAAwE;QACxE,0EAA0E;QAC1E,yDAAyD;QACzD,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,QAAQ;YAAE,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,aAAa,CAAC,MAAgC;IAC5D,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE;QAAE,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AAC9D,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spine-html",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Render Spine skeletal animations as plain DOM — rigid slots as <img> posed by CSS matrix, deform meshes on small per-part canvases, interleaved in one stacking context.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/dist/main.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=main.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":""}
package/dist/main.js DELETED
@@ -1,243 +0,0 @@
1
- import { AnimationState, AnimationStateData, AtlasAttachmentLoader, Physics, Skeleton, SkeletonJson, TextureAtlas, } from '@esotericsoftware/spine-core';
2
- import { DomTexture, unpackRegions } from './DomTexture.js';
3
- import { SpineHtmlRenderer } from './SpineHtmlRenderer.js';
4
- const ASSET_BASE = '/spineboy';
5
- const SKELETONS = {
6
- pro: 'spineboy-pro.json', // meshes (deform tier → per-part canvases)
7
- ess: 'spineboy-ess.json', // region attachments only (rigid tier → pure DOM)
8
- };
9
- const STAGE_SCALE = 0.4;
10
- // Debug knobs: ?skel=pro|ess &anim=walk &count=10 pick the scene;
11
- // ?expand=0 shows the mesh cracks; ?tint=ff8080 tints the whole skeleton;
12
- // ?dpr=2 overrides the mesh-canvas backing ratio; ?timescale=0 freezes the
13
- // animation (isolates the dirty-skip path: every mesh should report reused);
14
- // ?backend=webgl rasterizes meshes through the shared WebGL blitter;
15
- // ?time=1.2 seeks every instance to the same pose (deterministic screenshots).
16
- const params = new URLSearchParams(location.search);
17
- function parseTint() {
18
- const hex = params.get('tint');
19
- if (!hex || !/^[0-9a-fA-F]{6}$/.test(hex))
20
- return null;
21
- return {
22
- r: parseInt(hex.slice(0, 2), 16) / 255,
23
- g: parseInt(hex.slice(2, 4), 16) / 255,
24
- b: parseInt(hex.slice(4, 6), 16) / 255,
25
- };
26
- }
27
- async function loadImage(url) {
28
- return new Promise((resolve, reject) => {
29
- const img = new Image();
30
- img.onload = () => resolve(img);
31
- img.onerror = () => reject(new Error(`Failed to load ${url}`));
32
- img.src = url;
33
- });
34
- }
35
- async function load() {
36
- const [atlasText, proText, essText] = await Promise.all([
37
- fetch(`${ASSET_BASE}/spineboy.atlas`).then((r) => r.text()),
38
- fetch(`${ASSET_BASE}/${SKELETONS.pro}`).then((r) => r.text()),
39
- fetch(`${ASSET_BASE}/${SKELETONS.ess}`).then((r) => r.text()),
40
- ]);
41
- const atlas = new TextureAtlas(atlasText);
42
- const pageImages = new Map();
43
- for (const page of atlas.pages) {
44
- const image = await loadImage(`${ASSET_BASE}/${page.name}`);
45
- page.setTexture(new DomTexture(image));
46
- pageImages.set(page.name, image);
47
- }
48
- const regionImages = await unpackRegions(atlas, pageImages);
49
- const loader = new AtlasAttachmentLoader(atlas);
50
- return {
51
- data: {
52
- pro: new SkeletonJson(loader).readSkeletonData(proText),
53
- ess: new SkeletonJson(loader).readSkeletonData(essText),
54
- },
55
- regionImages,
56
- };
57
- }
58
- function main(data, regionImages) {
59
- const stage = document.getElementById('stage');
60
- const skelSelect = document.getElementById('skel');
61
- const animSelect = document.getElementById('anim');
62
- const countInput = document.getElementById('count');
63
- const backendSelect = document.getElementById('backend');
64
- const stats = document.getElementById('stats');
65
- backendSelect.value = params.get('backend') === 'webgl' ? 'webgl' : 'canvas2d';
66
- const skelParam = params.get('skel');
67
- let variant = skelParam === 'ess' ? 'ess' : 'pro';
68
- skelSelect.value = variant;
69
- let stateData = new AnimationStateData(data[variant]);
70
- let instances = [];
71
- const tint = parseTint();
72
- function fillAnimations() {
73
- animSelect.innerHTML = '';
74
- for (const anim of data[variant].animations) {
75
- const option = document.createElement('option');
76
- option.value = anim.name;
77
- option.textContent = anim.name;
78
- animSelect.appendChild(option);
79
- }
80
- const names = data[variant].animations.map((a) => a.name);
81
- animSelect.value = names.includes('walk') ? 'walk' : names[0];
82
- }
83
- function rebuild(count) {
84
- for (const inst of instances) {
85
- inst.renderer.dispose();
86
- inst.container.remove();
87
- }
88
- instances = [];
89
- for (let i = 0; i < count; i++) {
90
- const container = document.createElement('div');
91
- container.className = 'skeleton-root';
92
- // Spread instances horizontally; each root is the skeleton origin
93
- // (feet), so park it near the stage bottom.
94
- const x = count === 1 ? 50 : 10 + (80 * i) / (count - 1);
95
- container.style.left = `${x}%`;
96
- container.style.transform = `scale(${STAGE_SCALE})`;
97
- stage.appendChild(container);
98
- const skeleton = new Skeleton(data[variant]);
99
- if (tint)
100
- skeleton.color.set(tint.r, tint.g, tint.b, 1);
101
- const state = new AnimationState(stateData);
102
- state.setAnimation(0, animSelect.value, true);
103
- // Desync walk cycles so N instances don't look like one stamped sprite —
104
- // unless ?time seeks them all to one pose (for deterministic captures).
105
- const timeParam = params.get('time');
106
- state.update(timeParam !== null ? Number(timeParam) || 0 : (i * 0.37) % 2);
107
- const timescaleParam = params.get('timescale');
108
- if (timescaleParam !== null)
109
- state.timeScale = Number(timescaleParam) || 0;
110
- const renderer = new SpineHtmlRenderer(container, regionImages);
111
- renderer.meshBackend = backendSelect.value;
112
- const expandParam = params.get('expand');
113
- if (expandParam !== null)
114
- renderer.triangleExpand = Number(expandParam) || 0;
115
- // Mesh canvases raster at the effective on-screen resolution: device
116
- // pixels × the stage downscale (a plain devicePixelRatio would
117
- // oversample by 1/STAGE_SCALE).
118
- const dprParam = params.get('dpr');
119
- renderer.pixelRatio =
120
- dprParam !== null ? Number(dprParam) || 1 : (window.devicePixelRatio || 1) * STAGE_SCALE;
121
- instances.push({ skeleton, state, renderer, container });
122
- }
123
- }
124
- skelSelect.addEventListener('change', () => {
125
- variant = skelSelect.value;
126
- stateData = new AnimationStateData(data[variant]);
127
- stateData.defaultMix = 0.2;
128
- fillAnimations();
129
- rebuild(instances.length || 1);
130
- });
131
- animSelect.addEventListener('change', () => {
132
- for (const inst of instances)
133
- inst.state.setAnimation(0, animSelect.value, true);
134
- });
135
- countInput.addEventListener('change', () => {
136
- const count = Math.min(50, Math.max(1, Number(countInput.value) || 1));
137
- countInput.value = String(count);
138
- rebuild(count);
139
- });
140
- backendSelect.addEventListener('change', () => {
141
- // Live switch: the backend signature re-dirties every mesh on its own.
142
- for (const inst of instances)
143
- inst.renderer.meshBackend = backendSelect.value;
144
- });
145
- stateData.defaultMix = 0.2;
146
- fillAnimations();
147
- const animParam = params.get('anim');
148
- if (animParam && data[variant].animations.some((a) => a.name === animParam)) {
149
- animSelect.value = animParam;
150
- }
151
- const initialCount = Math.min(50, Math.max(1, Number(params.get('count')) || 1));
152
- countInput.value = String(initialCount);
153
- rebuild(initialCount);
154
- // Click the stats line to copy it (hand-selecting it mid-animation is fiddly).
155
- stats.title = 'click to copy';
156
- stats.addEventListener('click', async () => {
157
- try {
158
- await navigator.clipboard.writeText(stats.textContent ?? '');
159
- stats.style.color = '#ffd166';
160
- setTimeout(() => (stats.style.color = ''), 300);
161
- }
162
- catch {
163
- // Clipboard unavailable (non-secure context) — selecting stays possible.
164
- }
165
- });
166
- // --- frame loop with split timings (skeleton math vs DOM writes) ---
167
- let last = performance.now();
168
- let updateMs = 0;
169
- let renderMs = 0;
170
- let frames = 0;
171
- let statsAt = last;
172
- function frame(now) {
173
- const delta = Math.min((now - last) / 1000, 1 / 15);
174
- last = now;
175
- const t0 = performance.now();
176
- for (const inst of instances) {
177
- inst.state.update(delta);
178
- inst.state.apply(inst.skeleton);
179
- inst.skeleton.update(delta);
180
- inst.skeleton.updateWorldTransform(Physics.update);
181
- }
182
- const t1 = performance.now();
183
- for (const inst of instances)
184
- inst.renderer.render(inst.skeleton);
185
- const t2 = performance.now();
186
- updateMs += t1 - t0;
187
- renderMs += t2 - t1;
188
- frames++;
189
- if (now - statsAt >= 500 && frames > 0) {
190
- let meshes = 0;
191
- let reused = 0;
192
- let reallocs = 0;
193
- let triangles = 0;
194
- let clips = 0;
195
- let backing = 0;
196
- for (const inst of instances) {
197
- meshes += inst.renderer.meshCount;
198
- reused += inst.renderer.meshReuseCount;
199
- reallocs += inst.renderer.canvasReallocCount;
200
- triangles += inst.renderer.triangleCount;
201
- clips += inst.renderer.clipSkipCount;
202
- backing += inst.renderer.meshBackingPixels;
203
- }
204
- const reallocNote = reallocs ? ` / ${reallocs} realloc'd` : '';
205
- // Allocated mesh-canvas pixels: what an oversampling stage (a scaled
206
- // root whose scale is not folded into pixelRatio) shows first.
207
- const backingNote = backing ? ` · backing ${(backing / 1e6).toFixed(1)} Mpx` : '';
208
- const meshNote = meshes + reused
209
- ? ` · mesh canvases ${meshes} drawn (${triangles} tris) / ${reused} reused${reallocNote}${backingNote}`
210
- : '';
211
- const first = instances[0]?.renderer;
212
- // Surface the backend that actually ran, so a perf reading proves its path.
213
- const backendNote = first && first.meshBackend === 'webgl'
214
- ? first.meshBackendActive === 'webgl'
215
- ? ' · webgl'
216
- : ' · webgl unavailable → canvas2d'
217
- : '';
218
- const clipNote = clips ? ` · ${clips} clips skipped` : '';
219
- // Real fps from rAF cadence: catches bottlenecks that live outside the
220
- // frame callback (compositing, page throttling) which the ms split
221
- // cannot see.
222
- const fps = (frames * 1000) / (now - statsAt);
223
- stats.textContent =
224
- `${instances.length} skeleton(s) · ${fps.toFixed(0)} fps · ` +
225
- `skeleton ${(updateMs / frames).toFixed(2)}ms · ` +
226
- `render ${(renderMs / frames).toFixed(2)}ms / frame${meshNote}${backendNote}${clipNote}`;
227
- updateMs = renderMs = 0;
228
- frames = 0;
229
- statsAt = now;
230
- }
231
- requestAnimationFrame(frame);
232
- }
233
- requestAnimationFrame(frame);
234
- }
235
- load()
236
- .then(({ data, regionImages }) => main(data, regionImages))
237
- .catch((err) => {
238
- const stats = document.getElementById('stats');
239
- if (stats)
240
- stats.textContent = `Load failed: ${err.message} — did you run bun run fetch-assets?`;
241
- console.error(err);
242
- });
243
- //# sourceMappingURL=main.js.map
package/dist/main.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,qBAAqB,EACrB,OAAO,EACP,QAAQ,EAER,YAAY,EACZ,YAAY,GACb,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,UAAU,EAAoB,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAoB,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE7E,MAAM,UAAU,GAAG,WAAW,CAAC;AAC/B,MAAM,SAAS,GAAG;IAChB,GAAG,EAAE,mBAAmB,EAAE,2CAA2C;IACrE,GAAG,EAAE,mBAAmB,EAAE,kDAAkD;CACpE,CAAC;AAEX,MAAM,WAAW,GAAG,GAAG,CAAC;AAExB,kEAAkE;AAClE,0EAA0E;AAC1E,2EAA2E;AAC3E,6EAA6E;AAC7E,qEAAqE;AACrE,+EAA+E;AAC/E,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAEpD,SAAS,SAAS;IAChB,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,IAAI,CAAC,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACvD,OAAO;QACL,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG;QACtC,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG;QACtC,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG;KACvC,CAAC;AACJ,CAAC;AASD,KAAK,UAAU,SAAS,CAAC,GAAW;IAClC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,GAAG,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,kBAAkB,GAAG,EAAE,CAAC,CAAC,CAAC;QAC/D,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,IAAI;IAIjB,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACtD,KAAK,CAAC,GAAG,UAAU,iBAAiB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3D,KAAK,CAAC,GAAG,UAAU,IAAI,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7D,KAAK,CAAC,GAAG,UAAU,IAAI,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;KAC9D,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,IAAI,GAAG,EAA4B,CAAC;IACvD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,GAAG,UAAU,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5D,IAAI,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;QACvC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACnC,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IAC5D,MAAM,MAAM,GAAG,IAAI,qBAAqB,CAAC,KAAK,CAAC,CAAC;IAChD,OAAO;QACL,IAAI,EAAE;YACJ,GAAG,EAAE,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC;YACvD,GAAG,EAAE,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC;SACxD;QACD,YAAY;KACb,CAAC;AACJ,CAAC;AAED,SAAS,IAAI,CACX,IAA2C,EAC3C,YAAsC;IAEtC,MAAM,KAAK,GAAG,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAmB,CAAC;IACjE,MAAM,UAAU,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAsB,CAAC;IACxE,MAAM,UAAU,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAsB,CAAC;IACxE,MAAM,UAAU,GAAG,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAqB,CAAC;IACxE,MAAM,aAAa,GAAG,QAAQ,CAAC,cAAc,CAAC,SAAS,CAAsB,CAAC;IAC9E,MAAM,KAAK,GAAG,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAmB,CAAC;IACjE,aAAa,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;IAE/E,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,OAAO,GAAoB,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;IACnE,UAAU,CAAC,KAAK,GAAG,OAAO,CAAC;IAC3B,IAAI,SAAS,GAAG,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACtD,IAAI,SAAS,GAAe,EAAE,CAAC;IAC/B,MAAM,IAAI,GAAG,SAAS,EAAE,CAAC;IAEzB,SAAS,cAAc;QACrB,UAAU,CAAC,SAAS,GAAG,EAAE,CAAC;QAC1B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,CAAC;YAC5C,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAChD,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC;YACzB,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC;YAC/B,UAAU,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1D,UAAU,CAAC,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,SAAS,OAAO,CAAC,KAAa;QAC5B,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YACxB,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;QAC1B,CAAC;QACD,SAAS,GAAG,EAAE,CAAC;QACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/B,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;YAChD,SAAS,CAAC,SAAS,GAAG,eAAe,CAAC;YACtC,kEAAkE;YAClE,4CAA4C;YAC5C,MAAM,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YACzD,SAAS,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC;YAC/B,SAAS,CAAC,KAAK,CAAC,SAAS,GAAG,SAAS,WAAW,GAAG,CAAC;YACpD,KAAK,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;YAE7B,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;YAC7C,IAAI,IAAI;gBAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACxD,MAAM,KAAK,GAAG,IAAI,cAAc,CAAC,SAAS,CAAC,CAAC;YAC5C,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC9C,yEAAyE;YACzE,wEAAwE;YACxE,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACrC,KAAK,CAAC,MAAM,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3E,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YAC/C,IAAI,cAAc,KAAK,IAAI;gBAAE,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;YAE3E,MAAM,QAAQ,GAAG,IAAI,iBAAiB,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;YAChE,QAAQ,CAAC,WAAW,GAAG,aAAa,CAAC,KAAoB,CAAC;YAC1D,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACzC,IAAI,WAAW,KAAK,IAAI;gBAAE,QAAQ,CAAC,cAAc,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;YAC7E,qEAAqE;YACrE,+DAA+D;YAC/D,gCAAgC;YAChC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACnC,QAAQ,CAAC,UAAU;gBACjB,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,gBAAgB,IAAI,CAAC,CAAC,GAAG,WAAW,CAAC;YAC3F,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,UAAU,CAAC,gBAAgB,CAAC,QAAQ,EAAE,GAAG,EAAE;QACzC,OAAO,GAAG,UAAU,CAAC,KAAwB,CAAC;QAC9C,SAAS,GAAG,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAClD,SAAS,CAAC,UAAU,GAAG,GAAG,CAAC;QAC3B,cAAc,EAAE,CAAC;QACjB,OAAO,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IACH,UAAU,CAAC,gBAAgB,CAAC,QAAQ,EAAE,GAAG,EAAE;QACzC,KAAK,MAAM,IAAI,IAAI,SAAS;YAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACnF,CAAC,CAAC,CAAC;IACH,UAAU,CAAC,gBAAgB,CAAC,QAAQ,EAAE,GAAG,EAAE;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACvE,UAAU,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QACjC,OAAO,CAAC,KAAK,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC;IACH,aAAa,CAAC,gBAAgB,CAAC,QAAQ,EAAE,GAAG,EAAE;QAC5C,uEAAuE;QACvE,KAAK,MAAM,IAAI,IAAI,SAAS;YAAE,IAAI,CAAC,QAAQ,CAAC,WAAW,GAAG,aAAa,CAAC,KAAoB,CAAC;IAC/F,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,UAAU,GAAG,GAAG,CAAC;IAC3B,cAAc,EAAE,CAAC;IACjB,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,EAAE,CAAC;QAC5E,UAAU,CAAC,KAAK,GAAG,SAAS,CAAC;IAC/B,CAAC;IACD,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjF,UAAU,CAAC,KAAK,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;IACxC,OAAO,CAAC,YAAY,CAAC,CAAC;IAEtB,+EAA+E;IAC/E,KAAK,CAAC,KAAK,GAAG,eAAe,CAAC;IAC9B,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;QACzC,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;YAC7D,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,SAAS,CAAC;YAC9B,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,yEAAyE;QAC3E,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,sEAAsE;IACtE,IAAI,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAC7B,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,OAAO,GAAG,IAAI,CAAC;IAEnB,SAAS,KAAK,CAAC,GAAW;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;QACpD,IAAI,GAAG,GAAG,CAAC;QAEX,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAC7B,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACzB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAChC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5B,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACrD,CAAC;QACD,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAC7B,KAAK,MAAM,IAAI,IAAI,SAAS;YAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAClE,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAE7B,QAAQ,IAAI,EAAE,GAAG,EAAE,CAAC;QACpB,QAAQ,IAAI,EAAE,GAAG,EAAE,CAAC;QACpB,MAAM,EAAE,CAAC;QAET,IAAI,GAAG,GAAG,OAAO,IAAI,GAAG,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACvC,IAAI,MAAM,GAAG,CAAC,CAAC;YACf,IAAI,MAAM,GAAG,CAAC,CAAC;YACf,IAAI,QAAQ,GAAG,CAAC,CAAC;YACjB,IAAI,SAAS,GAAG,CAAC,CAAC;YAClB,IAAI,KAAK,GAAG,CAAC,CAAC;YACd,IAAI,OAAO,GAAG,CAAC,CAAC;YAChB,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;gBAC7B,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAClC,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC;gBACvC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC;gBAC7C,SAAS,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC;gBACzC,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC;gBACrC,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC;YAC7C,CAAC;YACD,MAAM,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC,MAAM,QAAQ,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/D,qEAAqE;YACrE,+DAA+D;YAC/D,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YAClF,MAAM,QAAQ,GACZ,MAAM,GAAG,MAAM;gBACb,CAAC,CAAC,oBAAoB,MAAM,WAAW,SAAS,YAAY,MAAM,UAAU,WAAW,GAAG,WAAW,EAAE;gBACvG,CAAC,CAAC,EAAE,CAAC;YACT,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC;YACrC,4EAA4E;YAC5E,MAAM,WAAW,GACf,KAAK,IAAI,KAAK,CAAC,WAAW,KAAK,OAAO;gBACpC,CAAC,CAAC,KAAK,CAAC,iBAAiB,KAAK,OAAO;oBACnC,CAAC,CAAC,UAAU;oBACZ,CAAC,CAAC,iCAAiC;gBACrC,CAAC,CAAC,EAAE,CAAC;YACT,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,KAAK,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1D,uEAAuE;YACvE,mEAAmE;YACnE,cAAc;YACd,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,OAAO,CAAC,CAAC;YAC9C,KAAK,CAAC,WAAW;gBACf,GAAG,SAAS,CAAC,MAAM,kBAAkB,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;oBAC5D,YAAY,CAAC,QAAQ,GAAG,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO;oBACjD,UAAU,CAAC,QAAQ,GAAG,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,QAAQ,GAAG,WAAW,GAAG,QAAQ,EAAE,CAAC;YAC3F,QAAQ,GAAG,QAAQ,GAAG,CAAC,CAAC;YACxB,MAAM,GAAG,CAAC,CAAC;YACX,OAAO,GAAG,GAAG,CAAC;QAChB,CAAC;QAED,qBAAqB,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IACD,qBAAqB,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED,IAAI,EAAE;KACH,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;KAC1D,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACb,MAAM,KAAK,GAAG,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;IAC/C,IAAI,KAAK;QAAE,KAAK,CAAC,WAAW,GAAG,gBAAgB,GAAG,CAAC,OAAO,sCAAsC,CAAC;IACjG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC,CAAC,CAAC"}