@nodecraft/rendermite 1.0.5 → 1.0.6

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 (110) hide show
  1. package/README.md +5 -0
  2. package/dist/atlas/registry.d.ts +3 -3
  3. package/dist/atlas/sources.d.ts +1 -1
  4. package/dist/{blend-Qvrn6YgK.d.ts → blend-CyDIxCne.d.ts} +1 -1
  5. package/dist/cache-Cr2ON3RQ.js +66 -0
  6. package/dist/cli.js +12 -6
  7. package/dist/compose-CQCb2LVv.js +104 -0
  8. package/dist/descriptor-DWebtkI1.js +26 -0
  9. package/dist/{discovery-D89pVVJA.d.ts → discovery-wiJBGryo.d.ts} +1 -1
  10. package/dist/dynamic/generators.d.ts +1 -1
  11. package/dist/dynamic/generators.js +1 -1
  12. package/dist/{encode-3u49i-eD.js → encode-D6dQu2dJ.js} +16 -4
  13. package/dist/{encode-C5kdAm45.d.ts → encode-DaNQF28M.d.ts} +1 -1
  14. package/dist/{entity-fallback-DfL0_eyf.js → entity-fallback-BRCeGKBT.js} +1 -1
  15. package/dist/{face-CpVdnf-w.d.ts → face-BIYg14TE.d.ts} +10 -4
  16. package/dist/{face-D-N1amV4.js → face-DmCiHUd5.js} +23 -10
  17. package/dist/{generators-CcVRG30y.js → generators-BlG4KmtP.js} +1 -1
  18. package/dist/{generators-CCB8h0mP.d.ts → generators-DhRov4RB.d.ts} +1 -1
  19. package/dist/{geo-Dq2zve09.js → geo-Diky41Tw.js} +6 -3
  20. package/dist/index.d.ts +14 -14
  21. package/dist/index.js +11 -11
  22. package/dist/{item-styles-DJ3LWZS2.js → item-styles-upgEO1yP.js} +1 -1
  23. package/dist/items/discovery.d.ts +1 -1
  24. package/dist/{jar-6T3xdgaL.d.ts → jar-CFkA-VgT.d.ts} +2 -1
  25. package/dist/{jar-Bt3i0ls4.js → jar-D9z-X7tA.js} +12 -12
  26. package/dist/{legacy-reshape-CewShfF3.js → legacy-reshape-C05SfAPR.js} +1 -1
  27. package/dist/{loader-A-BZa8J4.js → loader-B8NJO7pR.js} +12 -3
  28. package/dist/{loader-CBzn3Koc.js → loader-CfXMxcEV.js} +37 -7
  29. package/dist/{loader-C-6QN3xv.d.ts → loader-D22zZME7.d.ts} +2 -2
  30. package/dist/{loader-TFFNV4Jl.d.ts → loader-Myy22TDL.d.ts} +8 -2
  31. package/dist/{manager-dfI7b9N4.js → manager-CkletR5K.js} +29 -2
  32. package/dist/{manager-CoZaeQTu.d.ts → manager-Cv1NeFyF.d.ts} +28 -1
  33. package/dist/models/loader.d.ts +1 -1
  34. package/dist/models/loader.js +1 -1
  35. package/dist/mods/prepare.d.ts +1 -1
  36. package/dist/mods/prepare.js +1 -1
  37. package/dist/mods/synthesize.d.ts +2 -2
  38. package/dist/mods/synthesize.js +1 -1
  39. package/dist/mods/tints.d.ts +2 -2
  40. package/dist/mods/tints.js +1 -1
  41. package/dist/output/encode.d.ts +1 -1
  42. package/dist/output/encode.js +1 -1
  43. package/dist/output/item-styles.js +1 -1
  44. package/dist/pipeline-BIk1RRIp.js +221 -0
  45. package/dist/pipeline-zq-yU3Te.d.ts +58 -0
  46. package/dist/pool-CKGp1qup.js +131 -0
  47. package/dist/{prepare-BeUpXwaP.js → prepare-BESRrh6K.js} +4 -4
  48. package/dist/{prepare-gfR1aIjt.d.ts → prepare-CPxO0kKz.d.ts} +1 -1
  49. package/dist/{prepare-C8wRjAXP.js → prepare-Cc8QZ3j0.js} +4 -4
  50. package/dist/{prepare-wfWtaz_X.d.ts → prepare-Cs6QDqie.d.ts} +1 -1
  51. package/dist/{rasterizer-BHWDrmGz.js → rasterizer-76qO8TDh.js} +42 -4
  52. package/dist/{rasterizer-Dh35qXbn.d.ts → rasterizer-yqJkicm1.d.ts} +21 -2
  53. package/dist/render/blend.d.ts +1 -1
  54. package/dist/render/compose.d.ts +2 -2
  55. package/dist/render/compose.js +1 -1
  56. package/dist/render/entity-fallback.d.ts +6 -6
  57. package/dist/render/entity-fallback.js +1 -1
  58. package/dist/render/face.d.ts +2 -2
  59. package/dist/render/face.js +1 -1
  60. package/dist/render/fluid-bucket.d.ts +2 -2
  61. package/dist/render/geo.d.ts +4 -4
  62. package/dist/render/geo.js +1 -1
  63. package/dist/render/legacy-tints.d.ts +2 -2
  64. package/dist/render/obj.d.ts +5 -5
  65. package/dist/render/obj.js +1 -1
  66. package/dist/render/pipeline.d.ts +2 -0
  67. package/dist/render/pipeline.js +2 -0
  68. package/dist/render/rasterizer.d.ts +2 -2
  69. package/dist/render/rasterizer.js +2 -2
  70. package/dist/render/renderer.d.ts +2 -2
  71. package/dist/render/renderer.js +1 -1
  72. package/dist/render/tints.d.ts +1 -1
  73. package/dist/{renderer-LDhBNHcl.js → renderer-CjT19tHd.js} +130 -53
  74. package/dist/{renderer-nbMCw1Pl.d.ts → renderer-DVsZHAvf.d.ts} +23 -6
  75. package/dist/resources/cache.d.ts +30 -0
  76. package/dist/resources/cache.js +2 -0
  77. package/dist/resources/descriptor.d.ts +2 -0
  78. package/dist/resources/descriptor.js +2 -0
  79. package/dist/resources/jar.d.ts +1 -1
  80. package/dist/resources/jar.js +1 -1
  81. package/dist/resources/manager.d.ts +1 -1
  82. package/dist/resources/manager.js +1 -1
  83. package/dist/resources/synthetic.d.ts +1 -1
  84. package/dist/resources/synthetic.js +1 -1
  85. package/dist/resources/zip-reader.d.ts +5 -0
  86. package/dist/resources/zip-reader.js +2 -0
  87. package/dist/run-BnICOF4I.js +209 -0
  88. package/dist/{run-BlSZw9Qk.d.ts → run-CKUtynTy.d.ts} +12 -2
  89. package/dist/run.d.ts +1 -1
  90. package/dist/run.js +1 -1
  91. package/dist/{sources-DH9dk3KF.d.ts → sources-B9X9w3qP.d.ts} +1 -1
  92. package/dist/{synthesize-CRgSEmEp.js → synthesize-3Q0aaOnK.js} +1 -1
  93. package/dist/{synthetic-CDsj--li.d.ts → synthetic-Qk-BSUXO.d.ts} +5 -1
  94. package/dist/{synthetic-DC2bSnGe.js → synthetic-ZAUjXT7E.js} +0 -0
  95. package/dist/textures/loader.d.ts +1 -1
  96. package/dist/textures/loader.js +1 -1
  97. package/dist/{tints-CuG6GwQJ.js → tints-B8UJmZMu.js} +2 -2
  98. package/dist/{tints-txt-8xI5.d.ts → tints-DLBExVWN.d.ts} +2 -2
  99. package/dist/versions/legacy-reshape.d.ts +1 -1
  100. package/dist/versions/legacy-reshape.js +1 -1
  101. package/dist/versions/prepare.d.ts +1 -1
  102. package/dist/versions/prepare.js +1 -1
  103. package/dist/workers/pool.d.ts +36 -5
  104. package/dist/workers/pool.js +2 -67
  105. package/dist/workers/render-worker.d.ts +15 -0
  106. package/dist/workers/render-worker.js +40 -0
  107. package/dist/zip-reader-BSbJKUT1.js +156 -0
  108. package/package.json +4 -2
  109. package/dist/compose-qjhyBvnS.js +0 -41
  110. package/dist/run-CfnWIRY4.js +0 -316
@@ -1,5 +1,5 @@
1
1
  import { n as ResourceLocator } from "./locator-ycZzh_6z.js";
2
- import { n as MapResourceManager } from "./synthetic-DC2bSnGe.js";
2
+ import { n as MapResourceManager } from "./synthetic-ZAUjXT7E.js";
3
3
  //#region src/mods/synthesize.ts
4
4
  /**
5
5
  * Model synthesis for mods that ship textures without item models, ported from
@@ -1,5 +1,5 @@
1
1
  import { n as ResourceLocator } from "./locator-jDbdUeTd.js";
2
- import { a as ResourceManager, i as ResourceKindName } from "./manager-CoZaeQTu.js";
2
+ import { a as ResourceManager, i as ResourceKindName, s as ResourceDescriptor } from "./manager-Cv1NeFyF.js";
3
3
  //#region src/resources/synthetic.d.ts
4
4
  /**
5
5
  * In-memory resources, used for era shims (synthesized `models/item/generated.json`,
@@ -7,6 +7,8 @@ import { a as ResourceManager, i as ResourceKindName } from "./manager-CoZaeQTu.
7
7
  * mutating asset trees on disk like the old pipeline did.
8
8
  */
9
9
  declare class MapResourceManager implements ResourceManager {
10
+ /** Rebuild from a describe() payload (worker side). */
11
+ static fromEntries(entries: Iterable<[string, Uint8Array]>): MapResourceManager;
10
12
  private readonly entries;
11
13
  set(kind: ResourceKindName, locator: string | ResourceLocator, content: Buffer | string): this;
12
14
  getResource(kind: ResourceKindName, locator: ResourceLocator): Promise<Buffer | null>;
@@ -14,6 +16,7 @@ declare class MapResourceManager implements ResourceManager {
14
16
  getAllResources(kind: ResourceKindName, locator: ResourceLocator): Promise<Buffer[]>;
15
17
  listResources(kind: ResourceKindName, namespace: string): Promise<ResourceLocator[]>;
16
18
  listNamespaces(): Promise<string[]>;
19
+ describe(): ResourceDescriptor;
17
20
  }
18
21
  /**
19
22
  * Restricts a manager to specific resource kinds — donor versions may only
@@ -29,6 +32,7 @@ declare class KindFilteredResourceManager implements ResourceManager {
29
32
  getAllResources(kind: ResourceKindName, locator: ResourceLocator): Promise<Buffer[]>;
30
33
  listResources(kind: ResourceKindName, namespace: string): Promise<ResourceLocator[]>;
31
34
  listNamespaces(): Promise<string[]>;
35
+ describe(): ResourceDescriptor | null;
32
36
  }
33
37
  //#endregion
34
38
  export { MapResourceManager as n, KindFilteredResourceManager as t };
@@ -1,2 +1,2 @@
1
- import { a as TextureMeta, i as TextureLoader, n as AtlasResolverLike, o as TextureResolutionError, r as Texture, t as AnimatedTexture } from "../loader-TFFNV4Jl.js";
1
+ import { a as TextureMeta, i as TextureLoader, n as AtlasResolverLike, o as TextureResolutionError, r as Texture, t as AnimatedTexture } from "../loader-Myy22TDL.js";
2
2
  export { AnimatedTexture, AtlasResolverLike, Texture, TextureLoader, TextureMeta, TextureResolutionError };
@@ -1,2 +1,2 @@
1
- import { n as TextureLoader, r as TextureResolutionError, t as AnimatedTexture } from "../loader-CBzn3Koc.js";
1
+ import { n as TextureLoader, r as TextureResolutionError, t as AnimatedTexture } from "../loader-CfXMxcEV.js";
2
2
  export { AnimatedTexture, TextureLoader, TextureResolutionError };
@@ -1,8 +1,8 @@
1
1
  import { r as modTintPackPath } from "./builtin-caYjESh0.js";
2
2
  import { a as defaultProperties, o as evaluateItemModel } from "./definition-3MBm8ar_.js";
3
3
  import { n as ResourceLocator } from "./locator-ycZzh_6z.js";
4
- import { t as FolderResourceManager } from "./manager-dfI7b9N4.js";
5
- import { n as MapResourceManager } from "./synthetic-DC2bSnGe.js";
4
+ import { t as FolderResourceManager } from "./manager-CkletR5K.js";
5
+ import { n as MapResourceManager } from "./synthetic-ZAUjXT7E.js";
6
6
  //#region src/mods/tints.ts
7
7
  /**
8
8
  * Tints for mod items whose colors live in client code rather than asset JSON.
@@ -1,5 +1,5 @@
1
- import { i as TextureLoader } from "./loader-TFFNV4Jl.js";
2
- import { n as TintColor } from "./face-CpVdnf-w.js";
1
+ import { i as TextureLoader } from "./loader-Myy22TDL.js";
2
+ import { r as TintColor } from "./face-BIYg14TE.js";
3
3
  import { o as TintDefinition } from "./definition-BwX6Xxv0.js";
4
4
  //#region src/render/tints.d.ts
5
5
  declare class InvalidTinterDefinitionError extends Error {}
@@ -1,4 +1,4 @@
1
- import { a as ResourceManager } from "../manager-CoZaeQTu.js";
1
+ import { a as ResourceManager } from "../manager-Cv1NeFyF.js";
2
2
  //#region src/versions/legacy-reshape.d.ts
3
3
  /**
4
4
  * Build an in-memory texture layer for a pre-1.6 jar. `sheets` selects the
@@ -1,2 +1,2 @@
1
- import { t as reshapeLegacyAssets } from "../legacy-reshape-CewShfF3.js";
1
+ import { t as reshapeLegacyAssets } from "../legacy-reshape-C05SfAPR.js";
2
2
  export { reshapeLegacyAssets };
@@ -1,2 +1,2 @@
1
- import { a as compareVersions, i as PrepareOptions, n as EraName, o as eraForVersion, r as GEOMETRY_DONOR_VERSIONS, s as prepareVersionResources, t as Era } from "../prepare-wfWtaz_X.js";
1
+ import { a as compareVersions, i as PrepareOptions, n as EraName, o as eraForVersion, r as GEOMETRY_DONOR_VERSIONS, s as prepareVersionResources, t as Era } from "../prepare-Cs6QDqie.js";
2
2
  export { Era, EraName, GEOMETRY_DONOR_VERSIONS, PrepareOptions, compareVersions, eraForVersion, prepareVersionResources };
@@ -1,2 +1,2 @@
1
- import { a as prepareVersionResources, i as eraForVersion, n as GEOMETRY_DONOR_VERSIONS, r as compareVersions, t as Era } from "../prepare-C8wRjAXP.js";
1
+ import { a as prepareVersionResources, i as eraForVersion, n as GEOMETRY_DONOR_VERSIONS, r as compareVersions, t as Era } from "../prepare-Cc8QZ3j0.js";
2
2
  export { Era, GEOMETRY_DONOR_VERSIONS, compareVersions, eraForVersion, prepareVersionResources };
@@ -2,8 +2,14 @@
2
2
  /**
3
3
  * Worker-thread pool for parallel item rendering. Design goals born from the
4
4
  * PHP tool's failure modes (docs/research/mod-failure-taxonomy.md): errors
5
- * cross the worker boundary as structured data, a dead worker's queue items are
6
- * requeued not orphaned, and results stream back so output lands incrementally.
5
+ * cross the worker boundary as structured data, a dead worker's in-flight items
6
+ * are reported rather than orphaned, and results stream back so output lands
7
+ * incrementally.
8
+ *
9
+ * Workers spawn as soon as the pool is constructed, so their asset indexing
10
+ * overlaps the caller's own discovery pass; each holds several tasks at once so
11
+ * native encoding overlaps the next item's rasterization; and the queue drains
12
+ * through a cursor, because shift() on a 400k-item array is quadratic.
7
13
  */
8
14
  interface WorkerTask {
9
15
  id: string;
@@ -16,8 +22,33 @@ interface WorkerResult<T> {
16
22
  interface PoolOptions {
17
23
  workerUrl: URL;
18
24
  workerData: unknown;
19
- concurrency: number;
25
+ /** Worker threads to spawn. */
26
+ workers: number;
27
+ /** Tasks held by each worker at once (default 2). */
28
+ inFlight?: number;
29
+ }
30
+ declare class WorkerRenderPool<T> {
31
+ private readonly options;
32
+ private readonly inFlightLimit;
33
+ private readonly workers;
34
+ private tasks;
35
+ private cursor;
36
+ private started;
37
+ private onResult;
38
+ private settle;
39
+ private settled;
40
+ /** Workers dying before completing anything mean a bad config, not bad luck. */
41
+ private startupFailures;
42
+ constructor(options: PoolOptions);
43
+ private spawn;
44
+ private pump;
45
+ private retire;
46
+ private fail;
47
+ private finish;
48
+ private abort;
49
+ /** Run every task, calling back as results arrive. Terminates the pool after. */
50
+ run(tasks: WorkerTask[], onResult: (result: WorkerResult<T>) => void): Promise<void>;
51
+ close(): Promise<void>;
20
52
  }
21
- declare function runPool<T>(tasks: WorkerTask[], options: PoolOptions, onResult: (result: WorkerResult<T>) => void): Promise<void>;
22
53
  //#endregion
23
- export { PoolOptions, WorkerResult, WorkerTask, runPool };
54
+ export { PoolOptions, WorkerRenderPool, WorkerResult, WorkerTask };
@@ -1,67 +1,2 @@
1
- import { Worker } from "node:worker_threads";
2
- //#region src/workers/pool.ts
3
- /**
4
- * Worker-thread pool for parallel item rendering. Design goals born from the
5
- * PHP tool's failure modes (docs/research/mod-failure-taxonomy.md): errors
6
- * cross the worker boundary as structured data, a dead worker's queue items are
7
- * requeued not orphaned, and results stream back so output lands incrementally.
8
- */
9
- async function runPool(tasks, options, onResult) {
10
- const queue = [...tasks];
11
- const inFlight = /* @__PURE__ */ new Map();
12
- await new Promise((resolve) => {
13
- let active = 0;
14
- const spawn = () => {
15
- const worker = new Worker(options.workerUrl, { workerData: options.workerData });
16
- active++;
17
- const next = () => {
18
- const task = queue.shift();
19
- if (task === void 0) {
20
- worker.terminate().catch(() => {});
21
- active--;
22
- if (active === 0) resolve();
23
- return;
24
- }
25
- inFlight.set(worker, task);
26
- worker.postMessage(task);
27
- };
28
- worker.on("message", (message) => {
29
- inFlight.delete(worker);
30
- onResult(message);
31
- next();
32
- });
33
- worker.on("error", (err) => {
34
- const task = inFlight.get(worker);
35
- inFlight.delete(worker);
36
- if (task) onResult({
37
- id: task.id,
38
- error: `Worker crashed: ${err.message}`
39
- });
40
- active--;
41
- if (queue.length > 0) spawn();
42
- else if (active === 0) resolve();
43
- });
44
- worker.on("exit", (code) => {
45
- const task = inFlight.get(worker);
46
- if (task && code !== 0) {
47
- inFlight.delete(worker);
48
- onResult({
49
- id: task.id,
50
- error: `Worker exited with code ${code}`
51
- });
52
- active--;
53
- if (queue.length > 0) spawn();
54
- else if (active === 0) resolve();
55
- }
56
- });
57
- next();
58
- };
59
- if (queue.length === 0) {
60
- resolve();
61
- return;
62
- }
63
- for (let i = 0; i < Math.min(options.concurrency, queue.length); i++) spawn();
64
- });
65
- }
66
- //#endregion
67
- export { runPool };
1
+ import { t as WorkerRenderPool } from "../pool-CKGp1qup.js";
2
+ export { WorkerRenderPool };
@@ -0,0 +1,15 @@
1
+ import { s as ResourceDescriptor } from "../manager-Cv1NeFyF.js";
2
+ import { n as ItemRenderOutcome, r as PipelineSettings } from "../pipeline-zq-yU3Te.js";
3
+ //#region src/workers/render-worker.d.ts
4
+ interface RenderWorkerData {
5
+ resources: ResourceDescriptor;
6
+ settings: PipelineSettings;
7
+ /** libvips threads per worker; the pool already saturates the CPU. */
8
+ sharpConcurrency: number;
9
+ }
10
+ interface RenderWorkerTask {
11
+ id: string;
12
+ }
13
+ type RenderWorkerOutcome = ItemRenderOutcome;
14
+ //#endregion
15
+ export { RenderWorkerData, RenderWorkerOutcome, RenderWorkerTask };
@@ -0,0 +1,40 @@
1
+ import { t as ItemPipeline } from "../pipeline-BIk1RRIp.js";
2
+ import { n as restoreResources } from "../descriptor-DWebtkI1.js";
3
+ import sharp from "sharp";
4
+ import { parentPort, workerData } from "node:worker_threads";
5
+ //#region src/workers/render-worker.ts
6
+ /**
7
+ * Worker entry: rebuilds the run's resource tree from its descriptor, then
8
+ * renders, encodes and writes items on demand. Items are written here rather
9
+ * than shipped back as buffers — the main thread only needs the outcome, and
10
+ * the file write is the run's success signal either way.
11
+ *
12
+ * Messages in are `{ id }`; messages out are `{ id, result }` or `{ id, error }`.
13
+ * Errors cross as strings because Error instances lose their type across the
14
+ * structured-clone boundary.
15
+ */
16
+ if (!parentPort) throw new Error("render-worker must be started as a worker thread");
17
+ const port = parentPort;
18
+ const data = workerData;
19
+ sharp.concurrency(data.sharpConcurrency);
20
+ const pipeline = await ItemPipeline.create(restoreResources(data.resources), data.settings);
21
+ async function handle(task) {
22
+ try {
23
+ const result = await pipeline.renderItem(task.id);
24
+ port.postMessage({
25
+ id: task.id,
26
+ result
27
+ });
28
+ } catch (err) {
29
+ port.postMessage({
30
+ id: task.id,
31
+ error: err.message
32
+ });
33
+ }
34
+ }
35
+ port.on("message", (task) => {
36
+ handle(task).catch(() => {});
37
+ });
38
+ port.postMessage({ ready: true });
39
+ //#endregion
40
+ export {};
@@ -0,0 +1,156 @@
1
+ import { close, createReadStream, fstat, open, read } from "node:fs";
2
+ import { Readable } from "node:stream";
3
+ import { RandomAccessReader, fromRandomAccessReader, open as open$1 } from "yauzl";
4
+ //#region src/resources/zip-reader.ts
5
+ /**
6
+ * Opens a zip with its central directory held in memory.
7
+ *
8
+ * yauzl reads each central-directory record with its own small read, so a
9
+ * 26,000-entry client jar costs ~26,000 syscalls and well over a second before
10
+ * the first icon renders. The directory is one contiguous region, so reading it
11
+ * whole and serving those reads from the buffer makes indexing ~6× faster while
12
+ * keeping entry data itself on disk — only the directory stays resident.
13
+ *
14
+ * Anything unexpected (zip64, a truncated archive, no end-of-directory record)
15
+ * falls back to yauzl's own open, which is correct for every archive.
16
+ */
17
+ const EOCD_SIGNATURE = 101010256;
18
+ /** End-of-directory record plus the largest possible archive comment. */
19
+ const EOCD_SEARCH_BYTES = 65557;
20
+ const ZIP64_SENTINEL = 4294967295;
21
+ /** Above this, a range streams from its own descriptor instead of one buffer. */
22
+ const MAX_BUFFERED_READ_BYTES = 33554432;
23
+ /** Locate the central directory from the end-of-directory record. */
24
+ function findCentralDirectory(tail, tailStart) {
25
+ for (let index = tail.length - 22; index >= 0; index--) {
26
+ if (tail.readUInt32LE(index) !== EOCD_SIGNATURE) continue;
27
+ const size = tail.readUInt32LE(index + 12);
28
+ const offset = tail.readUInt32LE(index + 16);
29
+ if (size === ZIP64_SENTINEL || offset === ZIP64_SENTINEL) return null;
30
+ if (offset + size > tailStart + index) return null;
31
+ return {
32
+ offset,
33
+ size
34
+ };
35
+ }
36
+ return null;
37
+ }
38
+ /** Promisified positional read; a raw descriptor avoids FileHandle's close-on-GC. */
39
+ async function readAt(fd, buffer, offset, length, position) {
40
+ return new Promise((resolve, reject) => {
41
+ read(fd, buffer, offset, length, position, (err, bytesRead) => {
42
+ if (err) {
43
+ reject(err);
44
+ return;
45
+ }
46
+ resolve(bytesRead);
47
+ });
48
+ });
49
+ }
50
+ var BufferedDirectoryReader = class extends RandomAccessReader {
51
+ path;
52
+ fd;
53
+ directory;
54
+ directoryStart;
55
+ constructor(path, fd, directory, directoryStart) {
56
+ super();
57
+ this.path = path;
58
+ this.fd = fd;
59
+ this.directory = directory;
60
+ this.directoryStart = directoryStart;
61
+ }
62
+ _readStreamForRange(start, end) {
63
+ const directoryEnd = this.directoryStart + this.directory.length;
64
+ if (start >= this.directoryStart && end <= directoryEnd) return Readable.from([this.directory.subarray(start - this.directoryStart, end - this.directoryStart)]);
65
+ const length = end - start;
66
+ if (length > MAX_BUFFERED_READ_BYTES) return createReadStream(this.path, {
67
+ start,
68
+ end: end - 1
69
+ });
70
+ const { fd } = this;
71
+ return Readable.from((async function* read() {
72
+ const buffer = Buffer.alloc(length);
73
+ let filled = 0;
74
+ while (filled < length) {
75
+ const bytesRead = await readAt(fd, buffer, filled, length - filled, start + filled);
76
+ if (bytesRead === 0) break;
77
+ filled += bytesRead;
78
+ }
79
+ yield filled === length ? buffer : buffer.subarray(0, filled);
80
+ })());
81
+ }
82
+ /**
83
+ * yauzl reads each directory record through here. Copying from the buffer
84
+ * skips the per-read stream pipeline entirely, which is most of the cost of
85
+ * indexing tens of thousands of entries.
86
+ */
87
+ read(buffer, offset, length, position, callback) {
88
+ const directoryEnd = this.directoryStart + this.directory.length;
89
+ if (position >= this.directoryStart && position + length <= directoryEnd) {
90
+ const from = position - this.directoryStart;
91
+ this.directory.copy(buffer, offset, from, from + length);
92
+ queueMicrotask(() => callback(null));
93
+ return;
94
+ }
95
+ readAt(this.fd, buffer, offset, length, position).then(() => callback(null)).catch(callback);
96
+ }
97
+ /** yauzl's close hook, reached when the ZipFile releases its last reference. */
98
+ close(callback) {
99
+ close(this.fd, (err) => callback(err ?? null));
100
+ }
101
+ };
102
+ async function openZip(zipPath) {
103
+ const fallback = async () => new Promise((resolve, reject) => {
104
+ open$1(zipPath, {
105
+ lazyEntries: true,
106
+ autoClose: false
107
+ }, (err, zip) => {
108
+ if (err) {
109
+ reject(err);
110
+ return;
111
+ }
112
+ resolve(zip);
113
+ });
114
+ });
115
+ let fd;
116
+ try {
117
+ fd = await new Promise((resolve, reject) => {
118
+ open(zipPath, "r", (err, handle) => err ? reject(err) : resolve(handle));
119
+ });
120
+ } catch {
121
+ return fallback();
122
+ }
123
+ const abandon = async () => {
124
+ close(fd, () => {});
125
+ return fallback();
126
+ };
127
+ try {
128
+ const size = await new Promise((resolve, reject) => {
129
+ fstat(fd, (err, stats) => err ? reject(err) : resolve(stats.size));
130
+ });
131
+ const tailStart = Math.max(0, size - EOCD_SEARCH_BYTES);
132
+ const tail = Buffer.alloc(size - tailStart);
133
+ await readAt(fd, tail, 0, tail.length, tailStart);
134
+ const central = findCentralDirectory(tail, tailStart);
135
+ if (!central) return await abandon();
136
+ const directory = Buffer.alloc(central.size);
137
+ await readAt(fd, directory, 0, central.size, central.offset);
138
+ const reader = new BufferedDirectoryReader(zipPath, fd, directory, central.offset);
139
+ return await new Promise((resolve, reject) => {
140
+ fromRandomAccessReader(reader, size, {
141
+ lazyEntries: true,
142
+ autoClose: false
143
+ }, (err, zip) => {
144
+ if (err) {
145
+ reject(err);
146
+ return;
147
+ }
148
+ resolve(zip);
149
+ });
150
+ });
151
+ } catch {
152
+ return abandon();
153
+ }
154
+ }
155
+ //#endregion
156
+ export { openZip as t };
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@nodecraft/rendermite",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Minecraft item icon renderer: renders inventory-identical item icons from vanilla and modded assets across game versions, outputting optimized PNG/WebP/AVIF plus CSS and a JSON item library.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
- "author": "James Ross <james@jross.me> (https://jross.me/)",
7
+ "author": "Nodecraft",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/nodecraft/rendermite.git"
@@ -42,6 +42,8 @@
42
42
  "lint": "eslint . --fix",
43
43
  "lint:ci": "eslint .",
44
44
  "test": "vitest run",
45
+ "bench": "vitest bench --config vitest.bench.config.ts --run",
46
+ "bench:report": "vitest bench --config vitest.bench.config.ts --run --outputJson bench-results.json && node scripts/bench-report.ts",
45
47
  "test:watch": "vitest",
46
48
  "typecheck": "tsc --noEmit",
47
49
  "semantic-release": "semantic-release"
@@ -1,41 +0,0 @@
1
- import { t as blendPixel } from "./blend-CHP2-j9U.js";
2
- //#region src/render/compose.ts
3
- /**
4
- * Per-pixel painter's compositing, mirroring upstream Model::combineFaceImages:
5
- * at each pixel, faces sorted ascending by that pixel's depth value (0 where the
6
- * rasterizer never wrote depth — the overdraw band relies on sorting beneath),
7
- * then source-over blended in that order when alpha > 0.
8
- */
9
- function combineFaceBuffers(width, height, faces) {
10
- const out = new Float64Array(width * height * 4);
11
- const order = [...faces];
12
- for (let pixel = 0; pixel < width * height; pixel++) {
13
- const x = pixel % width;
14
- const y = Math.floor(pixel / width);
15
- const localPixel = (face) => {
16
- const localX = x - face.x;
17
- const localY = y - face.y;
18
- return localX >= 0 && localX < face.width && localY >= 0 && localY < face.height ? localY * face.width + localX : null;
19
- };
20
- order.sort((a, b) => {
21
- const aPixel = localPixel(a);
22
- const bPixel = localPixel(b);
23
- return (aPixel === null ? 0 : a.depths[aPixel]) - (bPixel === null ? 0 : b.depths[bPixel]);
24
- });
25
- const offset = pixel * 4;
26
- for (const face of order) {
27
- const facePixel = localPixel(face);
28
- if (facePixel === null) continue;
29
- const faceOffset = facePixel * 4;
30
- const alpha = face.colors[faceOffset + 3];
31
- if (alpha > 0) blendPixel(out, offset, face.colors[faceOffset], face.colors[faceOffset + 1], face.colors[faceOffset + 2], alpha);
32
- }
33
- }
34
- return {
35
- width,
36
- height,
37
- data: out
38
- };
39
- }
40
- //#endregion
41
- export { combineFaceBuffers as t };