minerender 2.1.0-alpha.85 → 2.1.0-alpha.87

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
@@ -106478,6 +106478,7 @@ ${shader.fragmentShader}`.replace(
106478
106478
  __export(index_browser_exports, {
106479
106479
  AXES: () => AXES,
106480
106480
  AnvilParser: () => AnvilParser,
106481
+ AnvilWorldSource: () => AnvilWorldSource,
106481
106482
  ArchiveAssetSource: () => ArchiveAssetSource,
106482
106483
  AssetKey: () => AssetKey,
106483
106484
  AssetLoadError: () => AssetLoadError,
@@ -106580,6 +106581,8 @@ ${shader.fragmentShader}`.replace(
106580
106581
  Ticker: () => Ticker,
106581
106582
  TintColors: () => TintColors,
106582
106583
  UVMapper: () => UVMapper,
106584
+ VideoExporter: () => VideoExporter,
106585
+ WorldStreamer: () => WorldStreamer,
106583
106586
  WrappedImage: () => WrappedImage,
106584
106587
  addBox3WireframeToObject: () => addBox3WireframeToObject,
106585
106588
  addWireframeToMesh: () => addWireframeToMesh,
@@ -110099,6 +110102,134 @@ property list uchar int vertex_index
110099
110102
  return new Texture().copy(copySource);
110100
110103
  }
110101
110104
 
110105
+ // src/export/VideoExporter.ts
110106
+ init_dirname();
110107
+ init_buffer2();
110108
+ init_process2();
110109
+ var VideoExporter = class {
110110
+ constructor(source, options) {
110111
+ this.source = source;
110112
+ this.chunks = [];
110113
+ this.settled = false;
110114
+ this.stopping = false;
110115
+ this.onStart = () => {
110116
+ this.timer = setTimeout(() => {
110117
+ this.stopping = true;
110118
+ try {
110119
+ this.recorder.stop();
110120
+ } catch (error2) {
110121
+ this.cancel(error2);
110122
+ }
110123
+ }, this.duration * 1e3);
110124
+ };
110125
+ this.onData = (event) => {
110126
+ if (event.data.size) this.chunks.push(event.data);
110127
+ };
110128
+ this.onStop = () => {
110129
+ if (!this.stopping) return this.cancel(new Error("Video recording stopped before its duration elapsed"));
110130
+ if (!this.chunks.length) return this.cancel(new Error("The browser produced no video data"));
110131
+ const mimeType = this.chunks.find((chunk) => chunk.type)?.type || this.recorder.mimeType;
110132
+ const blob = new Blob(this.chunks, { type: mimeType });
110133
+ this.settled = true;
110134
+ this.cleanup();
110135
+ this.resolve(blob);
110136
+ };
110137
+ this.onError = (event) => {
110138
+ this.cancel(event.error ?? new Error("Video recording failed"));
110139
+ };
110140
+ this.onAbort = () => {
110141
+ this.cancel(this.signal.reason);
110142
+ };
110143
+ this.onTrackEnded = () => {
110144
+ this.cancel(new Error("The video capture stream stopped producing frames"));
110145
+ };
110146
+ const fps = options.fps ?? 30;
110147
+ if (!Number.isFinite(options.duration) || options.duration <= 0 || options.duration * 1e3 > 2147483647) {
110148
+ throw new RangeError("Video duration must be greater than zero and at most 2147483.647 seconds");
110149
+ }
110150
+ if (!Number.isFinite(fps) || fps <= 0) throw new RangeError("Video fps must be a finite positive number");
110151
+ const bitrate = options.videoBitsPerSecond;
110152
+ if (bitrate !== void 0 && (!Number.isInteger(bitrate) || bitrate <= 0 || bitrate > 4294967295)) {
110153
+ throw new RangeError("Video bitrate must be a positive unsigned 32-bit integer");
110154
+ }
110155
+ if (!source.width || !source.height) throw new Error("Cannot export video from an empty canvas");
110156
+ options.signal?.throwIfAborted();
110157
+ if (typeof document === "undefined" || typeof MediaRecorder === "undefined" || typeof MediaRecorder.isTypeSupported !== "function") {
110158
+ throw new Error("Video export requires a browser with MediaRecorder support");
110159
+ }
110160
+ const mimeTypes2 = options.mimeType === void 0 ? ["video/webm;codecs=vp9", "video/webm;codecs=vp8", "video/webm", "video/mp4"] : [options.mimeType];
110161
+ const mimeType = mimeTypes2.find((type) => type.startsWith("video/") && MediaRecorder.isTypeSupported(type));
110162
+ if (!mimeType) throw new Error("The browser does not support the requested video format");
110163
+ this.canvas = document.createElement("canvas");
110164
+ this.canvas.width = source.width;
110165
+ this.canvas.height = source.height;
110166
+ if (typeof this.canvas.captureStream !== "function") throw new Error("Video export requires canvas captureStream support");
110167
+ const context = this.canvas.getContext("2d", { alpha: false });
110168
+ if (!context) throw new Error("Could not create a canvas for video export");
110169
+ this.context = context;
110170
+ this.signal = options.signal;
110171
+ this.duration = options.duration;
110172
+ this.result = new Promise((resolve, reject) => {
110173
+ this.resolve = resolve;
110174
+ this.reject = reject;
110175
+ });
110176
+ try {
110177
+ this.stream = this.canvas.captureStream(fps);
110178
+ this.recorder = new MediaRecorder(this.stream, { mimeType, videoBitsPerSecond: bitrate });
110179
+ this.recorder.addEventListener("start", this.onStart);
110180
+ this.recorder.addEventListener("dataavailable", this.onData);
110181
+ this.recorder.addEventListener("stop", this.onStop);
110182
+ this.recorder.addEventListener("error", this.onError);
110183
+ for (const track of this.stream.getTracks()) {
110184
+ track.addEventListener("ended", this.onTrackEnded);
110185
+ }
110186
+ this.signal?.addEventListener("abort", this.onAbort, { once: true });
110187
+ this.recorder.start();
110188
+ } catch (error2) {
110189
+ this.cancel(error2);
110190
+ }
110191
+ }
110192
+ /** Copies a freshly drawn source frame before WebGL can clear its drawing buffer. */
110193
+ capture() {
110194
+ if (this.settled || this.stopping) return;
110195
+ try {
110196
+ this.context.fillStyle = "#000000";
110197
+ this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
110198
+ this.context.drawImage(this.source, 0, 0, this.canvas.width, this.canvas.height);
110199
+ } catch (error2) {
110200
+ this.cancel(error2);
110201
+ }
110202
+ }
110203
+ /** Cancels recording, releases its capture stream, and rejects {@link result}. */
110204
+ cancel(reason = new DOMException("Video export cancelled", "AbortError")) {
110205
+ if (this.settled) return;
110206
+ this.settled = true;
110207
+ this.cleanup();
110208
+ this.reject(reason);
110209
+ }
110210
+ cleanup() {
110211
+ if (this.timer !== void 0) clearTimeout(this.timer);
110212
+ this.signal?.removeEventListener("abort", this.onAbort);
110213
+ if (this.recorder) {
110214
+ this.recorder.removeEventListener("start", this.onStart);
110215
+ this.recorder.removeEventListener("dataavailable", this.onData);
110216
+ this.recorder.removeEventListener("stop", this.onStop);
110217
+ this.recorder.removeEventListener("error", this.onError);
110218
+ if (this.recorder.state !== "inactive") {
110219
+ try {
110220
+ this.recorder.stop();
110221
+ } catch {
110222
+ }
110223
+ }
110224
+ }
110225
+ for (const track of this.stream?.getTracks() ?? []) {
110226
+ track.removeEventListener("ended", this.onTrackEnded);
110227
+ track.stop();
110228
+ }
110229
+ this.chunks.length = 0;
110230
+ }
110231
+ };
110232
+
110102
110233
  // src/gui/index.ts
110103
110234
  init_dirname();
110104
110235
  init_buffer2();
@@ -117544,6 +117675,9 @@ gl_FragDepth=readDepth(vUv);
117544
117675
  this._inAnimationLoop = true;
117545
117676
  try {
117546
117677
  this.animate(time);
117678
+ } catch (error2) {
117679
+ this._videoExporter?.cancel(error2);
117680
+ throw error2;
117547
117681
  } finally {
117548
117682
  this._inAnimationLoop = false;
117549
117683
  }
@@ -117738,16 +117872,18 @@ gl_FragDepth=readDepth(vUv);
117738
117872
  if (this._frameCallbacks.get(callback) === subscription) this._frameCallbacks.delete(callback);
117739
117873
  };
117740
117874
  }
117741
- /** Starts or resumes the animation loop and registered frame callbacks. */
117875
+ /** Starts or resumes the animation loop and registered frame callbacks. Has no effect while running. */
117742
117876
  start() {
117743
- if (this._disposed) return;
117877
+ if (this._disposed || this._running) return;
117744
117878
  this.stop();
117745
117879
  this._running = true;
117746
117880
  if (!this._inAnimationLoop) this.renderer.setAnimationLoop(this._animationLoop);
117747
117881
  }
117748
- /** Pauses rendering and frame callbacks. Call {@link start} to resume. */
117882
+ /** Pauses rendering and frame callbacks, and cancels video export. Call {@link start} to resume. */
117749
117883
  stop() {
117750
117884
  if (this._disposed) return;
117885
+ this._videoExporter?.cancel();
117886
+ this._videoExporter = void 0;
117751
117887
  this._running = false;
117752
117888
  if (this._inAnimationLoop) {
117753
117889
  queueMicrotask(() => {
@@ -117809,7 +117945,7 @@ gl_FragDepth=readDepth(vUv);
117809
117945
  this._controls.update();
117810
117946
  }
117811
117947
  if (!this._running) return;
117812
- if (!this.dirty && !this.options.render.renderAlways && !this._frameCallbacks.size) return;
117948
+ if (!this.dirty && !this.options.render.renderAlways && !this._frameCallbacks.size && !this._videoExporter) return;
117813
117949
  const interval = this._frameInterval;
117814
117950
  if (interval) {
117815
117951
  const next = this._nextFrameTime;
@@ -117834,6 +117970,7 @@ gl_FragDepth=readDepth(vUv);
117834
117970
  } else {
117835
117971
  this.renderer.render(this.scene, this.camera);
117836
117972
  }
117973
+ this._videoExporter?.capture();
117837
117974
  this.dirty = false;
117838
117975
  if (this._stats) {
117839
117976
  this._stats.end();
@@ -117854,6 +117991,35 @@ gl_FragDepth=readDepth(vUv);
117854
117991
  const canvas = trim ? trimCanvas(this.renderer.domElement) : this.renderer.domElement;
117855
117992
  return canvas.toDataURL(mime, quality);
117856
117993
  }
117994
+ /**
117995
+ * Records a silent browser video in real time, including frame callbacks and animated textures.
117996
+ * Keep the tab visible; browser scheduling and `render.fpsLimit` limit the capture rate.
117997
+ * The video keeps the initial drawing-buffer size and fills transparent pixels with black.
117998
+ * Temporarily starts a stopped renderer. Stop, disposal, or `options.signal` cancels the export.
117999
+ * Only one export can run at a time. Use the returned Blob's type to select a file extension.
118000
+ */
118001
+ async toVideo(options) {
118002
+ if (this._disposed) throw new Error("Cannot export a video from a disposed renderer");
118003
+ if (this._videoExporter) throw new Error("A video export is already running");
118004
+ const exporter = new VideoExporter(this.renderer.domElement, options);
118005
+ const wasRunning = this._running;
118006
+ try {
118007
+ try {
118008
+ if (!wasRunning) this.start();
118009
+ this._videoExporter = exporter;
118010
+ if (this._controls?.enabled) this._controls.update();
118011
+ this.drawFrame();
118012
+ } catch (error2) {
118013
+ exporter.cancel(error2);
118014
+ }
118015
+ return await exporter.result;
118016
+ } finally {
118017
+ if (this._videoExporter === exporter) {
118018
+ this._videoExporter = void 0;
118019
+ if (!wasRunning) this.stop();
118020
+ }
118021
+ }
118022
+ }
117857
118023
  /** Exports visible scene meshes as OBJ text, without texture images. */
117858
118024
  toObj() {
117859
118025
  return SceneExporter.toObj(this.scene);
@@ -118851,6 +119017,137 @@ gl_FragDepth=readDepth(vUv);
118851
119017
  }
118852
119018
  };
118853
119019
 
119020
+ // src/world/AnvilWorldSource.ts
119021
+ init_dirname();
119022
+ init_buffer2();
119023
+ init_process2();
119024
+ init_MineRenderError();
119025
+ async function abortable2(promise, signal) {
119026
+ if (!signal) return promise;
119027
+ let abort2;
119028
+ const cancelled = new Promise((_4, reject) => {
119029
+ abort2 = () => reject(signal.reason);
119030
+ signal.addEventListener("abort", abort2, { once: true });
119031
+ if (signal.aborted) abort2();
119032
+ });
119033
+ try {
119034
+ return await Promise.race([promise, cancelled]);
119035
+ } finally {
119036
+ signal.removeEventListener("abort", abort2);
119037
+ }
119038
+ }
119039
+ var AnvilWorldSource = class {
119040
+ constructor(readRegion, options = {}) {
119041
+ this.readRegion = readRegion;
119042
+ this.regions = /* @__PURE__ */ new Map();
119043
+ this.pending = /* @__PURE__ */ new Map();
119044
+ this.cachedBytes = 0;
119045
+ this.maxCachedRegions = options.maxCachedRegions ?? 4;
119046
+ this.maxCachedBytes = options.maxCachedBytes ?? 64 * 1024 * 1024;
119047
+ for (const [name2, value] of Object.entries({ maxCachedRegions: this.maxCachedRegions, maxCachedBytes: this.maxCachedBytes })) {
119048
+ if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name2} must be a nonnegative safe integer`);
119049
+ }
119050
+ }
119051
+ /** Decodes one column at absolute chunk coordinates. Aborting a caller leaves other shared readers active. */
119052
+ async getChunk(x4, z4, signal) {
119053
+ if (!Number.isSafeInteger(x4) || !Number.isSafeInteger(z4)) {
119054
+ throw new RangeError("Chunk column coordinates must be safe integers");
119055
+ }
119056
+ const regionX = Math.floor(x4 / 32), regionZ = Math.floor(z4 / 32);
119057
+ const data = await this.getRegion(regionX, regionZ, signal);
119058
+ signal?.throwIfAborted();
119059
+ if (!data) return void 0;
119060
+ const chunk = await abortable2(AnvilParser.parseChunk(data, x4 - regionX * 32, z4 - regionZ * 32), signal);
119061
+ signal?.throwIfAborted();
119062
+ if (chunk && (chunk.x !== x4 || chunk.z !== z4)) {
119063
+ throw new MineRenderError(`Anvil chunk coordinates ${chunk.x},${chunk.z} do not match requested column ${x4},${z4}`);
119064
+ }
119065
+ return chunk;
119066
+ }
119067
+ /** Releases cached regions. Reads already in progress finish without repopulating this cache. */
119068
+ clearCache() {
119069
+ this.regions.clear();
119070
+ this.pending.clear();
119071
+ this.cachedBytes = 0;
119072
+ }
119073
+ getRegion(x4, z4, signal) {
119074
+ signal?.throwIfAborted();
119075
+ const key = `${x4},${z4}`;
119076
+ const cached2 = this.regions.get(key);
119077
+ if (cached2) {
119078
+ this.regions.delete(key);
119079
+ this.regions.set(key, cached2);
119080
+ return Promise.resolve(cached2.data);
119081
+ }
119082
+ let loading = this.pending.get(key);
119083
+ if (!loading) {
119084
+ const controller = new AbortController();
119085
+ loading = {
119086
+ controller,
119087
+ waiters: 0,
119088
+ settled: false,
119089
+ promise: Promise.resolve().then(() => {
119090
+ controller.signal.throwIfAborted();
119091
+ return this.readRegion(x4, z4, controller.signal);
119092
+ }).then((data) => {
119093
+ controller.signal.throwIfAborted();
119094
+ let bytes = data instanceof Uint8Array ? data : data === void 0 ? void 0 : new Uint8Array(data);
119095
+ if (bytes && bytes.byteLength !== bytes.buffer.byteLength) bytes = new Uint8Array(bytes);
119096
+ if (bytes) AnvilParser.getChunkList(bytes);
119097
+ loading.settled = true;
119098
+ if (this.pending.get(key) === loading) this.cacheRegion(key, bytes);
119099
+ return bytes;
119100
+ }).finally(() => {
119101
+ loading.settled = true;
119102
+ if (this.pending.get(key) === loading) this.pending.delete(key);
119103
+ })
119104
+ };
119105
+ this.pending.set(key, loading);
119106
+ }
119107
+ const read = loading;
119108
+ read.waiters++;
119109
+ return new Promise((resolve, reject) => {
119110
+ let finished = false;
119111
+ const release2 = () => {
119112
+ if (finished) return false;
119113
+ finished = true;
119114
+ signal?.removeEventListener("abort", abort2);
119115
+ read.waiters--;
119116
+ return true;
119117
+ };
119118
+ const abort2 = () => {
119119
+ if (!release2()) return;
119120
+ if (!read.waiters && !read.settled) {
119121
+ if (this.pending.get(key) === read) this.pending.delete(key);
119122
+ read.controller.abort(signal.reason);
119123
+ }
119124
+ reject(signal.reason);
119125
+ };
119126
+ signal?.addEventListener("abort", abort2, { once: true });
119127
+ read.promise.then(
119128
+ (data) => {
119129
+ if (release2()) resolve(data);
119130
+ },
119131
+ (error2) => {
119132
+ if (release2()) reject(error2);
119133
+ }
119134
+ );
119135
+ if (signal?.aborted) abort2();
119136
+ });
119137
+ }
119138
+ cacheRegion(key, data) {
119139
+ const size = data?.byteLength ?? 0;
119140
+ if (!this.maxCachedRegions || size > this.maxCachedBytes) return;
119141
+ this.regions.set(key, { data, size });
119142
+ this.cachedBytes += size;
119143
+ while (this.regions.size > this.maxCachedRegions || this.cachedBytes > this.maxCachedBytes) {
119144
+ const oldest = this.regions.keys().next().value;
119145
+ this.cachedBytes -= this.regions.get(oldest).size;
119146
+ this.regions.delete(oldest);
119147
+ }
119148
+ }
119149
+ };
119150
+
118854
119151
  // src/world/BlockInfo.ts
118855
119152
  init_dirname();
118856
119153
  init_buffer2();
@@ -119542,6 +119839,188 @@ gl_FragDepth=readDepth(vUv);
119542
119839
  );
119543
119840
  }
119544
119841
  };
119842
+
119843
+ // src/world/WorldChunkSource.ts
119844
+ init_dirname();
119845
+ init_buffer2();
119846
+ init_process2();
119847
+
119848
+ // src/world/WorldStreamer.ts
119849
+ init_dirname();
119850
+ init_buffer2();
119851
+ init_process2();
119852
+ var WorldStreamer = class {
119853
+ constructor(world, source, options = {}) {
119854
+ this.world = world;
119855
+ this.source = source;
119856
+ this.loaded = /* @__PURE__ */ new Map();
119857
+ this.owned = /* @__PURE__ */ new Map();
119858
+ this.missing = /* @__PURE__ */ new Map();
119859
+ this.failed = /* @__PURE__ */ new Map();
119860
+ this.desired = [];
119861
+ this.disposed = false;
119862
+ this.loadRadius = options.loadRadius ?? 2;
119863
+ this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);
119864
+ if (![this.loadRadius, this.unloadRadius].every((value) => Number.isInteger(value) && value >= 0 && value <= 32) || this.unloadRadius < this.loadRadius) {
119865
+ throw new RangeError("Chunk radii must be integers from 0 to 32, with unloadRadius at least loadRadius");
119866
+ }
119867
+ }
119868
+ /** Returns absolute coordinates of successfully placed columns, including retained columns. */
119869
+ get loadedChunks() {
119870
+ return [...this.loaded.values()].map((position) => ({ ...position }));
119871
+ }
119872
+ /** Returns source failures retained within the unload radius. Failed columns do not block other loads. */
119873
+ get failedChunks() {
119874
+ return [...this.failed.values()].map((failure) => ({ ...failure }));
119875
+ }
119876
+ /** Counts desired columns still waiting for a source read or placement, including the active column. */
119877
+ get pendingChunks() {
119878
+ return this.desired.filter((position) => this.needsLoad(position)).length;
119879
+ }
119880
+ /**
119881
+ * Updates the center in absolute chunk coordinates. Repeated calls share the active drain.
119882
+ * Source failures are recorded in failedChunks; other columns continue loading. Missing and failed
119883
+ * columns are remembered within the retention radius. Call retryFailedChunks to retry source failures.
119884
+ * Placement and unload failures reject the drain; call update again to retry those operations.
119885
+ * Reads outside the latest load radius receive an abort signal; placement finishes before unloading.
119886
+ * Sources should represent an unchanged world during streaming.
119887
+ */
119888
+ async update(x4, z4) {
119889
+ if (this.disposed) throw new Error("WorldStreamer is disposed");
119890
+ if (![x4, z4].every((value) => Number.isSafeInteger(value) && Number.isSafeInteger(value - this.unloadRadius) && Number.isSafeInteger(value + this.unloadRadius))) {
119891
+ throw new RangeError("Chunk coordinates must be safe integers within the streaming radius");
119892
+ }
119893
+ if (!this.center || this.center.x !== x4 || this.center.z !== z4) {
119894
+ this.center = { x: x4, z: z4 };
119895
+ this.desired = [];
119896
+ for (let dz = -this.loadRadius; dz <= this.loadRadius; dz++) {
119897
+ for (let dx = -this.loadRadius; dx <= this.loadRadius; dx++) {
119898
+ this.desired.push({ x: x4 + dx, z: z4 + dz });
119899
+ }
119900
+ }
119901
+ this.desired.sort((a4, b4) => (a4.x - x4) ** 2 + (a4.z - z4) ** 2 - (b4.x - x4) ** 2 - (b4.z - z4) ** 2);
119902
+ if (this.loading && !this.within(this.loading.position, this.loadRadius)) {
119903
+ this.loading.controller.abort();
119904
+ }
119905
+ }
119906
+ return this.running ?? (this.running = Promise.resolve().then(() => this.drain()));
119907
+ }
119908
+ /** Updates from a camera or view-center position in scene units. One chunk spans 256 scene units. */
119909
+ async updatePosition(position) {
119910
+ if (![position.x, position.z].every(Number.isFinite)) throw new RangeError("Scene coordinates must be finite");
119911
+ return this.update(Math.floor(position.x / 256), Math.floor(position.z / 256));
119912
+ }
119913
+ /** Forgets recorded source failures and retries desired columns at the latest center. */
119914
+ async retryFailedChunks() {
119915
+ if (this.disposed) throw new Error("WorldStreamer is disposed");
119916
+ this.failed.clear();
119917
+ if (this.center) await this.update(this.center.x, this.center.z);
119918
+ }
119919
+ /** Aborts source reads, waits for active work, then unloads owned columns. The world and source remain caller-owned. */
119920
+ dispose() {
119921
+ this.disposed = true;
119922
+ this.desired = [];
119923
+ this.center = void 0;
119924
+ this.loading?.controller.abort();
119925
+ return this.disposal ?? (this.disposal = Promise.resolve().then(async () => {
119926
+ await this.running?.catch(() => void 0);
119927
+ let failure;
119928
+ let failed = false;
119929
+ for (const position of this.owned.values()) {
119930
+ try {
119931
+ await this.unload(position);
119932
+ } catch (error2) {
119933
+ failed = true;
119934
+ failure ?? (failure = error2);
119935
+ }
119936
+ }
119937
+ this.missing.clear();
119938
+ this.failed.clear();
119939
+ if (failed) throw failure;
119940
+ }).catch((error2) => {
119941
+ this.disposal = void 0;
119942
+ throw error2;
119943
+ }));
119944
+ }
119945
+ async drain() {
119946
+ try {
119947
+ while (!this.disposed) {
119948
+ for (const [key, position2] of this.missing) {
119949
+ if (!this.within(position2, this.unloadRadius)) this.missing.delete(key);
119950
+ }
119951
+ for (const [key, position2] of this.failed) {
119952
+ if (!this.within(position2, this.unloadRadius)) this.failed.delete(key);
119953
+ }
119954
+ const obsolete = [...this.owned.values()].find((position2) => !this.loaded.has(this.key(position2)) || !this.within(position2, this.unloadRadius));
119955
+ if (obsolete) {
119956
+ await this.unload(obsolete);
119957
+ continue;
119958
+ }
119959
+ const position = this.desired.find((position2) => this.needsLoad(position2));
119960
+ if (!position) return;
119961
+ let chunk;
119962
+ const controller = new AbortController();
119963
+ this.loading = { position, controller };
119964
+ try {
119965
+ chunk = await this.source.getChunk(position.x, position.z, controller.signal);
119966
+ if (chunk) this.validateChunk(chunk, position);
119967
+ } catch (error2) {
119968
+ if (!controller.signal.aborted && !this.disposed && this.within(position, this.loadRadius)) {
119969
+ this.failed.set(this.key(position), { ...position, error: error2 });
119970
+ }
119971
+ continue;
119972
+ } finally {
119973
+ this.loading = void 0;
119974
+ }
119975
+ if (controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) continue;
119976
+ if (!chunk) {
119977
+ this.missing.set(this.key(position), position);
119978
+ continue;
119979
+ }
119980
+ this.owned.set(this.key(position), position);
119981
+ try {
119982
+ await this.world.placeChunk(chunk);
119983
+ this.loaded.set(this.key(position), position);
119984
+ } catch (error2) {
119985
+ try {
119986
+ await this.unload(position);
119987
+ } catch (cleanupError) {
119988
+ const failure = error2 instanceof Error ? error2 : new Error(String(error2));
119989
+ try {
119990
+ Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
119991
+ } catch {
119992
+ throw Object.assign(new Error(failure.message), { cause: cleanupError });
119993
+ }
119994
+ throw failure;
119995
+ }
119996
+ throw error2;
119997
+ }
119998
+ }
119999
+ } finally {
120000
+ this.running = void 0;
120001
+ }
120002
+ }
120003
+ needsLoad(position) {
120004
+ const key = this.key(position);
120005
+ return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);
120006
+ }
120007
+ async unload(position) {
120008
+ await this.world.unloadChunkColumn(position.x, position.z);
120009
+ this.owned.delete(this.key(position));
120010
+ this.loaded.delete(this.key(position));
120011
+ }
120012
+ validateChunk(chunk, position) {
120013
+ if (chunk.x !== position.x || chunk.z !== position.z) {
120014
+ throw new Error(`World source returned chunk ${chunk.x},${chunk.z} for ${position.x},${position.z}`);
120015
+ }
120016
+ }
120017
+ within(position, radius) {
120018
+ return !!this.center && Math.abs(position.x - this.center.x) <= radius && Math.abs(position.z - this.center.z) <= radius;
120019
+ }
120020
+ key(position) {
120021
+ return `${position.x},${position.z}`;
120022
+ }
120023
+ };
119545
120024
  return __toCommonJS(index_browser_exports);
119546
120025
  })();
119547
120026
  /*! Bundled license information: