minerender 2.1.0-alpha.142 → 2.1.0-alpha.144

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
@@ -34068,8 +34068,6 @@ void main() {
34068
34068
  });
34069
34069
  }
34070
34070
  function yieldToEventLoop() {
34071
- const scheduler = globalThis.scheduler;
34072
- if (scheduler?.yield) return scheduler.yield();
34073
34071
  return new Promise((resolve) => {
34074
34072
  if (typeof MessageChannel !== "undefined") {
34075
34073
  const channel = new MessageChannel();
@@ -103239,6 +103237,7 @@ gl_FragDepth=readDepth(vUv);
103239
103237
  init_dirname();
103240
103238
  init_buffer2();
103241
103239
  init_process2();
103240
+ init_BlockObject();
103242
103241
  init_three_module();
103243
103242
  init_BlockStates();
103244
103243
  init_AssetKey();
@@ -103252,6 +103251,7 @@ gl_FragDepth=readDepth(vUv);
103252
103251
  init_util();
103253
103252
  init_AssetKey();
103254
103253
  init_BlockStates();
103254
+ init_BlockEntities();
103255
103255
  init_CubeFace();
103256
103256
 
103257
103257
  // src/world/SectionModels.ts
@@ -103352,6 +103352,10 @@ gl_FragDepth=readDepth(vUv);
103352
103352
  biomeTint: source ? [...templates.values()].some((template) => template.biomeTint?.some((value) => value === 1)) || void 0 : void 0
103353
103353
  };
103354
103354
  }
103355
+ /** Starts loading the state's models and atlases so a later `prepareState` with the same properties finds them loaded. */
103356
+ async preload(blockState3, properties = {}) {
103357
+ await this.state(blockState3, properties);
103358
+ }
103355
103359
  async state(blockState3, properties) {
103356
103360
  let states = this.states.get(blockState3);
103357
103361
  if (!states) this.states.set(blockState3, states = /* @__PURE__ */ new Map());
@@ -103374,22 +103378,24 @@ gl_FragDepth=readDepth(vUv);
103374
103378
  async prepare(blockState3, properties) {
103375
103379
  const state = { ...await BlockStateResolver.defaults(blockState3), ...properties };
103376
103380
  const groups = BlockStateResolver.matching(blockState3, state);
103377
- if (!groups.length) return void 0;
103381
+ if (!groups.length || groups.some((group) => Array.isArray(group) && !group.length)) return void 0;
103382
+ const variants = groups.flatMap((group) => Array.isArray(group) ? group : [group]);
103383
+ if (variants.some((variant) => !variant.model)) return void 0;
103384
+ const settled = await Promise.allSettled(variants.map(async (variant) => {
103385
+ const modelKey = AssetKey.parse("models", variant.model);
103386
+ modelKey.root = blockState3.key?.root;
103387
+ const model = await Models.getMerged(modelKey);
103388
+ if (!model?.elements?.length) return void 0;
103389
+ const atlas = await UVMapper.getAtlas(model);
103390
+ const rotation = BlockStateResolver.rotation(variant);
103391
+ if (!atlas || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return void 0;
103392
+ return { model, atlas, rotation };
103393
+ }));
103378
103394
  const prepared = /* @__PURE__ */ new Map();
103379
- for (const group of groups) {
103380
- const choices = Array.isArray(group) ? group : [group];
103381
- if (!choices.length) return void 0;
103382
- for (const variant of choices) {
103383
- if (!variant.model) return void 0;
103384
- const modelKey = AssetKey.parse("models", variant.model);
103385
- modelKey.root = blockState3.key?.root;
103386
- const model = await Models.getMerged(modelKey);
103387
- if (!model?.elements?.length) return void 0;
103388
- const atlas = await UVMapper.getAtlas(model);
103389
- const rotation = BlockStateResolver.rotation(variant);
103390
- if (!atlas || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return void 0;
103391
- prepared.set(variant, { model, atlas, rotation });
103392
- }
103395
+ for (const [i4, result] of settled.entries()) {
103396
+ if (result.status === "rejected") throw result.reason;
103397
+ if (!result.value) return void 0;
103398
+ prepared.set(variants[i4], result.value);
103393
103399
  }
103394
103400
  return { state, groups, choices: prepared };
103395
103401
  }
@@ -103867,6 +103873,7 @@ gl_FragDepth=readDepth(vUv);
103867
103873
  // src/world/MineRenderWorld.ts
103868
103874
  init_log();
103869
103875
  var log21 = logger("MineRenderWorld");
103876
+ var YIELD_INTERVAL_MS = 8;
103870
103877
  var MineRenderWorld = class {
103871
103878
  constructor(scene, options = {}) {
103872
103879
  this._chunks = /* @__PURE__ */ new Map();
@@ -103980,7 +103987,7 @@ gl_FragDepth=readDepth(vUv);
103980
103987
  failures.set(chunk, error2);
103981
103988
  if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingBiomes.set(chunk, void 0);
103982
103989
  }
103983
- if (performance.now() - sliceStart > 8) {
103990
+ if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
103984
103991
  await yieldToEventLoop();
103985
103992
  sliceStart = performance.now();
103986
103993
  }
@@ -104081,15 +104088,18 @@ gl_FragDepth=readDepth(vUv);
104081
104088
  const key = AssetKey.parse("blockstates", block.type);
104082
104089
  keys.set(key.serialize(), key);
104083
104090
  }
104084
- await BlockStates.getAll(keys.values());
104091
+ const loaded = await BlockStates.getAll(keys.values());
104092
+ const states = new Map([...keys.keys()].map((key, i4) => [key, loaded[i4]]));
104085
104093
  if (!useBatches) {
104086
104094
  for (const block of multiblock.blocks) await place(block);
104087
104095
  return;
104088
104096
  }
104089
104097
  if (!executor) {
104090
104098
  const groups2 = /* @__PURE__ */ new Map();
104099
+ const distinct = /* @__PURE__ */ new Map();
104091
104100
  const position = new Vector3();
104092
104101
  for (const block of multiblock.blocks) {
104102
+ if (this.sectionModels && !Chunk.isAir(block)) distinct.set(ChunkData.paletteKey(block), block);
104093
104103
  const [x4, y5, z4] = block.position;
104094
104104
  this.validatePosBounds(position.set(x4, y5, z4));
104095
104105
  const cx = Math.floor(x4 / 16), cy = Math.floor(y5 / 16), cz = Math.floor(z4 / 16);
@@ -104100,6 +104110,16 @@ gl_FragDepth=readDepth(vUv);
104100
104110
  if (!group) groups2.set(chunk, group = []);
104101
104111
  group.push({ index: (y5 - cy * 16) * 256 + (z4 - cz * 16) * 16 + x4 - cx * 16, block });
104102
104112
  }
104113
+ if (this.sectionModels) {
104114
+ for (const block of distinct.values()) {
104115
+ const state = states.get(AssetKey.parse("blockstates", block.type).serialize());
104116
+ if (!state) continue;
104117
+ void (async () => {
104118
+ if (state.key && BlockEntities.entry(await BlockEntities.getIndex(state.key.root), state.key.toNamespacedString())) return;
104119
+ await this.sectionModels.preload(state, block.properties);
104120
+ })().catch(() => void 0);
104121
+ }
104122
+ }
104103
104123
  await this.placeChunkGroups(groups2, changes);
104104
104124
  return;
104105
104125
  }
@@ -104135,7 +104155,7 @@ gl_FragDepth=readDepth(vUv);
104135
104155
  )));
104136
104156
  for (const pos of positions) changes.set(`${pos.x},${pos.y},${pos.z}`, pos);
104137
104157
  }
104138
- if (performance.now() - sliceStart > 8) {
104158
+ if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
104139
104159
  await yieldToEventLoop();
104140
104160
  sliceStart = performance.now();
104141
104161
  }
@@ -104143,6 +104163,62 @@ gl_FragDepth=readDepth(vUv);
104143
104163
  }
104144
104164
  /** Replaces a chunk column's blocks, biomes, and saved light and, with `renderEntities`, its supported saved mobs. */
104145
104165
  async placeChunk(chunk, executor) {
104166
+ return this.placeColumn(chunk, executor);
104167
+ }
104168
+ /**
104169
+ * Replaces parsed columns in input order, then refreshes neighboring geometry once.
104170
+ * Duplicate coordinates retain their order. A failed column stops placement; queued refreshes finish before rejection.
104171
+ */
104172
+ async placeChunks(chunks) {
104173
+ const columns = [];
104174
+ for (const chunk of chunks) {
104175
+ const { x: x4, z: z4 } = chunk ?? {};
104176
+ if (![x4, z4].every(Number.isSafeInteger)) {
104177
+ throw new RangeError("Chunk column coordinates must be safe integers");
104178
+ }
104179
+ const sections = Array.from(chunk.sections, (section) => {
104180
+ const { y: y5 } = section ?? {};
104181
+ if (!Number.isSafeInteger(y5)) throw new RangeError("Chunk section coordinates must be safe integers");
104182
+ return { ...section, y: y5 };
104183
+ });
104184
+ columns.push({ ...chunk, x: x4, z: z4, sections });
104185
+ }
104186
+ if (!columns.length) return;
104187
+ await this.culling;
104188
+ let failed = false, failure, sliceStart = performance.now();
104189
+ try {
104190
+ for (const column of columns) {
104191
+ await this.placeColumn(column, void 0, true);
104192
+ if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
104193
+ await yieldToEventLoop();
104194
+ sliceStart = performance.now();
104195
+ }
104196
+ }
104197
+ } catch (error2) {
104198
+ failed = true;
104199
+ failure = error2;
104200
+ } finally {
104201
+ try {
104202
+ const failures = await this.refreshBiomes(true);
104203
+ if (failures.size) log21.warn("Failed to refresh biome colors", failures.values().next().value);
104204
+ } catch (error2) {
104205
+ if (!failed) {
104206
+ failed = true;
104207
+ failure = error2;
104208
+ }
104209
+ }
104210
+ try {
104211
+ await this.updateCulling([]);
104212
+ } catch (error2) {
104213
+ if (!failed) {
104214
+ failed = true;
104215
+ failure = error2;
104216
+ }
104217
+ }
104218
+ }
104219
+ if (failed) throw failure;
104220
+ }
104221
+ async placeColumn(chunk, executor, deferCulling = false) {
104146
104222
  if (![chunk.x, chunk.z].every(Number.isSafeInteger)) {
104147
104223
  throw new RangeError("Chunk column coordinates must be safe integers");
104148
104224
  }
@@ -104204,7 +104280,7 @@ gl_FragDepth=readDepth(vUv);
104204
104280
  } finally {
104205
104281
  this.markSectionChanged(target);
104206
104282
  }
104207
- if (performance.now() - sliceStart > 8) {
104283
+ if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
104208
104284
  await yieldToEventLoop();
104209
104285
  sliceStart = performance.now();
104210
104286
  }
@@ -104227,9 +104303,12 @@ gl_FragDepth=readDepth(vUv);
104227
104303
  await placeEntities?.();
104228
104304
  } finally {
104229
104305
  this.markBiomesChanged(chunk.x, chunk.z, void 0, false);
104230
- const failures = await this.refreshBiomes();
104231
- if (failures.size) log21.warn("Failed to refresh biome colors", failures.values().next().value);
104232
- await this.updateCulling([...changes.values()]);
104306
+ if (deferCulling) await this.updateCulling([...changes.values()], true);
104307
+ else {
104308
+ const failures = await this.refreshBiomes();
104309
+ if (failures.size) log21.warn("Failed to refresh biome colors", failures.values().next().value);
104310
+ await this.updateCulling([...changes.values()]);
104311
+ }
104233
104312
  }
104234
104313
  }
104235
104314
  /**
@@ -104259,7 +104338,7 @@ gl_FragDepth=readDepth(vUv);
104259
104338
  for (const { x: x4, z: z4 } of columns.values()) {
104260
104339
  this.entities?.clearColumn(x4, z4);
104261
104340
  await this.clearChunkColumn(x4, z4);
104262
- if (performance.now() - sliceStart > 8) {
104341
+ if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
104263
104342
  await yieldToEventLoop();
104264
104343
  sliceStart = performance.now();
104265
104344
  }
@@ -104398,15 +104477,25 @@ gl_FragDepth=readDepth(vUv);
104398
104477
  for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
104399
104478
  await chunk.refreshLight(indices === "all" ? void 0 : indices);
104400
104479
  chunk.refreshAmbientOcclusion(this._ambientOcclusion);
104401
- if ((this._ambientOcclusion || this.ambientOcclusionPending) && performance.now() - sliceStart > 8) {
104480
+ if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
104402
104481
  await yieldToEventLoop();
104403
104482
  sliceStart = performance.now();
104404
104483
  }
104405
104484
  }
104406
- const builds = await Promise.allSettled([...batch.keys()].map(async (chunk) => {
104407
- await chunk.rebuildSectionMesh();
104408
- batch.delete(chunk);
104409
- }));
104485
+ const pending = [];
104486
+ for (const chunk of [...batch.keys()]) {
104487
+ const build = (async () => {
104488
+ await chunk.rebuildSectionMesh();
104489
+ batch.delete(chunk);
104490
+ })();
104491
+ void build.catch(() => void 0);
104492
+ pending.push(build);
104493
+ if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
104494
+ await yieldToEventLoop();
104495
+ sliceStart = performance.now();
104496
+ }
104497
+ }
104498
+ const builds = await Promise.allSettled(pending);
104410
104499
  const failure = builds.find((result) => result.status === "rejected");
104411
104500
  if (failure?.status === "rejected") throw failure.reason;
104412
104501
  } catch (error2) {
@@ -105372,8 +105461,10 @@ gl_FragDepth=readDepth(vUv);
105372
105461
  if (rejected?.status === "rejected") throw rejected.reason;
105373
105462
  }
105374
105463
  const positions = [];
105375
- let failed = false, failure;
105376
- for (const { index, key, block } of placements) {
105464
+ const jobs = /* @__PURE__ */ new Map();
105465
+ let failureOrder = Infinity, failure;
105466
+ for (const [order, { index, key, block }] of placements.entries()) {
105467
+ jobs.delete(index);
105377
105468
  this.hiddenBlocks.delete(index);
105378
105469
  this.data.assign(index, this.data.intern(block), block?.nbt);
105379
105470
  const readBlock = this.data.snapshot(index);
@@ -105389,7 +105480,6 @@ gl_FragDepth=readDepth(vUv);
105389
105480
  this.z * 16 + Math.floor(index / 16) % 16
105390
105481
  );
105391
105482
  positions.push(worldPos);
105392
- let object4;
105393
105483
  try {
105394
105484
  if (!readBlock) continue;
105395
105485
  const resolved = resolutions.get(key);
@@ -105411,7 +105501,7 @@ gl_FragDepth=readDepth(vUv);
105411
105501
  this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0, biomeTint: context?.tint })));
105412
105502
  this.meshDirty = true;
105413
105503
  } else {
105414
- object4 = await this.scene.addBlock(blockState3, {
105504
+ jobs.set(index, { order, readBlock, blockState: blockState3, position: MineRenderWorld.worldToScenePosition(worldPos), options: {
105415
105505
  mergeMeshes: true,
105416
105506
  instanceMeshes: true,
105417
105507
  maxInstanceCount: 2e3,
@@ -105419,13 +105509,12 @@ gl_FragDepth=readDepth(vUv);
105419
105509
  variantPosition,
105420
105510
  biome: context?.biome,
105421
105511
  biomePosition: this.biomePosition(index, blockState3, stored)
105422
- });
105423
- object4.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
105512
+ } });
105513
+ continue;
105424
105514
  }
105425
105515
  this.renderedBlocks.set(index, { get block() {
105426
105516
  return readBlock();
105427
- }, object: object4 });
105428
- if (object4) this.objectBlocks.add(index);
105517
+ }, object: void 0 });
105429
105518
  } catch (error2) {
105430
105519
  if (preserveDataOnFailure && readBlock) {
105431
105520
  this.renderedBlocks.set(index, { get block() {
@@ -105433,12 +105522,35 @@ gl_FragDepth=readDepth(vUv);
105433
105522
  }, object: void 0 });
105434
105523
  } else this.data.assign(index, 0);
105435
105524
  this.fluidCells[index] = 0;
105436
- object4?.removeFromScene();
105437
- if (!failed) failure = error2;
105438
- failed = true;
105525
+ if (order < failureOrder) {
105526
+ failureOrder = order;
105527
+ failure = error2;
105528
+ }
105439
105529
  }
105440
105530
  }
105441
- if (failed) throw failure;
105531
+ const pending = [...jobs];
105532
+ const objects = await Promise.allSettled(pending.map(([, job]) => this.createBlockObject(job.blockState, job.position, job.options)));
105533
+ for (const [i4, [index, { order, readBlock }]] of pending.entries()) {
105534
+ const result = objects[i4];
105535
+ if (result.status === "fulfilled") {
105536
+ this.renderedBlocks.set(index, { get block() {
105537
+ return readBlock();
105538
+ }, object: result.value });
105539
+ this.objectBlocks.add(index);
105540
+ } else {
105541
+ if (preserveDataOnFailure) {
105542
+ this.renderedBlocks.set(index, { get block() {
105543
+ return readBlock();
105544
+ }, object: void 0 });
105545
+ } else this.data.assign(index, 0);
105546
+ this.fluidCells[index] = 0;
105547
+ if (order < failureOrder) {
105548
+ failureOrder = order;
105549
+ failure = result.reason;
105550
+ }
105551
+ }
105552
+ }
105553
+ if (failureOrder < Infinity) throw failure;
105442
105554
  return positions;
105443
105555
  }
105444
105556
  /** Replaces every cell from parsed section data. The caller refreshes neighbor culling. */
@@ -105509,37 +105621,42 @@ gl_FragDepth=readDepth(vUv);
105509
105621
  failed = true;
105510
105622
  }
105511
105623
  }
105512
- for (const { index, blockState: blockState3, biome } of perBlocks) {
105513
- let object4;
105514
- try {
105515
- object4 = await this.scene.addBlock(blockState3, {
105516
- mergeMeshes: true,
105517
- instanceMeshes: true,
105518
- maxInstanceCount: 2e3,
105519
- initialState: this.data.stateAt(this.data.idAt(index)).properties,
105520
- variantPosition: this.positionAt(index),
105521
- biome,
105522
- biomePosition: this.biomePosition(index, blockState3, this.data.stateAt(this.data.idAt(index)))
105523
- });
105524
- object4.setPosition(new Vector3(
105525
- this.x * 256 + index % 16 * 16,
105526
- this.y * 256 + Math.floor(index / 256) * 16,
105527
- this.z * 256 + Math.floor(index / 16) % 16 * 16
105528
- ));
105529
- this.renderedBlocks.get(index).object = object4;
105624
+ const objects = await Promise.allSettled(perBlocks.map(({ index, blockState: blockState3, biome }) => this.createBlockObject(blockState3, new Vector3(
105625
+ this.x * 256 + index % 16 * 16,
105626
+ this.y * 256 + Math.floor(index / 256) * 16,
105627
+ this.z * 256 + Math.floor(index / 16) % 16 * 16
105628
+ ), {
105629
+ mergeMeshes: true,
105630
+ instanceMeshes: true,
105631
+ maxInstanceCount: 2e3,
105632
+ initialState: this.data.stateAt(this.data.idAt(index)).properties,
105633
+ variantPosition: this.positionAt(index),
105634
+ biome,
105635
+ biomePosition: this.biomePosition(index, blockState3, this.data.stateAt(this.data.idAt(index)))
105636
+ })));
105637
+ for (const [i4, { index }] of perBlocks.entries()) {
105638
+ const result = objects[i4];
105639
+ if (result.status === "fulfilled") {
105640
+ this.renderedBlocks.get(index).object = result.value;
105530
105641
  this.objectBlocks.add(index);
105531
- } catch (error2) {
105642
+ } else {
105532
105643
  this.data.assign(index, 0);
105533
105644
  this.fluidCells[index] = 0;
105534
105645
  this.renderedBlocks.delete(index);
105535
105646
  this.objectBlocks.delete(index);
105536
- object4?.removeFromScene();
105537
- if (!failed) failure = error2;
105647
+ if (!failed) failure = result.reason;
105538
105648
  failed = true;
105539
105649
  }
105540
105650
  }
105541
105651
  if (failed) throw failure;
105542
105652
  }
105653
+ createBlockObject(blockState3, position, options) {
105654
+ return this.scene.addSceneObject(blockState3, () => {
105655
+ const object4 = new BlockObject(blockState3, options);
105656
+ object4.position.copy(position);
105657
+ return object4;
105658
+ }, options);
105659
+ }
105543
105660
  positionAt(index) {
105544
105661
  return [this.x * 16 + index % 16, this.y * 16 + Math.floor(index / 256), this.z * 16 + Math.floor(index / 16) % 16];
105545
105662
  }
@@ -105898,6 +106015,7 @@ gl_FragDepth=readDepth(vUv);
105898
106015
  init_dirname();
105899
106016
  init_buffer2();
105900
106017
  init_process2();
106018
+ var MAX_PREFETCHED_COLUMNS = 4;
105901
106019
  var WorldStreamer = class {
105902
106020
  constructor(world, source, options = {}) {
105903
106021
  this.world = world;
@@ -105907,6 +106025,7 @@ gl_FragDepth=readDepth(vUv);
105907
106025
  this.missing = /* @__PURE__ */ new Map();
105908
106026
  this.failed = /* @__PURE__ */ new Map();
105909
106027
  this.desired = [];
106028
+ this.reads = /* @__PURE__ */ new Map();
105910
106029
  this.disposed = false;
105911
106030
  this.loadRadius = options.loadRadius ?? 2;
105912
106031
  this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);
@@ -105951,6 +106070,12 @@ gl_FragDepth=readDepth(vUv);
105951
106070
  if (this.loading && !this.within(this.loading.position, this.loadRadius)) {
105952
106071
  this.loading.controller.abort();
105953
106072
  }
106073
+ for (const [key, read] of this.reads) {
106074
+ if (!this.within(read.position, this.loadRadius)) {
106075
+ read.controller.abort();
106076
+ if (read.ready) this.reads.delete(key);
106077
+ }
106078
+ }
105954
106079
  }
105955
106080
  return this.running ?? (this.running = Promise.resolve().then(() => this.drain()));
105956
106081
  }
@@ -105971,8 +106096,11 @@ gl_FragDepth=readDepth(vUv);
105971
106096
  this.desired = [];
105972
106097
  this.center = void 0;
105973
106098
  this.loading?.controller.abort();
106099
+ for (const read of this.reads.values()) read.controller.abort();
105974
106100
  return this.disposal ?? (this.disposal = Promise.resolve().then(async () => {
105975
106101
  await this.running?.catch(() => void 0);
106102
+ await Promise.all([...this.reads.values()].map((read) => read.promise));
106103
+ this.reads.clear();
105976
106104
  try {
105977
106105
  await this.unloadMany([...this.owned.values()]);
105978
106106
  } finally {
@@ -105998,6 +106126,10 @@ gl_FragDepth=readDepth(vUv);
105998
106126
  await this.unloadMany(obsolete);
105999
106127
  continue;
106000
106128
  }
106129
+ if (this.world.placeChunks) {
106130
+ if (await this.placeReadyChunks()) continue;
106131
+ return;
106132
+ }
106001
106133
  const position = this.desired.find((position2) => this.needsLoad(position2));
106002
106134
  if (!position) return;
106003
106135
  let chunk;
@@ -106020,26 +106152,105 @@ gl_FragDepth=readDepth(vUv);
106020
106152
  continue;
106021
106153
  }
106022
106154
  this.owned.set(this.key(position), position);
106155
+ await this.placeOwnedColumns([position], () => this.world.placeChunk(chunk));
106156
+ }
106157
+ } finally {
106158
+ for (const [key, read] of this.reads) {
106159
+ read.controller.abort();
106160
+ if (read.ready) this.reads.delete(key);
106161
+ }
106162
+ this.running = void 0;
106163
+ }
106164
+ }
106165
+ fillReadSlots() {
106166
+ while (!this.disposed && this.reads.size < MAX_PREFETCHED_COLUMNS) {
106167
+ const position = this.desired.find((position2) => this.needsLoad(position2) && !this.reads.has(this.key(position2)));
106168
+ if (!position) return;
106169
+ const key = this.key(position);
106170
+ const read = {
106171
+ position,
106172
+ controller: new AbortController(),
106173
+ promise: Promise.resolve(),
106174
+ ready: false,
106175
+ failed: false
106176
+ };
106177
+ this.reads.set(key, read);
106178
+ read.promise = (async () => {
106023
106179
  try {
106024
- await this.world.placeChunk(chunk);
106025
- this.loaded.set(this.key(position), position);
106180
+ read.chunk = await this.source.getChunk(position.x, position.z, read.controller.signal);
106181
+ if (read.chunk) this.validateChunk(read.chunk, position);
106026
106182
  } catch (error2) {
106027
- try {
106028
- await this.unloadMany([position]);
106029
- } catch (cleanupError) {
106030
- const failure = error2 instanceof Error ? error2 : new Error(String(error2));
106031
- try {
106032
- Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
106033
- } catch {
106034
- throw Object.assign(new Error(failure.message), { cause: cleanupError });
106035
- }
106036
- throw failure;
106183
+ read.failed = true;
106184
+ read.error = error2;
106185
+ } finally {
106186
+ read.ready = true;
106187
+ if (read.controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) {
106188
+ this.reads.delete(key);
106037
106189
  }
106038
- throw error2;
106190
+ }
106191
+ })();
106192
+ }
106193
+ }
106194
+ /** Places only the ready prefix, so later source reads cannot delay the nearest ready column. */
106195
+ async placeReadyChunks() {
106196
+ this.fillReadSlots();
106197
+ const position = this.desired.find((position2) => this.needsLoad(position2));
106198
+ if (!position) {
106199
+ if (!this.reads.size) return false;
106200
+ await Promise.all([...this.reads.values()].map((read) => read.promise));
106201
+ return true;
106202
+ }
106203
+ const head = this.reads.get(this.key(position));
106204
+ if (!head) {
106205
+ for (let index = this.desired.length - 1; index >= 0; index--) {
106206
+ const key = this.key(this.desired[index]);
106207
+ if (this.reads.get(key)?.ready) {
106208
+ this.reads.delete(key);
106209
+ return true;
106039
106210
  }
106040
106211
  }
106041
- } finally {
106042
- this.running = void 0;
106212
+ await Promise.race([...this.reads.values()].map((read) => read.promise));
106213
+ return true;
106214
+ }
106215
+ if (!head.ready) {
106216
+ await head.promise;
106217
+ return true;
106218
+ }
106219
+ const chunks = [], positions = [];
106220
+ for (const desired of this.desired) {
106221
+ if (!this.needsLoad(desired)) continue;
106222
+ const key = this.key(desired), read = this.reads.get(key);
106223
+ if (!read?.ready) break;
106224
+ this.reads.delete(key);
106225
+ if (read.failed) this.failed.set(key, { ...desired, error: read.error });
106226
+ else if (!read.chunk) this.missing.set(key, desired);
106227
+ else {
106228
+ chunks.push(read.chunk);
106229
+ positions.push(desired);
106230
+ }
106231
+ }
106232
+ if (!chunks.length) return true;
106233
+ for (const position2 of positions) this.owned.set(this.key(position2), position2);
106234
+ await this.placeOwnedColumns(positions, () => this.world.placeChunks(chunks));
106235
+ return true;
106236
+ }
106237
+ async placeOwnedColumns(positions, place) {
106238
+ try {
106239
+ await place();
106240
+ for (const position of positions) this.loaded.set(this.key(position), position);
106241
+ } catch (error2) {
106242
+ try {
106243
+ await this.unloadMany(positions);
106244
+ } catch (cleanupError) {
106245
+ const failure = error2 instanceof Error ? error2 : new Error(String(error2));
106246
+ try {
106247
+ Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
106248
+ } catch {
106249
+ throw Object.assign(new Error(failure.message), { cause: cleanupError });
106250
+ }
106251
+ throw failure;
106252
+ }
106253
+ throw error2;
106043
106254
  }
106044
106255
  }
106045
106256
  needsLoad(position) {