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.
@@ -29048,10 +29048,13 @@ var AnvilParser = class {
29048
29048
  return this.locations(this.bytes(data)).map(({ x, z }) => ({ x, z }));
29049
29049
  }
29050
29050
  /** Decodes all stored chunks. Gzip, zlib, LZ4, and uncompressed payloads are supported. */
29051
- static async parse(data) {
29051
+ static async parse(data, options = {}) {
29052
+ options.signal?.throwIfAborted();
29052
29053
  const bytes = this.bytes(data);
29053
29054
  const chunks = [];
29054
- for (const location of this.locations(bytes)) chunks.push(await this.readChunk(bytes, location));
29055
+ for (const location of this.locations(bytes)) {
29056
+ chunks.push(await this.abortable(this.readChunk(bytes, location, options), options.signal));
29057
+ }
29055
29058
  return { chunks };
29056
29059
  }
29057
29060
  /**
@@ -29060,13 +29063,28 @@ var AnvilParser = class {
29060
29063
  * @param localZ - Chunk z within the region, from 0 to 31.
29061
29064
  * @returns The chunk with absolute coordinates, or `undefined` if the region has no chunk there.
29062
29065
  */
29063
- static async parseChunk(data, localX, localZ) {
29066
+ static async parseChunk(data, localX, localZ, options = {}) {
29067
+ options.signal?.throwIfAborted();
29064
29068
  if (![localX, localZ].every((value) => Number.isInteger(value) && value >= 0 && value < 32)) {
29065
29069
  throw new RangeError("Region-local chunk coordinates must be integers from 0 to 31");
29066
29070
  }
29067
29071
  const bytes = this.bytes(data);
29068
29072
  const location = this.locations(bytes).find(({ x, z }) => x === localX && z === localZ);
29069
- return location ? this.readChunk(bytes, location) : void 0;
29073
+ return location ? this.abortable(this.readChunk(bytes, location, options), options.signal) : void 0;
29074
+ }
29075
+ static async abortable(promise, signal) {
29076
+ if (!signal) return promise;
29077
+ let abort;
29078
+ const cancelled = new Promise((_, reject) => {
29079
+ abort = () => reject(signal.reason);
29080
+ signal.addEventListener("abort", abort, { once: true });
29081
+ if (signal.aborted) abort();
29082
+ });
29083
+ try {
29084
+ return await Promise.race([promise, cancelled]);
29085
+ } finally {
29086
+ signal.removeEventListener("abort", abort);
29087
+ }
29070
29088
  }
29071
29089
  static bytes(data) {
29072
29090
  return data instanceof Uint8Array ? data : new Uint8Array(data);
@@ -29092,20 +29110,41 @@ var AnvilParser = class {
29092
29110
  }
29093
29111
  return locations;
29094
29112
  }
29095
- static async readChunk(data, location) {
29096
- const compression = data[location.offset + 4];
29097
- if (compression & 128) throw new MineRenderError("External Anvil .mcc chunks are not supported");
29113
+ static async readChunk(data, location, options) {
29114
+ options.signal?.throwIfAborted();
29115
+ const format2 = data[location.offset + 4];
29116
+ const external = (format2 & 128) !== 0;
29117
+ const compression = format2 & 127;
29098
29118
  if (compression !== 1 && compression !== 2 && compression !== 3 && compression !== 4) {
29099
29119
  throw new MineRenderError(`Unsupported Anvil compression ${compression}`);
29100
29120
  }
29101
29121
  let payload = data.subarray(location.offset + 5, location.offset + 4 + location.length);
29122
+ let expected;
29123
+ if (external) {
29124
+ if (!options.region || !options.readExternalChunk) {
29125
+ throw new MineRenderError("External Anvil .mcc chunks require region coordinates and readExternalChunk");
29126
+ }
29127
+ if (![options.region.x, options.region.z].every(Number.isSafeInteger)) {
29128
+ throw new RangeError("Anvil region coordinates must be safe integers");
29129
+ }
29130
+ expected = { x: options.region.x * 32 + location.x, z: options.region.z * 32 + location.z };
29131
+ if (![expected.x, expected.z].every(Number.isSafeInteger)) {
29132
+ throw new RangeError("External Anvil chunk coordinates must be safe integers");
29133
+ }
29134
+ const externalData = await options.readExternalChunk(expected.x, expected.z, options.signal);
29135
+ options.signal?.throwIfAborted();
29136
+ if (externalData === void 0) throw new MineRenderError(`External Anvil chunk c.${expected.x}.${expected.z}.mcc is missing`);
29137
+ payload = this.bytes(externalData);
29138
+ }
29102
29139
  if (compression === 4) {
29103
29140
  payload = decodeAnvilLz4(payload);
29104
29141
  } else if (compression !== 3) {
29105
29142
  const stream = new Blob([payload]).stream().pipeThrough(new DecompressionStream(compression === 1 ? "gzip" : "deflate"));
29106
29143
  payload = new Uint8Array(await new Response(stream).arrayBuffer());
29107
29144
  }
29145
+ options.signal?.throwIfAborted();
29108
29146
  const nbt = await NBTHelper.fromBuffer(payload, "big");
29147
+ options.signal?.throwIfAborted();
29109
29148
  const root = nbt.value;
29110
29149
  const level = root.Level?.type === "compound" ? root.Level.value : root;
29111
29150
  const dataVersion = root.DataVersion?.type === "int" ? root.DataVersion.value : void 0;
@@ -29118,6 +29157,9 @@ var AnvilParser = class {
29118
29157
  if ((chunk.x % 32 + 32) % 32 !== location.x || (chunk.z % 32 + 32) % 32 !== location.z) {
29119
29158
  throw new MineRenderError("Anvil chunk coordinates do not match its region location");
29120
29159
  }
29160
+ if (expected && (chunk.x !== expected.x || chunk.z !== expected.z)) {
29161
+ throw new MineRenderError(`Anvil chunk coordinates do not match external file c.${expected.x}.${expected.z}.mcc`);
29162
+ }
29121
29163
  for (const section of this.compounds(level.sections ?? level.Sections, "sections")) {
29122
29164
  const y = this.integer(section.Y, "section Y");
29123
29165
  if (section.Blocks || section.Data || section.Add) {
@@ -29249,6 +29291,7 @@ var AnvilWorldSource = class {
29249
29291
  this.cachedBytes = 0;
29250
29292
  this.maxCachedRegions = options.maxCachedRegions ?? 4;
29251
29293
  this.maxCachedBytes = options.maxCachedBytes ?? 64 * 1024 * 1024;
29294
+ this.readExternalChunk = options.readExternalChunk;
29252
29295
  for (const [name, value] of Object.entries({ maxCachedRegions: this.maxCachedRegions, maxCachedBytes: this.maxCachedBytes })) {
29253
29296
  if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a nonnegative safe integer`);
29254
29297
  }
@@ -29262,7 +29305,11 @@ var AnvilWorldSource = class {
29262
29305
  const data = await this.getRegion(regionX, regionZ, signal);
29263
29306
  signal?.throwIfAborted();
29264
29307
  if (!data) return void 0;
29265
- const chunk = await abortable2(AnvilParser.parseChunk(data, x - regionX * 32, z - regionZ * 32), signal);
29308
+ const chunk = await AnvilParser.parseChunk(data, x - regionX * 32, z - regionZ * 32, {
29309
+ region: { x: regionX, z: regionZ },
29310
+ readExternalChunk: this.readExternalChunk,
29311
+ signal
29312
+ });
29266
29313
  signal?.throwIfAborted();
29267
29314
  if (chunk && (chunk.x !== x || chunk.z !== z)) {
29268
29315
  throw new MineRenderError(`Anvil chunk coordinates ${chunk.x},${chunk.z} do not match requested column ${x},${z}`);
@@ -29293,7 +29340,7 @@ var AnvilWorldSource = class {
29293
29340
  settled: false,
29294
29341
  promise: Promise.resolve().then(() => {
29295
29342
  controller.signal.throwIfAborted();
29296
- return this.readRegion(x, z, controller.signal);
29343
+ return abortable2(this.readRegion(x, z, controller.signal), controller.signal);
29297
29344
  }).then((data) => {
29298
29345
  controller.signal.throwIfAborted();
29299
29346
  let bytes = data instanceof Uint8Array ? data : data === void 0 ? void 0 : new Uint8Array(data);