minerender 2.1.0-alpha.137 → 2.1.0-alpha.139

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.
package/dist/bundle.js CHANGED
@@ -97035,13 +97035,16 @@ property list uchar int vertex_index
97035
97035
  const blocks = nbt.blocks.value.value.map((block) => {
97036
97036
  const state = palette.value.value[block.state.value];
97037
97037
  if (!state) throw new MineRenderError(`Structure palette index ${block.state.value} does not exist`);
97038
- if (["minecraft:air", "minecraft:cave_air", "minecraft:void_air"].includes(state.Name.value)) return void 0;
97038
+ const name2 = (state.Name ?? state.id)?.value;
97039
+ if (!name2) throw new MineRenderError(`Structure palette entry ${block.state.value} has no block name`);
97040
+ if (["minecraft:air", "minecraft:cave_air", "minecraft:void_air"].includes(name2)) return void 0;
97039
97041
  const props = {};
97040
- for (let k4 in state.Properties?.value) {
97041
- props[k4] = state.Properties.value[k4].value;
97042
+ const properties = (state.Properties ?? state.properties)?.value;
97043
+ for (let k4 in properties) {
97044
+ props[k4] = properties[k4].value;
97042
97045
  }
97043
97046
  return {
97044
- type: state.Name.value,
97047
+ type: name2,
97045
97048
  properties: props,
97046
97049
  position: block.pos.value.value,
97047
97050
  nbt: block.nbt
@@ -103880,8 +103883,14 @@ gl_FragDepth=readDepth(vUv);
103880
103883
  }
103881
103884
  return queued;
103882
103885
  }
103883
- async refreshBiomes() {
103884
- if (this.biomeRefreshing) await this.biomeRefreshing;
103886
+ async refreshBiomes(deferCulling = false) {
103887
+ if (this.biomeRefreshing) {
103888
+ await this.biomeRefreshing;
103889
+ if (!deferCulling) {
103890
+ if (this.pendingCulling.size) await this.updateCulling([]);
103891
+ else await this.culling;
103892
+ }
103893
+ }
103885
103894
  if (!this.pendingBiomes.size) return /* @__PURE__ */ new Map();
103886
103895
  return this.biomeRefreshing ?? (this.biomeRefreshing = Promise.resolve().then(async () => {
103887
103896
  const failures = /* @__PURE__ */ new Map();
@@ -103893,7 +103902,7 @@ gl_FragDepth=readDepth(vUv);
103893
103902
  this.pendingBiomes.delete(chunk);
103894
103903
  if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) !== chunk) continue;
103895
103904
  try {
103896
- await chunk.refreshBiomes(columns);
103905
+ await chunk.refreshBiomes(columns, deferCulling);
103897
103906
  } catch (error2) {
103898
103907
  failures.set(chunk, error2);
103899
103908
  if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingBiomes.set(chunk, void 0);
@@ -104155,15 +104164,35 @@ gl_FragDepth=readDepth(vUv);
104155
104164
  * Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
104156
104165
  */
104157
104166
  async unloadChunkColumn(x4, z4) {
104158
- if (!Number.isInteger(x4) || !Number.isInteger(z4)) {
104159
- throw new RangeError("Chunk column coordinates must be integers");
104167
+ return this.unloadChunkColumns([{ x: x4, z: z4 }]);
104168
+ }
104169
+ /**
104170
+ * Removes chunk columns together and refreshes surviving neighbors once.
104171
+ * Coordinates are absolute chunk columns. Duplicate and missing columns are ignored.
104172
+ */
104173
+ async unloadChunkColumns(positions) {
104174
+ const columns = /* @__PURE__ */ new Map();
104175
+ for (const position of positions) {
104176
+ const { x: x4, z: z4 } = position ?? {};
104177
+ if (!Number.isInteger(x4) || !Number.isInteger(z4)) {
104178
+ throw new RangeError("Chunk column coordinates must be integers");
104179
+ }
104180
+ columns.set(`${x4}_${z4}`, { x: x4, z: z4 });
104160
104181
  }
104161
- this.entities?.clearColumn(x4, z4);
104182
+ if (!columns.size) return;
104162
104183
  await this.culling;
104184
+ let sliceStart = performance.now();
104163
104185
  try {
104164
- await this.clearChunkColumn(x4, z4);
104186
+ for (const { x: x4, z: z4 } of columns.values()) {
104187
+ this.entities?.clearColumn(x4, z4);
104188
+ await this.clearChunkColumn(x4, z4);
104189
+ if (performance.now() - sliceStart > 8) {
104190
+ await yieldToEventLoop();
104191
+ sliceStart = performance.now();
104192
+ }
104193
+ }
104165
104194
  } finally {
104166
- const failures = await this.refreshBiomes();
104195
+ const failures = await this.refreshBiomes(true);
104167
104196
  if (failures.size) log21.warn("Failed to refresh biome colors", failures.values().next().value);
104168
104197
  await this.updateCulling([]);
104169
104198
  }
@@ -104226,7 +104255,7 @@ gl_FragDepth=readDepth(vUv);
104226
104255
  }
104227
104256
  this.sectionModels?.clear();
104228
104257
  }
104229
- updateCulling(positions) {
104258
+ updateCulling(positions, defer = false) {
104230
104259
  const neighbor = new Vector3();
104231
104260
  for (const pos of positions) {
104232
104261
  const cx = Math.floor(pos.x / 16), cy = Math.floor(pos.y / 16), cz = Math.floor(pos.z / 16);
@@ -104248,58 +104277,72 @@ gl_FragDepth=readDepth(vUv);
104248
104277
  }
104249
104278
  }
104250
104279
  }
104280
+ if (defer) return Promise.resolve();
104251
104281
  return this.culling ?? (this.culling = Promise.resolve().then(async () => {
104252
104282
  let sliceStart = performance.now();
104253
104283
  try {
104254
104284
  const culling = await loadBlockCulling();
104255
104285
  while (this.pendingCulling.size) {
104256
- const batch = [...this.pendingCulling];
104286
+ const batch = new Map(this.pendingCulling);
104257
104287
  this.pendingCulling.clear();
104258
- for (const [chunk, indices] of batch) {
104259
- const neighbors = CUBE_FACE_OFFSETS.map(([x4, y5, z4]) => this._chunks.get(`${chunk.x + x4}_${chunk.y + y5}_${chunk.z + z4}`));
104260
- const neighborAt = (x4, y5, z4) => {
104261
- const lx = x4 - chunk.x * 16, ly = y5 - chunk.y * 16, lz = z4 - chunk.z * 16;
104262
- const section = neighbors[lx >= 16 ? 0 : lx < 0 ? 1 : ly >= 16 ? 2 : ly < 0 ? 3 : lz >= 16 ? 4 : 5];
104263
- return [section, (ly & 15) * 256 + (lz & 15) * 16 + (lx & 15)];
104264
- };
104265
- const masks = chunk.updateCullMasks(indices === "all" ? void 0 : indices, (x4, y5, z4) => {
104266
- const [section, i4] = neighborAt(x4, y5, z4);
104267
- return section?.isOccludingIndex(i4) ?? false;
104268
- }, culling, (x4, y5, z4) => {
104269
- const [section, i4] = neighborAt(x4, y5, z4);
104270
- return section?.visibleBlockIndex(i4);
104271
- });
104272
- const fluidIndices = indices === "all" ? function* () {
104273
- for (let index = 0; index < 4096; index++) yield index;
104274
- }() : indices;
104275
- for (const index of fluidIndices) {
104276
- if (!chunk.fluidByteIndex(index) || !chunk.isBlockVisibleIndex(index)) continue;
104277
- const lx = index % 16, ly = Math.floor(index / 256), lz = Math.floor(index / 16) % 16;
104278
- const wx = chunk.x * 16 + lx, wy = chunk.y * 16 + ly, wz = chunk.z * 16 + lz;
104279
- const block = chunk.getBlockAt(neighbor.set(wx, wy, wz));
104280
- if (block.object?.fluidKind) {
104281
- await block.object.updateFluid((x4, y5, z4) => {
104282
- const nx = lx + x4, ny = ly + y5, nz = lz + z4;
104283
- const section = nx >= 0 && nx < 16 && ny >= 0 && ny < 16 && nz >= 0 && nz < 16 ? chunk : this._chunks.get(`${chunk.x + Math.floor(nx / 16)}_${chunk.y + Math.floor(ny / 16)}_${chunk.z + Math.floor(nz / 16)}`);
104284
- const neighborIndex = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
104285
- const byte = section?.fluidByteIndex(neighborIndex) ?? 0;
104286
- return {
104287
- fluid: fluidKindOf(byte),
104288
- level: byte & 15,
104289
- solid: section?.isOccludingIndex(neighborIndex) ?? false
104290
- };
104291
- });
104288
+ try {
104289
+ for (const [chunk, indices] of batch) {
104290
+ const neighbors = CUBE_FACE_OFFSETS.map(([x4, y5, z4]) => this._chunks.get(`${chunk.x + x4}_${chunk.y + y5}_${chunk.z + z4}`));
104291
+ const neighborAt = (x4, y5, z4) => {
104292
+ const lx = x4 - chunk.x * 16, ly = y5 - chunk.y * 16, lz = z4 - chunk.z * 16;
104293
+ const section = neighbors[lx >= 16 ? 0 : lx < 0 ? 1 : ly >= 16 ? 2 : ly < 0 ? 3 : lz >= 16 ? 4 : 5];
104294
+ return [section, (ly & 15) * 256 + (lz & 15) * 16 + (lx & 15)];
104295
+ };
104296
+ const masks = chunk.updateCullMasks(indices === "all" ? void 0 : indices, (x4, y5, z4) => {
104297
+ const [section, i4] = neighborAt(x4, y5, z4);
104298
+ return section?.isOccludingIndex(i4) ?? false;
104299
+ }, culling, (x4, y5, z4) => {
104300
+ const [section, i4] = neighborAt(x4, y5, z4);
104301
+ return section?.visibleBlockIndex(i4);
104302
+ });
104303
+ const fluidIndices = indices === "all" ? function* () {
104304
+ for (let index = 0; index < 4096; index++) yield index;
104305
+ }() : indices;
104306
+ for (const index of fluidIndices) {
104307
+ if (!chunk.fluidByteIndex(index) || !chunk.isBlockVisibleIndex(index)) continue;
104308
+ const lx = index % 16, ly = Math.floor(index / 256), lz = Math.floor(index / 16) % 16;
104309
+ const wx = chunk.x * 16 + lx, wy = chunk.y * 16 + ly, wz = chunk.z * 16 + lz;
104310
+ const block = chunk.getBlockAt(neighbor.set(wx, wy, wz));
104311
+ if (block.object?.fluidKind) {
104312
+ await block.object.updateFluid((x4, y5, z4) => {
104313
+ const nx = lx + x4, ny = ly + y5, nz = lz + z4;
104314
+ const section = nx >= 0 && nx < 16 && ny >= 0 && ny < 16 && nz >= 0 && nz < 16 ? chunk : this._chunks.get(`${chunk.x + Math.floor(nx / 16)}_${chunk.y + Math.floor(ny / 16)}_${chunk.z + Math.floor(nz / 16)}`);
104315
+ const neighborIndex = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
104316
+ const byte = section?.fluidByteIndex(neighborIndex) ?? 0;
104317
+ return {
104318
+ fluid: fluidKindOf(byte),
104319
+ level: byte & 15,
104320
+ solid: section?.isOccludingIndex(neighborIndex) ?? false
104321
+ };
104322
+ });
104323
+ }
104324
+ }
104325
+ for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
104326
+ await chunk.refreshLight(indices === "all" ? void 0 : indices);
104327
+ chunk.refreshAmbientOcclusion(this._ambientOcclusion);
104328
+ if ((this._ambientOcclusion || this.ambientOcclusionPending) && performance.now() - sliceStart > 8) {
104329
+ await yieldToEventLoop();
104330
+ sliceStart = performance.now();
104292
104331
  }
104293
104332
  }
104294
- for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
104295
- await chunk.refreshLight(indices === "all" ? void 0 : indices);
104296
- chunk.refreshAmbientOcclusion(this._ambientOcclusion);
104297
- if ((this._ambientOcclusion || this.ambientOcclusionPending) && performance.now() - sliceStart > 8) {
104298
- await yieldToEventLoop();
104299
- sliceStart = performance.now();
104333
+ const builds = await Promise.allSettled([...batch.keys()].map(async (chunk) => {
104334
+ await chunk.rebuildSectionMesh();
104335
+ batch.delete(chunk);
104336
+ }));
104337
+ const failure = builds.find((result) => result.status === "rejected");
104338
+ if (failure?.status === "rejected") throw failure.reason;
104339
+ } catch (error2) {
104340
+ for (const [chunk, indices] of batch) {
104341
+ const pending = this.pendingCulling.get(chunk);
104342
+ this.pendingCulling.set(chunk, indices === "all" || pending === "all" ? "all" : /* @__PURE__ */ new Set([...indices, ...pending ?? []]));
104300
104343
  }
104344
+ throw error2;
104301
104345
  }
104302
- await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
104303
104346
  }
104304
104347
  } finally {
104305
104348
  this.culling = void 0;
@@ -104316,7 +104359,7 @@ gl_FragDepth=readDepth(vUv);
104316
104359
  x4,
104317
104360
  y5,
104318
104361
  z4,
104319
- (positions) => this.updateCulling(positions),
104362
+ (positions, defer) => this.updateCulling(positions, defer),
104320
104363
  this.sectionModels,
104321
104364
  (x5, y6, z5) => {
104322
104365
  const cx = Math.floor(x5 / 16), cy = Math.floor(y6 / 16), cz = Math.floor(z5 / 16);
@@ -105065,11 +105108,14 @@ gl_FragDepth=readDepth(vUv);
105065
105108
  this.biomes = copy;
105066
105109
  return this.refreshBiomes();
105067
105110
  }
105068
- /** Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects. */
105069
- refreshBiomes(columns) {
105070
- return this.changeBlocks(() => this.refreshBiomeBlocks(columns), true);
105111
+ /**
105112
+ * Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects.
105113
+ * The owning world can defer neighbor culling until all affected chunks are refreshed.
105114
+ */
105115
+ refreshBiomes(columns, deferCulling = false) {
105116
+ return this.changeBlocks(() => this.refreshBiomeBlocks(columns, deferCulling), true);
105071
105117
  }
105072
- async refreshBiomeBlocks(columns) {
105118
+ async refreshBiomeBlocks(columns, deferCulling = false) {
105073
105119
  const blend = columns && this.biomeBlend?.();
105074
105120
  const ownColumn = columns?.some((column) => column.x === this.x && column.z === this.z);
105075
105121
  const affected = /* @__PURE__ */ new Map();
@@ -105126,7 +105172,7 @@ gl_FragDepth=readDepth(vUv);
105126
105172
  this.renderedBlocks.get(index)?.object?.setVisible(false);
105127
105173
  }
105128
105174
  const positions = blocks.map(({ index }) => new Vector3(...this.positionAt(index)));
105129
- if (this.onBlocksChanged) await this.onBlocksChanged(positions);
105175
+ if (this.onBlocksChanged) await this.onBlocksChanged(positions, deferCulling);
105130
105176
  else await this.rebuildSectionMesh();
105131
105177
  }
105132
105178
  }
@@ -105798,19 +105844,12 @@ gl_FragDepth=readDepth(vUv);
105798
105844
  this.loading?.controller.abort();
105799
105845
  return this.disposal ?? (this.disposal = Promise.resolve().then(async () => {
105800
105846
  await this.running?.catch(() => void 0);
105801
- let failure;
105802
- let failed = false;
105803
- for (const position of this.owned.values()) {
105804
- try {
105805
- await this.unload(position);
105806
- } catch (error2) {
105807
- failed = true;
105808
- failure ?? (failure = error2);
105809
- }
105847
+ try {
105848
+ await this.unloadMany([...this.owned.values()]);
105849
+ } finally {
105850
+ this.missing.clear();
105851
+ this.failed.clear();
105810
105852
  }
105811
- this.missing.clear();
105812
- this.failed.clear();
105813
- if (failed) throw failure;
105814
105853
  }).catch((error2) => {
105815
105854
  this.disposal = void 0;
105816
105855
  throw error2;
@@ -105825,9 +105864,9 @@ gl_FragDepth=readDepth(vUv);
105825
105864
  for (const [key, position2] of this.failed) {
105826
105865
  if (!this.within(position2, this.unloadRadius)) this.failed.delete(key);
105827
105866
  }
105828
- const obsolete = [...this.owned.values()].find((position2) => !this.loaded.has(this.key(position2)) || !this.within(position2, this.unloadRadius));
105829
- if (obsolete) {
105830
- await this.unload(obsolete);
105867
+ const obsolete = [...this.owned.values()].filter((position2) => !this.loaded.has(this.key(position2)) || !this.within(position2, this.unloadRadius));
105868
+ if (obsolete.length) {
105869
+ await this.unloadMany(obsolete);
105831
105870
  continue;
105832
105871
  }
105833
105872
  const position = this.desired.find((position2) => this.needsLoad(position2));
@@ -105857,7 +105896,7 @@ gl_FragDepth=readDepth(vUv);
105857
105896
  this.loaded.set(this.key(position), position);
105858
105897
  } catch (error2) {
105859
105898
  try {
105860
- await this.unload(position);
105899
+ await this.unloadMany([position]);
105861
105900
  } catch (cleanupError) {
105862
105901
  const failure = error2 instanceof Error ? error2 : new Error(String(error2));
105863
105902
  try {
@@ -105878,10 +105917,18 @@ gl_FragDepth=readDepth(vUv);
105878
105917
  const key = this.key(position);
105879
105918
  return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);
105880
105919
  }
105881
- async unload(position) {
105882
- await this.world.unloadChunkColumn(position.x, position.z);
105883
- this.owned.delete(this.key(position));
105884
- this.loaded.delete(this.key(position));
105920
+ async unloadMany(positions) {
105921
+ if (!positions.length) return;
105922
+ try {
105923
+ await this.world.unloadChunkColumns(positions.map(({ x: x4, z: z4 }) => ({ x: x4, z: z4 })));
105924
+ } catch (error2) {
105925
+ for (const position of positions) this.loaded.delete(this.key(position));
105926
+ throw error2;
105927
+ }
105928
+ for (const position of positions) {
105929
+ this.owned.delete(this.key(position));
105930
+ this.loaded.delete(this.key(position));
105931
+ }
105885
105932
  }
105886
105933
  validateChunk(chunk, position) {
105887
105934
  if (chunk.x !== position.x || chunk.z !== position.z) {