minerender 2.1.0-alpha.91 → 2.1.0-alpha.92

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.
@@ -29097,10 +29097,13 @@ var AnvilParser = class {
29097
29097
  return this.locations(this.bytes(data)).map(({ x, z }) => ({ x, z }));
29098
29098
  }
29099
29099
  /** Decodes all stored chunks. Gzip, zlib, LZ4, and uncompressed payloads are supported. */
29100
- static async parse(data) {
29100
+ static async parse(data, options = {}) {
29101
+ options.signal?.throwIfAborted();
29101
29102
  const bytes = this.bytes(data);
29102
29103
  const chunks = [];
29103
- for (const location of this.locations(bytes)) chunks.push(await this.readChunk(bytes, location));
29104
+ for (const location of this.locations(bytes)) {
29105
+ chunks.push(await this.abortable(this.readChunk(bytes, location, options), options.signal));
29106
+ }
29104
29107
  return { chunks };
29105
29108
  }
29106
29109
  /**
@@ -29109,13 +29112,28 @@ var AnvilParser = class {
29109
29112
  * @param localZ - Chunk z within the region, from 0 to 31.
29110
29113
  * @returns The chunk with absolute coordinates, or `undefined` if the region has no chunk there.
29111
29114
  */
29112
- static async parseChunk(data, localX, localZ) {
29115
+ static async parseChunk(data, localX, localZ, options = {}) {
29116
+ options.signal?.throwIfAborted();
29113
29117
  if (![localX, localZ].every((value) => Number.isInteger(value) && value >= 0 && value < 32)) {
29114
29118
  throw new RangeError("Region-local chunk coordinates must be integers from 0 to 31");
29115
29119
  }
29116
29120
  const bytes = this.bytes(data);
29117
29121
  const location = this.locations(bytes).find(({ x, z }) => x === localX && z === localZ);
29118
- return location ? this.readChunk(bytes, location) : void 0;
29122
+ return location ? this.abortable(this.readChunk(bytes, location, options), options.signal) : void 0;
29123
+ }
29124
+ static async abortable(promise, signal) {
29125
+ if (!signal) return promise;
29126
+ let abort;
29127
+ const cancelled = new Promise((_, reject) => {
29128
+ abort = () => reject(signal.reason);
29129
+ signal.addEventListener("abort", abort, { once: true });
29130
+ if (signal.aborted) abort();
29131
+ });
29132
+ try {
29133
+ return await Promise.race([promise, cancelled]);
29134
+ } finally {
29135
+ signal.removeEventListener("abort", abort);
29136
+ }
29119
29137
  }
29120
29138
  static bytes(data) {
29121
29139
  return data instanceof Uint8Array ? data : new Uint8Array(data);
@@ -29141,20 +29159,41 @@ var AnvilParser = class {
29141
29159
  }
29142
29160
  return locations;
29143
29161
  }
29144
- static async readChunk(data, location) {
29145
- const compression = data[location.offset + 4];
29146
- if (compression & 128) throw new MineRenderError("External Anvil .mcc chunks are not supported");
29162
+ static async readChunk(data, location, options) {
29163
+ options.signal?.throwIfAborted();
29164
+ const format2 = data[location.offset + 4];
29165
+ const external = (format2 & 128) !== 0;
29166
+ const compression = format2 & 127;
29147
29167
  if (compression !== 1 && compression !== 2 && compression !== 3 && compression !== 4) {
29148
29168
  throw new MineRenderError(`Unsupported Anvil compression ${compression}`);
29149
29169
  }
29150
29170
  let payload = data.subarray(location.offset + 5, location.offset + 4 + location.length);
29171
+ let expected;
29172
+ if (external) {
29173
+ if (!options.region || !options.readExternalChunk) {
29174
+ throw new MineRenderError("External Anvil .mcc chunks require region coordinates and readExternalChunk");
29175
+ }
29176
+ if (![options.region.x, options.region.z].every(Number.isSafeInteger)) {
29177
+ throw new RangeError("Anvil region coordinates must be safe integers");
29178
+ }
29179
+ expected = { x: options.region.x * 32 + location.x, z: options.region.z * 32 + location.z };
29180
+ if (![expected.x, expected.z].every(Number.isSafeInteger)) {
29181
+ throw new RangeError("External Anvil chunk coordinates must be safe integers");
29182
+ }
29183
+ const externalData = await options.readExternalChunk(expected.x, expected.z, options.signal);
29184
+ options.signal?.throwIfAborted();
29185
+ if (externalData === void 0) throw new MineRenderError(`External Anvil chunk c.${expected.x}.${expected.z}.mcc is missing`);
29186
+ payload = this.bytes(externalData);
29187
+ }
29151
29188
  if (compression === 4) {
29152
29189
  payload = decodeAnvilLz4(payload);
29153
29190
  } else if (compression !== 3) {
29154
29191
  const stream = new Blob([payload]).stream().pipeThrough(new DecompressionStream(compression === 1 ? "gzip" : "deflate"));
29155
29192
  payload = new Uint8Array(await new Response(stream).arrayBuffer());
29156
29193
  }
29194
+ options.signal?.throwIfAborted();
29157
29195
  const nbt = await NBTHelper.fromBuffer(payload, "big");
29196
+ options.signal?.throwIfAborted();
29158
29197
  const root = nbt.value;
29159
29198
  const level = root.Level?.type === "compound" ? root.Level.value : root;
29160
29199
  const dataVersion = root.DataVersion?.type === "int" ? root.DataVersion.value : void 0;
@@ -29167,6 +29206,9 @@ var AnvilParser = class {
29167
29206
  if ((chunk.x % 32 + 32) % 32 !== location.x || (chunk.z % 32 + 32) % 32 !== location.z) {
29168
29207
  throw new MineRenderError("Anvil chunk coordinates do not match its region location");
29169
29208
  }
29209
+ if (expected && (chunk.x !== expected.x || chunk.z !== expected.z)) {
29210
+ throw new MineRenderError(`Anvil chunk coordinates do not match external file c.${expected.x}.${expected.z}.mcc`);
29211
+ }
29170
29212
  for (const section of this.compounds(level.sections ?? level.Sections, "sections")) {
29171
29213
  const y = this.integer(section.Y, "section Y");
29172
29214
  if (section.Blocks || section.Data || section.Add) {
@@ -29298,6 +29340,7 @@ var AnvilWorldSource = class {
29298
29340
  this.cachedBytes = 0;
29299
29341
  this.maxCachedRegions = options.maxCachedRegions ?? 4;
29300
29342
  this.maxCachedBytes = options.maxCachedBytes ?? 64 * 1024 * 1024;
29343
+ this.readExternalChunk = options.readExternalChunk;
29301
29344
  for (const [name, value] of Object.entries({ maxCachedRegions: this.maxCachedRegions, maxCachedBytes: this.maxCachedBytes })) {
29302
29345
  if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a nonnegative safe integer`);
29303
29346
  }
@@ -29311,7 +29354,11 @@ var AnvilWorldSource = class {
29311
29354
  const data = await this.getRegion(regionX, regionZ, signal);
29312
29355
  signal?.throwIfAborted();
29313
29356
  if (!data) return void 0;
29314
- const chunk = await abortable2(AnvilParser.parseChunk(data, x - regionX * 32, z - regionZ * 32), signal);
29357
+ const chunk = await AnvilParser.parseChunk(data, x - regionX * 32, z - regionZ * 32, {
29358
+ region: { x: regionX, z: regionZ },
29359
+ readExternalChunk: this.readExternalChunk,
29360
+ signal
29361
+ });
29315
29362
  signal?.throwIfAborted();
29316
29363
  if (chunk && (chunk.x !== x || chunk.z !== z)) {
29317
29364
  throw new MineRenderError(`Anvil chunk coordinates ${chunk.x},${chunk.z} do not match requested column ${x},${z}`);
@@ -29342,7 +29389,7 @@ var AnvilWorldSource = class {
29342
29389
  settled: false,
29343
29390
  promise: Promise.resolve().then(() => {
29344
29391
  controller.signal.throwIfAborted();
29345
- return this.readRegion(x, z, controller.signal);
29392
+ return abortable2(this.readRegion(x, z, controller.signal), controller.signal);
29346
29393
  }).then((data) => {
29347
29394
  controller.signal.throwIfAborted();
29348
29395
  let bytes = data instanceof Uint8Array ? data : data === void 0 ? void 0 : new Uint8Array(data);