minerender 2.1.0-alpha.137 → 2.1.0-alpha.138

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.
@@ -18543,8 +18543,14 @@ var MineRenderWorld = class {
18543
18543
  }
18544
18544
  return queued;
18545
18545
  }
18546
- async refreshBiomes() {
18547
- if (this.biomeRefreshing) await this.biomeRefreshing;
18546
+ async refreshBiomes(deferCulling = false) {
18547
+ if (this.biomeRefreshing) {
18548
+ await this.biomeRefreshing;
18549
+ if (!deferCulling) {
18550
+ if (this.pendingCulling.size) await this.updateCulling([]);
18551
+ else await this.culling;
18552
+ }
18553
+ }
18548
18554
  if (!this.pendingBiomes.size) return /* @__PURE__ */ new Map();
18549
18555
  return this.biomeRefreshing ??= Promise.resolve().then(async () => {
18550
18556
  const failures = /* @__PURE__ */ new Map();
@@ -18556,7 +18562,7 @@ var MineRenderWorld = class {
18556
18562
  this.pendingBiomes.delete(chunk);
18557
18563
  if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) !== chunk) continue;
18558
18564
  try {
18559
- await chunk.refreshBiomes(columns);
18565
+ await chunk.refreshBiomes(columns, deferCulling);
18560
18566
  } catch (error) {
18561
18567
  failures.set(chunk, error);
18562
18568
  if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingBiomes.set(chunk, void 0);
@@ -18818,15 +18824,35 @@ var MineRenderWorld = class {
18818
18824
  * Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
18819
18825
  */
18820
18826
  async unloadChunkColumn(x, z) {
18821
- if (!Number.isInteger(x) || !Number.isInteger(z)) {
18822
- throw new RangeError("Chunk column coordinates must be integers");
18827
+ return this.unloadChunkColumns([{ x, z }]);
18828
+ }
18829
+ /**
18830
+ * Removes chunk columns together and refreshes surviving neighbors once.
18831
+ * Coordinates are absolute chunk columns. Duplicate and missing columns are ignored.
18832
+ */
18833
+ async unloadChunkColumns(positions) {
18834
+ const columns = /* @__PURE__ */ new Map();
18835
+ for (const position of positions) {
18836
+ const { x, z } = position ?? {};
18837
+ if (!Number.isInteger(x) || !Number.isInteger(z)) {
18838
+ throw new RangeError("Chunk column coordinates must be integers");
18839
+ }
18840
+ columns.set(`${x}_${z}`, { x, z });
18823
18841
  }
18824
- this.entities?.clearColumn(x, z);
18842
+ if (!columns.size) return;
18825
18843
  await this.culling;
18844
+ let sliceStart = performance.now();
18826
18845
  try {
18827
- await this.clearChunkColumn(x, z);
18846
+ for (const { x, z } of columns.values()) {
18847
+ this.entities?.clearColumn(x, z);
18848
+ await this.clearChunkColumn(x, z);
18849
+ if (performance.now() - sliceStart > 8) {
18850
+ await yieldToEventLoop();
18851
+ sliceStart = performance.now();
18852
+ }
18853
+ }
18828
18854
  } finally {
18829
- const failures = await this.refreshBiomes();
18855
+ const failures = await this.refreshBiomes(true);
18830
18856
  if (failures.size) log18.warn("Failed to refresh biome colors", failures.values().next().value);
18831
18857
  await this.updateCulling([]);
18832
18858
  }
@@ -18889,7 +18915,7 @@ var MineRenderWorld = class {
18889
18915
  }
18890
18916
  this.sectionModels?.clear();
18891
18917
  }
18892
- updateCulling(positions) {
18918
+ updateCulling(positions, defer = false) {
18893
18919
  const neighbor = new Vector319();
18894
18920
  for (const pos of positions) {
18895
18921
  const cx = Math.floor(pos.x / 16), cy = Math.floor(pos.y / 16), cz = Math.floor(pos.z / 16);
@@ -18911,58 +18937,72 @@ var MineRenderWorld = class {
18911
18937
  }
18912
18938
  }
18913
18939
  }
18940
+ if (defer) return Promise.resolve();
18914
18941
  return this.culling ??= Promise.resolve().then(async () => {
18915
18942
  let sliceStart = performance.now();
18916
18943
  try {
18917
18944
  const culling = await loadBlockCulling();
18918
18945
  while (this.pendingCulling.size) {
18919
- const batch = [...this.pendingCulling];
18946
+ const batch = new Map(this.pendingCulling);
18920
18947
  this.pendingCulling.clear();
18921
- for (const [chunk, indices] of batch) {
18922
- const neighbors = CUBE_FACE_OFFSETS.map(([x, y, z]) => this._chunks.get(`${chunk.x + x}_${chunk.y + y}_${chunk.z + z}`));
18923
- const neighborAt = (x, y, z) => {
18924
- const lx = x - chunk.x * 16, ly = y - chunk.y * 16, lz = z - chunk.z * 16;
18925
- const section = neighbors[lx >= 16 ? 0 : lx < 0 ? 1 : ly >= 16 ? 2 : ly < 0 ? 3 : lz >= 16 ? 4 : 5];
18926
- return [section, (ly & 15) * 256 + (lz & 15) * 16 + (lx & 15)];
18927
- };
18928
- const masks = chunk.updateCullMasks(indices === "all" ? void 0 : indices, (x, y, z) => {
18929
- const [section, i] = neighborAt(x, y, z);
18930
- return section?.isOccludingIndex(i) ?? false;
18931
- }, culling, (x, y, z) => {
18932
- const [section, i] = neighborAt(x, y, z);
18933
- return section?.visibleBlockIndex(i);
18934
- });
18935
- const fluidIndices = indices === "all" ? function* () {
18936
- for (let index = 0; index < 4096; index++) yield index;
18937
- }() : indices;
18938
- for (const index of fluidIndices) {
18939
- if (!chunk.fluidByteIndex(index) || !chunk.isBlockVisibleIndex(index)) continue;
18940
- const lx = index % 16, ly = Math.floor(index / 256), lz = Math.floor(index / 16) % 16;
18941
- const wx = chunk.x * 16 + lx, wy = chunk.y * 16 + ly, wz = chunk.z * 16 + lz;
18942
- const block = chunk.getBlockAt(neighbor.set(wx, wy, wz));
18943
- if (block.object?.fluidKind) {
18944
- await block.object.updateFluid((x, y, z) => {
18945
- const nx = lx + x, ny = ly + y, nz = lz + z;
18946
- 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)}`);
18947
- const neighborIndex = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
18948
- const byte = section?.fluidByteIndex(neighborIndex) ?? 0;
18949
- return {
18950
- fluid: fluidKindOf(byte),
18951
- level: byte & 15,
18952
- solid: section?.isOccludingIndex(neighborIndex) ?? false
18953
- };
18954
- });
18948
+ try {
18949
+ for (const [chunk, indices] of batch) {
18950
+ const neighbors = CUBE_FACE_OFFSETS.map(([x, y, z]) => this._chunks.get(`${chunk.x + x}_${chunk.y + y}_${chunk.z + z}`));
18951
+ const neighborAt = (x, y, z) => {
18952
+ const lx = x - chunk.x * 16, ly = y - chunk.y * 16, lz = z - chunk.z * 16;
18953
+ const section = neighbors[lx >= 16 ? 0 : lx < 0 ? 1 : ly >= 16 ? 2 : ly < 0 ? 3 : lz >= 16 ? 4 : 5];
18954
+ return [section, (ly & 15) * 256 + (lz & 15) * 16 + (lx & 15)];
18955
+ };
18956
+ const masks = chunk.updateCullMasks(indices === "all" ? void 0 : indices, (x, y, z) => {
18957
+ const [section, i] = neighborAt(x, y, z);
18958
+ return section?.isOccludingIndex(i) ?? false;
18959
+ }, culling, (x, y, z) => {
18960
+ const [section, i] = neighborAt(x, y, z);
18961
+ return section?.visibleBlockIndex(i);
18962
+ });
18963
+ const fluidIndices = indices === "all" ? function* () {
18964
+ for (let index = 0; index < 4096; index++) yield index;
18965
+ }() : indices;
18966
+ for (const index of fluidIndices) {
18967
+ if (!chunk.fluidByteIndex(index) || !chunk.isBlockVisibleIndex(index)) continue;
18968
+ const lx = index % 16, ly = Math.floor(index / 256), lz = Math.floor(index / 16) % 16;
18969
+ const wx = chunk.x * 16 + lx, wy = chunk.y * 16 + ly, wz = chunk.z * 16 + lz;
18970
+ const block = chunk.getBlockAt(neighbor.set(wx, wy, wz));
18971
+ if (block.object?.fluidKind) {
18972
+ await block.object.updateFluid((x, y, z) => {
18973
+ const nx = lx + x, ny = ly + y, nz = lz + z;
18974
+ 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)}`);
18975
+ const neighborIndex = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
18976
+ const byte = section?.fluidByteIndex(neighborIndex) ?? 0;
18977
+ return {
18978
+ fluid: fluidKindOf(byte),
18979
+ level: byte & 15,
18980
+ solid: section?.isOccludingIndex(neighborIndex) ?? false
18981
+ };
18982
+ });
18983
+ }
18984
+ }
18985
+ for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
18986
+ await chunk.refreshLight(indices === "all" ? void 0 : indices);
18987
+ chunk.refreshAmbientOcclusion(this._ambientOcclusion);
18988
+ if ((this._ambientOcclusion || this.ambientOcclusionPending) && performance.now() - sliceStart > 8) {
18989
+ await yieldToEventLoop();
18990
+ sliceStart = performance.now();
18955
18991
  }
18956
18992
  }
18957
- for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
18958
- await chunk.refreshLight(indices === "all" ? void 0 : indices);
18959
- chunk.refreshAmbientOcclusion(this._ambientOcclusion);
18960
- if ((this._ambientOcclusion || this.ambientOcclusionPending) && performance.now() - sliceStart > 8) {
18961
- await yieldToEventLoop();
18962
- sliceStart = performance.now();
18993
+ const builds = await Promise.allSettled([...batch.keys()].map(async (chunk) => {
18994
+ await chunk.rebuildSectionMesh();
18995
+ batch.delete(chunk);
18996
+ }));
18997
+ const failure = builds.find((result) => result.status === "rejected");
18998
+ if (failure?.status === "rejected") throw failure.reason;
18999
+ } catch (error) {
19000
+ for (const [chunk, indices] of batch) {
19001
+ const pending = this.pendingCulling.get(chunk);
19002
+ this.pendingCulling.set(chunk, indices === "all" || pending === "all" ? "all" : /* @__PURE__ */ new Set([...indices, ...pending ?? []]));
18963
19003
  }
19004
+ throw error;
18964
19005
  }
18965
- await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
18966
19006
  }
18967
19007
  } finally {
18968
19008
  this.culling = void 0;
@@ -18979,7 +19019,7 @@ var MineRenderWorld = class {
18979
19019
  x,
18980
19020
  y,
18981
19021
  z,
18982
- (positions) => this.updateCulling(positions),
19022
+ (positions, defer) => this.updateCulling(positions, defer),
18983
19023
  this.sectionModels,
18984
19024
  (x2, y2, z2) => {
18985
19025
  const cx = Math.floor(x2 / 16), cy = Math.floor(y2 / 16), cz = Math.floor(z2 / 16);
@@ -19714,11 +19754,14 @@ var Chunk = class _Chunk {
19714
19754
  this.biomes = copy;
19715
19755
  return this.refreshBiomes();
19716
19756
  }
19717
- /** Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects. */
19718
- refreshBiomes(columns) {
19719
- return this.changeBlocks(() => this.refreshBiomeBlocks(columns), true);
19757
+ /**
19758
+ * Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects.
19759
+ * The owning world can defer neighbor culling until all affected chunks are refreshed.
19760
+ */
19761
+ refreshBiomes(columns, deferCulling = false) {
19762
+ return this.changeBlocks(() => this.refreshBiomeBlocks(columns, deferCulling), true);
19720
19763
  }
19721
- async refreshBiomeBlocks(columns) {
19764
+ async refreshBiomeBlocks(columns, deferCulling = false) {
19722
19765
  const blend = columns && this.biomeBlend?.();
19723
19766
  const ownColumn = columns?.some((column) => column.x === this.x && column.z === this.z);
19724
19767
  const affected = /* @__PURE__ */ new Map();
@@ -19775,7 +19818,7 @@ var Chunk = class _Chunk {
19775
19818
  this.renderedBlocks.get(index)?.object?.setVisible(false);
19776
19819
  }
19777
19820
  const positions = blocks.map(({ index }) => new Vector320(...this.positionAt(index)));
19778
- if (this.onBlocksChanged) await this.onBlocksChanged(positions);
19821
+ if (this.onBlocksChanged) await this.onBlocksChanged(positions, deferCulling);
19779
19822
  else await this.rebuildSectionMesh();
19780
19823
  }
19781
19824
  }
@@ -20429,19 +20472,12 @@ var WorldStreamer = class {
20429
20472
  this.loading?.controller.abort();
20430
20473
  return this.disposal ??= Promise.resolve().then(async () => {
20431
20474
  await this.running?.catch(() => void 0);
20432
- let failure;
20433
- let failed = false;
20434
- for (const position of this.owned.values()) {
20435
- try {
20436
- await this.unload(position);
20437
- } catch (error) {
20438
- failed = true;
20439
- failure ??= error;
20440
- }
20475
+ try {
20476
+ await this.unloadMany([...this.owned.values()]);
20477
+ } finally {
20478
+ this.missing.clear();
20479
+ this.failed.clear();
20441
20480
  }
20442
- this.missing.clear();
20443
- this.failed.clear();
20444
- if (failed) throw failure;
20445
20481
  }).catch((error) => {
20446
20482
  this.disposal = void 0;
20447
20483
  throw error;
@@ -20456,9 +20492,9 @@ var WorldStreamer = class {
20456
20492
  for (const [key, position2] of this.failed) {
20457
20493
  if (!this.within(position2, this.unloadRadius)) this.failed.delete(key);
20458
20494
  }
20459
- const obsolete = [...this.owned.values()].find((position2) => !this.loaded.has(this.key(position2)) || !this.within(position2, this.unloadRadius));
20460
- if (obsolete) {
20461
- await this.unload(obsolete);
20495
+ const obsolete = [...this.owned.values()].filter((position2) => !this.loaded.has(this.key(position2)) || !this.within(position2, this.unloadRadius));
20496
+ if (obsolete.length) {
20497
+ await this.unloadMany(obsolete);
20462
20498
  continue;
20463
20499
  }
20464
20500
  const position = this.desired.find((position2) => this.needsLoad(position2));
@@ -20488,7 +20524,7 @@ var WorldStreamer = class {
20488
20524
  this.loaded.set(this.key(position), position);
20489
20525
  } catch (error) {
20490
20526
  try {
20491
- await this.unload(position);
20527
+ await this.unloadMany([position]);
20492
20528
  } catch (cleanupError) {
20493
20529
  const failure = error instanceof Error ? error : new Error(String(error));
20494
20530
  try {
@@ -20509,10 +20545,18 @@ var WorldStreamer = class {
20509
20545
  const key = this.key(position);
20510
20546
  return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);
20511
20547
  }
20512
- async unload(position) {
20513
- await this.world.unloadChunkColumn(position.x, position.z);
20514
- this.owned.delete(this.key(position));
20515
- this.loaded.delete(this.key(position));
20548
+ async unloadMany(positions) {
20549
+ if (!positions.length) return;
20550
+ try {
20551
+ await this.world.unloadChunkColumns(positions.map(({ x, z }) => ({ x, z })));
20552
+ } catch (error) {
20553
+ for (const position of positions) this.loaded.delete(this.key(position));
20554
+ throw error;
20555
+ }
20556
+ for (const position of positions) {
20557
+ this.owned.delete(this.key(position));
20558
+ this.loaded.delete(this.key(position));
20559
+ }
20516
20560
  }
20517
20561
  validateChunk(chunk, position) {
20518
20562
  if (chunk.x !== position.x || chunk.z !== position.z) {