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.
@@ -1,4 +1,5 @@
1
1
  // src/world/WorldStreamer.ts
2
+ var MAX_PREFETCHED_COLUMNS = 4;
2
3
  var WorldStreamer = class {
3
4
  constructor(world, source, options = {}) {
4
5
  this.world = world;
@@ -8,6 +9,7 @@ var WorldStreamer = class {
8
9
  this.missing = /* @__PURE__ */ new Map();
9
10
  this.failed = /* @__PURE__ */ new Map();
10
11
  this.desired = [];
12
+ this.reads = /* @__PURE__ */ new Map();
11
13
  this.disposed = false;
12
14
  this.loadRadius = options.loadRadius ?? 2;
13
15
  this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);
@@ -52,6 +54,12 @@ var WorldStreamer = class {
52
54
  if (this.loading && !this.within(this.loading.position, this.loadRadius)) {
53
55
  this.loading.controller.abort();
54
56
  }
57
+ for (const [key, read] of this.reads) {
58
+ if (!this.within(read.position, this.loadRadius)) {
59
+ read.controller.abort();
60
+ if (read.ready) this.reads.delete(key);
61
+ }
62
+ }
55
63
  }
56
64
  return this.running ?? (this.running = Promise.resolve().then(() => this.drain()));
57
65
  }
@@ -72,8 +80,11 @@ var WorldStreamer = class {
72
80
  this.desired = [];
73
81
  this.center = void 0;
74
82
  this.loading?.controller.abort();
83
+ for (const read of this.reads.values()) read.controller.abort();
75
84
  return this.disposal ?? (this.disposal = Promise.resolve().then(async () => {
76
85
  await this.running?.catch(() => void 0);
86
+ await Promise.all([...this.reads.values()].map((read) => read.promise));
87
+ this.reads.clear();
77
88
  try {
78
89
  await this.unloadMany([...this.owned.values()]);
79
90
  } finally {
@@ -99,6 +110,10 @@ var WorldStreamer = class {
99
110
  await this.unloadMany(obsolete);
100
111
  continue;
101
112
  }
113
+ if (this.world.placeChunks) {
114
+ if (await this.placeReadyChunks()) continue;
115
+ return;
116
+ }
102
117
  const position = this.desired.find((position2) => this.needsLoad(position2));
103
118
  if (!position) return;
104
119
  let chunk;
@@ -121,26 +136,105 @@ var WorldStreamer = class {
121
136
  continue;
122
137
  }
123
138
  this.owned.set(this.key(position), position);
139
+ await this.placeOwnedColumns([position], () => this.world.placeChunk(chunk));
140
+ }
141
+ } finally {
142
+ for (const [key, read] of this.reads) {
143
+ read.controller.abort();
144
+ if (read.ready) this.reads.delete(key);
145
+ }
146
+ this.running = void 0;
147
+ }
148
+ }
149
+ fillReadSlots() {
150
+ while (!this.disposed && this.reads.size < MAX_PREFETCHED_COLUMNS) {
151
+ const position = this.desired.find((position2) => this.needsLoad(position2) && !this.reads.has(this.key(position2)));
152
+ if (!position) return;
153
+ const key = this.key(position);
154
+ const read = {
155
+ position,
156
+ controller: new AbortController(),
157
+ promise: Promise.resolve(),
158
+ ready: false,
159
+ failed: false
160
+ };
161
+ this.reads.set(key, read);
162
+ read.promise = (async () => {
124
163
  try {
125
- await this.world.placeChunk(chunk);
126
- this.loaded.set(this.key(position), position);
164
+ read.chunk = await this.source.getChunk(position.x, position.z, read.controller.signal);
165
+ if (read.chunk) this.validateChunk(read.chunk, position);
127
166
  } catch (error) {
128
- try {
129
- await this.unloadMany([position]);
130
- } catch (cleanupError) {
131
- const failure = error instanceof Error ? error : new Error(String(error));
132
- try {
133
- Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
134
- } catch {
135
- throw Object.assign(new Error(failure.message), { cause: cleanupError });
136
- }
137
- throw failure;
167
+ read.failed = true;
168
+ read.error = error;
169
+ } finally {
170
+ read.ready = true;
171
+ if (read.controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) {
172
+ this.reads.delete(key);
138
173
  }
139
- throw error;
174
+ }
175
+ })();
176
+ }
177
+ }
178
+ /** Places only the ready prefix, so later source reads cannot delay the nearest ready column. */
179
+ async placeReadyChunks() {
180
+ this.fillReadSlots();
181
+ const position = this.desired.find((position2) => this.needsLoad(position2));
182
+ if (!position) {
183
+ if (!this.reads.size) return false;
184
+ await Promise.all([...this.reads.values()].map((read) => read.promise));
185
+ return true;
186
+ }
187
+ const head = this.reads.get(this.key(position));
188
+ if (!head) {
189
+ for (let index = this.desired.length - 1; index >= 0; index--) {
190
+ const key = this.key(this.desired[index]);
191
+ if (this.reads.get(key)?.ready) {
192
+ this.reads.delete(key);
193
+ return true;
140
194
  }
141
195
  }
142
- } finally {
143
- this.running = void 0;
196
+ await Promise.race([...this.reads.values()].map((read) => read.promise));
197
+ return true;
198
+ }
199
+ if (!head.ready) {
200
+ await head.promise;
201
+ return true;
202
+ }
203
+ const chunks = [], positions = [];
204
+ for (const desired of this.desired) {
205
+ if (!this.needsLoad(desired)) continue;
206
+ const key = this.key(desired), read = this.reads.get(key);
207
+ if (!read?.ready) break;
208
+ this.reads.delete(key);
209
+ if (read.failed) this.failed.set(key, { ...desired, error: read.error });
210
+ else if (!read.chunk) this.missing.set(key, desired);
211
+ else {
212
+ chunks.push(read.chunk);
213
+ positions.push(desired);
214
+ }
215
+ }
216
+ if (!chunks.length) return true;
217
+ for (const position2 of positions) this.owned.set(this.key(position2), position2);
218
+ await this.placeOwnedColumns(positions, () => this.world.placeChunks(chunks));
219
+ return true;
220
+ }
221
+ async placeOwnedColumns(positions, place) {
222
+ try {
223
+ await place();
224
+ for (const position of positions) this.loaded.set(this.key(position), position);
225
+ } catch (error) {
226
+ try {
227
+ await this.unloadMany(positions);
228
+ } catch (cleanupError) {
229
+ const failure = error instanceof Error ? error : new Error(String(error));
230
+ try {
231
+ Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
232
+ } catch {
233
+ throw Object.assign(new Error(failure.message), { cause: cleanupError });
234
+ }
235
+ throw failure;
236
+ }
237
+ throw error;
144
238
  }
145
239
  }
146
240
  needsLoad(position) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/world/WorldStreamer.ts"],"sourcesContent":["import type { AnvilChunk } from \"./AnvilParser\";\nimport type { MineRenderWorld } from \"./MineRenderWorld\";\nimport type { WorldChunkSource } from \"./WorldChunkSource\";\n\nexport interface WorldStreamerOptions {\n /** Loads a square of chunk columns around the center. Integer from 0 to 32; defaults to 2. */\n loadRadius?: number;\n /** Retains loaded columns within this radius. Defaults to loadRadius + 1, capped at 32. */\n unloadRadius?: number;\n}\n\ninterface ChunkPosition {\n x: number;\n z: number;\n}\n\n/**\n * Loads nearby chunk columns in distance order and unloads distant columns.\n * Updates coalesce to the latest center and serialize world mutations. Use a dedicated world:\n * the streamer replaces columns it loads, and callers must await disposal before editing that world.\n * The source remains caller-owned. Lighting, biome tint, and LOD are not added by streaming.\n */\nexport class WorldStreamer {\n private readonly loadRadius: number;\n private readonly unloadRadius: number;\n private readonly loaded = new Map<string, ChunkPosition>();\n private readonly owned = new Map<string, ChunkPosition>();\n private readonly missing = new Map<string, ChunkPosition>();\n private readonly failed = new Map<string, ChunkPosition & { error: unknown }>();\n private desired: ChunkPosition[] = [];\n private center?: ChunkPosition;\n private loading?: { position: ChunkPosition; controller: AbortController };\n private running?: Promise<void>;\n private disposal?: Promise<void>;\n private disposed = false;\n\n constructor(\n private readonly world: Pick<MineRenderWorld<boolean>, \"placeChunk\" | \"unloadChunkColumns\">,\n private readonly source: WorldChunkSource,\n options: WorldStreamerOptions = {}\n ) {\n this.loadRadius = options.loadRadius ?? 2;\n this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);\n if (![this.loadRadius, this.unloadRadius].every(value => Number.isInteger(value) && value >= 0 && value <= 32)\n || this.unloadRadius < this.loadRadius) {\n throw new RangeError(\"Chunk radii must be integers from 0 to 32, with unloadRadius at least loadRadius\");\n }\n }\n\n /** Returns absolute coordinates of successfully placed columns, including retained columns. */\n public get loadedChunks(): readonly { x: number; z: number }[] {\n return [...this.loaded.values()].map(position => ({ ...position }));\n }\n\n /** Returns source failures retained within the unload radius. Failed columns do not block other loads. */\n public get failedChunks(): readonly { x: number; z: number; error: unknown }[] {\n return [...this.failed.values()].map(failure => ({ ...failure }));\n }\n\n /** Counts desired columns still waiting for a source read or placement, including the active column. */\n public get pendingChunks(): number {\n return this.desired.filter(position => this.needsLoad(position)).length;\n }\n\n /**\n * Updates the center in absolute chunk coordinates. Repeated calls share the active drain.\n * Source failures are recorded in failedChunks; other columns continue loading. Missing and failed\n * columns are remembered within the retention radius. Call retryFailedChunks to retry source failures.\n * Placement and unload failures reject the drain; call update again to retry those operations.\n * Reads outside the latest load radius receive an abort signal; placement finishes before unloading.\n * Sources should represent an unchanged world during streaming.\n */\n public async update(x: number, z: number): Promise<void> {\n if (this.disposed) throw new Error(\"WorldStreamer is disposed\");\n if (![x, z].every(value => Number.isSafeInteger(value)\n && Number.isSafeInteger(value - this.unloadRadius) && Number.isSafeInteger(value + this.unloadRadius))) {\n throw new RangeError(\"Chunk coordinates must be safe integers within the streaming radius\");\n }\n if (!this.center || this.center.x !== x || this.center.z !== z) {\n this.center = { x, z };\n this.desired = [];\n for (let dz = -this.loadRadius; dz <= this.loadRadius; dz++) {\n for (let dx = -this.loadRadius; dx <= this.loadRadius; dx++) {\n this.desired.push({ x: x + dx, z: z + dz });\n }\n }\n this.desired.sort((a, b) => (a.x - x) ** 2 + (a.z - z) ** 2 - (b.x - x) ** 2 - (b.z - z) ** 2);\n if (this.loading && !this.within(this.loading.position, this.loadRadius)) {\n this.loading.controller.abort();\n }\n }\n return this.running ??= Promise.resolve().then(() => this.drain());\n }\n\n /** Updates from a camera or view-center position in scene units. One chunk spans 256 scene units. */\n public async updatePosition(position: { x: number; z: number }): Promise<void> {\n if (![position.x, position.z].every(Number.isFinite)) throw new RangeError(\"Scene coordinates must be finite\");\n return this.update(Math.floor(position.x / 256), Math.floor(position.z / 256));\n }\n\n /** Forgets recorded source failures and retries desired columns at the latest center. */\n public async retryFailedChunks(): Promise<void> {\n if (this.disposed) throw new Error(\"WorldStreamer is disposed\");\n this.failed.clear();\n if (this.center) await this.update(this.center.x, this.center.z);\n }\n\n /** Aborts source reads, waits for active work, then unloads owned columns. The world and source remain caller-owned. */\n public dispose(): Promise<void> {\n this.disposed = true;\n this.desired = [];\n this.center = undefined;\n this.loading?.controller.abort();\n return this.disposal ??= Promise.resolve().then(async () => {\n await this.running?.catch(() => undefined);\n try {\n await this.unloadMany([...this.owned.values()]);\n } finally {\n this.missing.clear();\n this.failed.clear();\n }\n }).catch(error => {\n this.disposal = undefined;\n throw error;\n });\n }\n\n private async drain(): Promise<void> {\n try {\n while (!this.disposed) {\n for (const [key, position] of this.missing) {\n if (!this.within(position, this.unloadRadius)) this.missing.delete(key);\n }\n for (const [key, position] of this.failed) {\n if (!this.within(position, this.unloadRadius)) this.failed.delete(key);\n }\n // Failed placements remain owned until cleanup succeeds, even inside the retained area.\n const obsolete = [...this.owned.values()].filter(position =>\n !this.loaded.has(this.key(position)) || !this.within(position, this.unloadRadius));\n if (obsolete.length) {\n await this.unloadMany(obsolete);\n continue;\n }\n const position = this.desired.find(position => this.needsLoad(position));\n if (!position) return;\n let chunk: AnvilChunk | undefined;\n const controller = new AbortController();\n this.loading = { position, controller };\n try {\n chunk = await this.source.getChunk(position.x, position.z, controller.signal);\n if (chunk) this.validateChunk(chunk, position);\n } catch (error) {\n if (!controller.signal.aborted && !this.disposed && this.within(position, this.loadRadius)) {\n this.failed.set(this.key(position), { ...position, error });\n }\n continue;\n } finally {\n this.loading = undefined;\n }\n if (controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) continue;\n if (!chunk) {\n this.missing.set(this.key(position), position);\n continue;\n }\n this.owned.set(this.key(position), position);\n try {\n await this.world.placeChunk(chunk);\n this.loaded.set(this.key(position), position);\n } catch (error) {\n try {\n await this.unloadMany([position]);\n } catch (cleanupError) {\n const failure = error instanceof Error ? error : new Error(String(error));\n try {\n Object.defineProperty(failure, \"cause\", { value: cleanupError, configurable: true });\n } catch {\n throw Object.assign(new Error(failure.message), { cause: cleanupError });\n }\n throw failure;\n }\n throw error;\n }\n }\n } finally {\n this.running = undefined;\n }\n }\n\n private needsLoad(position: ChunkPosition): boolean {\n const key = this.key(position);\n return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);\n }\n\n private async unloadMany(positions: readonly ChunkPosition[]): Promise<void> {\n if (!positions.length) return;\n try {\n await this.world.unloadChunkColumns(positions.map(({ x, z }) => ({ x, z })));\n } catch (error) {\n // A rejected batch may have removed any subset; retry every owner before reloading it.\n for (const position of positions) this.loaded.delete(this.key(position));\n throw error;\n }\n for (const position of positions) {\n this.owned.delete(this.key(position));\n this.loaded.delete(this.key(position));\n }\n }\n\n private validateChunk(chunk: AnvilChunk, position: ChunkPosition): void {\n if (chunk.x !== position.x || chunk.z !== position.z) {\n throw new Error(`World source returned chunk ${chunk.x},${chunk.z} for ${position.x},${position.z}`);\n }\n }\n\n private within(position: ChunkPosition, radius: number): boolean {\n return !!this.center && Math.abs(position.x - this.center.x) <= radius && Math.abs(position.z - this.center.z) <= radius;\n }\n\n private key(position: ChunkPosition): string {\n return `${position.x},${position.z}`;\n }\n}\n"],"mappings":"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","names":["position"]}
1
+ {"version":3,"sources":["../../../../src/world/WorldStreamer.ts"],"sourcesContent":["import type { AnvilChunk } from \"./AnvilParser\";\nimport type { MineRenderWorld } from \"./MineRenderWorld\";\nimport type { WorldChunkSource } from \"./WorldChunkSource\";\n\nexport interface WorldStreamerOptions {\n /** Loads a square of chunk columns around the center. Integer from 0 to 32; defaults to 2. */\n loadRadius?: number;\n /** Retains loaded columns within this radius. Defaults to loadRadius + 1, capped at 32. */\n unloadRadius?: number;\n}\n\ninterface ChunkPosition {\n x: number;\n z: number;\n}\n\ninterface ChunkRead {\n position: ChunkPosition;\n controller: AbortController;\n promise: Promise<void>;\n ready: boolean;\n chunk?: AnvilChunk;\n failed: boolean;\n error?: unknown;\n}\n\nconst MAX_PREFETCHED_COLUMNS = 4;\n\n/**\n * Loads nearby chunk columns in distance order and unloads distant columns.\n * Updates coalesce to the latest center and serialize world mutations. Use a dedicated world:\n * the streamer replaces columns it loads, and callers must await disposal before editing that world.\n * The source remains caller-owned. Lighting, biome tint, and LOD are not added by streaming.\n */\nexport class WorldStreamer {\n private readonly loadRadius: number;\n private readonly unloadRadius: number;\n private readonly loaded = new Map<string, ChunkPosition>();\n private readonly owned = new Map<string, ChunkPosition>();\n private readonly missing = new Map<string, ChunkPosition>();\n private readonly failed = new Map<string, ChunkPosition & { error: unknown }>();\n private desired: ChunkPosition[] = [];\n private center?: ChunkPosition;\n private loading?: { position: ChunkPosition; controller: AbortController };\n private readonly reads = new Map<string, ChunkRead>();\n private running?: Promise<void>;\n private disposal?: Promise<void>;\n private disposed = false;\n\n constructor(\n private readonly world: Pick<MineRenderWorld<boolean>, \"placeChunk\" | \"unloadChunkColumns\">\n & Partial<Pick<MineRenderWorld<boolean>, \"placeChunks\">>,\n private readonly source: WorldChunkSource,\n options: WorldStreamerOptions = {}\n ) {\n this.loadRadius = options.loadRadius ?? 2;\n this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);\n if (![this.loadRadius, this.unloadRadius].every(value => Number.isInteger(value) && value >= 0 && value <= 32)\n || this.unloadRadius < this.loadRadius) {\n throw new RangeError(\"Chunk radii must be integers from 0 to 32, with unloadRadius at least loadRadius\");\n }\n }\n\n /** Returns absolute coordinates of successfully placed columns, including retained columns. */\n public get loadedChunks(): readonly { x: number; z: number }[] {\n return [...this.loaded.values()].map(position => ({ ...position }));\n }\n\n /** Returns source failures retained within the unload radius. Failed columns do not block other loads. */\n public get failedChunks(): readonly { x: number; z: number; error: unknown }[] {\n return [...this.failed.values()].map(failure => ({ ...failure }));\n }\n\n /** Counts desired columns still waiting for a source read or placement, including the active column. */\n public get pendingChunks(): number {\n return this.desired.filter(position => this.needsLoad(position)).length;\n }\n\n /**\n * Updates the center in absolute chunk coordinates. Repeated calls share the active drain.\n * Source failures are recorded in failedChunks; other columns continue loading. Missing and failed\n * columns are remembered within the retention radius. Call retryFailedChunks to retry source failures.\n * Placement and unload failures reject the drain; call update again to retry those operations.\n * Reads outside the latest load radius receive an abort signal; placement finishes before unloading.\n * Sources should represent an unchanged world during streaming.\n */\n public async update(x: number, z: number): Promise<void> {\n if (this.disposed) throw new Error(\"WorldStreamer is disposed\");\n if (![x, z].every(value => Number.isSafeInteger(value)\n && Number.isSafeInteger(value - this.unloadRadius) && Number.isSafeInteger(value + this.unloadRadius))) {\n throw new RangeError(\"Chunk coordinates must be safe integers within the streaming radius\");\n }\n if (!this.center || this.center.x !== x || this.center.z !== z) {\n this.center = { x, z };\n this.desired = [];\n for (let dz = -this.loadRadius; dz <= this.loadRadius; dz++) {\n for (let dx = -this.loadRadius; dx <= this.loadRadius; dx++) {\n this.desired.push({ x: x + dx, z: z + dz });\n }\n }\n this.desired.sort((a, b) => (a.x - x) ** 2 + (a.z - z) ** 2 - (b.x - x) ** 2 - (b.z - z) ** 2);\n if (this.loading && !this.within(this.loading.position, this.loadRadius)) {\n this.loading.controller.abort();\n }\n for (const [key, read] of this.reads) {\n if (!this.within(read.position, this.loadRadius)) {\n read.controller.abort();\n if (read.ready) this.reads.delete(key);\n }\n }\n }\n return this.running ??= Promise.resolve().then(() => this.drain());\n }\n\n /** Updates from a camera or view-center position in scene units. One chunk spans 256 scene units. */\n public async updatePosition(position: { x: number; z: number }): Promise<void> {\n if (![position.x, position.z].every(Number.isFinite)) throw new RangeError(\"Scene coordinates must be finite\");\n return this.update(Math.floor(position.x / 256), Math.floor(position.z / 256));\n }\n\n /** Forgets recorded source failures and retries desired columns at the latest center. */\n public async retryFailedChunks(): Promise<void> {\n if (this.disposed) throw new Error(\"WorldStreamer is disposed\");\n this.failed.clear();\n if (this.center) await this.update(this.center.x, this.center.z);\n }\n\n /** Aborts source reads, waits for active work, then unloads owned columns. The world and source remain caller-owned. */\n public dispose(): Promise<void> {\n this.disposed = true;\n this.desired = [];\n this.center = undefined;\n this.loading?.controller.abort();\n for (const read of this.reads.values()) read.controller.abort();\n return this.disposal ??= Promise.resolve().then(async () => {\n await this.running?.catch(() => undefined);\n await Promise.all([...this.reads.values()].map(read => read.promise));\n this.reads.clear();\n try {\n await this.unloadMany([...this.owned.values()]);\n } finally {\n this.missing.clear();\n this.failed.clear();\n }\n }).catch(error => {\n this.disposal = undefined;\n throw error;\n });\n }\n\n private async drain(): Promise<void> {\n try {\n while (!this.disposed) {\n for (const [key, position] of this.missing) {\n if (!this.within(position, this.unloadRadius)) this.missing.delete(key);\n }\n for (const [key, position] of this.failed) {\n if (!this.within(position, this.unloadRadius)) this.failed.delete(key);\n }\n // Failed placements remain owned until cleanup succeeds, even inside the retained area.\n const obsolete = [...this.owned.values()].filter(position =>\n !this.loaded.has(this.key(position)) || !this.within(position, this.unloadRadius));\n if (obsolete.length) {\n await this.unloadMany(obsolete);\n continue;\n }\n if (this.world.placeChunks) {\n if (await this.placeReadyChunks()) continue;\n return;\n }\n const position = this.desired.find(position => this.needsLoad(position));\n if (!position) return;\n let chunk: AnvilChunk | undefined;\n const controller = new AbortController();\n this.loading = { position, controller };\n try {\n chunk = await this.source.getChunk(position.x, position.z, controller.signal);\n if (chunk) this.validateChunk(chunk, position);\n } catch (error) {\n if (!controller.signal.aborted && !this.disposed && this.within(position, this.loadRadius)) {\n this.failed.set(this.key(position), { ...position, error });\n }\n continue;\n } finally {\n this.loading = undefined;\n }\n if (controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) continue;\n if (!chunk) {\n this.missing.set(this.key(position), position);\n continue;\n }\n this.owned.set(this.key(position), position);\n await this.placeOwnedColumns([position], () => this.world.placeChunk(chunk!));\n }\n } finally {\n for (const [key, read] of this.reads) {\n read.controller.abort();\n if (read.ready) this.reads.delete(key);\n }\n this.running = undefined;\n }\n }\n\n private fillReadSlots(): void {\n while (!this.disposed && this.reads.size < MAX_PREFETCHED_COLUMNS) {\n const position = this.desired.find(position => this.needsLoad(position) && !this.reads.has(this.key(position)));\n if (!position) return;\n const key = this.key(position);\n const read: ChunkRead = {\n position, controller: new AbortController(), promise: Promise.resolve(), ready: false, failed: false\n };\n this.reads.set(key, read);\n read.promise = (async () => {\n try {\n read.chunk = await this.source.getChunk(position.x, position.z, read.controller.signal);\n if (read.chunk) this.validateChunk(read.chunk, position);\n } catch (error) {\n read.failed = true;\n read.error = error;\n } finally {\n read.ready = true;\n if (read.controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) {\n this.reads.delete(key);\n }\n }\n })();\n }\n }\n\n /** Places only the ready prefix, so later source reads cannot delay the nearest ready column. */\n private async placeReadyChunks(): Promise<boolean> {\n this.fillReadSlots();\n const position = this.desired.find(position => this.needsLoad(position));\n if (!position) {\n if (!this.reads.size) return false;\n await Promise.all([...this.reads.values()].map(read => read.promise));\n return true;\n }\n const head = this.reads.get(this.key(position));\n if (!head) {\n for (let index = this.desired.length - 1; index >= 0; index--) {\n const key = this.key(this.desired[index]);\n if (this.reads.get(key)?.ready) {\n this.reads.delete(key);\n return true;\n }\n }\n // Aborted reads still occupy slots until their source work settles.\n await Promise.race([...this.reads.values()].map(read => read.promise));\n return true;\n }\n if (!head.ready) {\n await head.promise;\n return true;\n }\n const chunks: AnvilChunk[] = [], positions: ChunkPosition[] = [];\n for (const desired of this.desired) {\n if (!this.needsLoad(desired)) continue;\n const key = this.key(desired), read = this.reads.get(key);\n if (!read?.ready) break;\n this.reads.delete(key);\n if (read.failed) this.failed.set(key, { ...desired, error: read.error });\n else if (!read.chunk) this.missing.set(key, desired);\n else {\n chunks.push(read.chunk);\n positions.push(desired);\n }\n }\n if (!chunks.length) return true;\n for (const position of positions) this.owned.set(this.key(position), position);\n await this.placeOwnedColumns(positions, () => this.world.placeChunks!(chunks));\n return true;\n }\n\n private async placeOwnedColumns(positions: readonly ChunkPosition[], place: () => Promise<void>): Promise<void> {\n try {\n await place();\n for (const position of positions) this.loaded.set(this.key(position), position);\n } catch (error) {\n try {\n await this.unloadMany(positions);\n } catch (cleanupError) {\n const failure = error instanceof Error ? error : new Error(String(error));\n try {\n Object.defineProperty(failure, \"cause\", { value: cleanupError, configurable: true });\n } catch {\n throw Object.assign(new Error(failure.message), { cause: cleanupError });\n }\n throw failure;\n }\n throw error;\n }\n }\n\n private needsLoad(position: ChunkPosition): boolean {\n const key = this.key(position);\n return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);\n }\n\n private async unloadMany(positions: readonly ChunkPosition[]): Promise<void> {\n if (!positions.length) return;\n try {\n await this.world.unloadChunkColumns(positions.map(({ x, z }) => ({ x, z })));\n } catch (error) {\n // A rejected batch may have removed any subset; retry every owner before reloading it.\n for (const position of positions) this.loaded.delete(this.key(position));\n throw error;\n }\n for (const position of positions) {\n this.owned.delete(this.key(position));\n this.loaded.delete(this.key(position));\n }\n }\n\n private validateChunk(chunk: AnvilChunk, position: ChunkPosition): void {\n if (chunk.x !== position.x || chunk.z !== position.z) {\n throw new Error(`World source returned chunk ${chunk.x},${chunk.z} for ${position.x},${position.z}`);\n }\n }\n\n private within(position: ChunkPosition, radius: number): boolean {\n return !!this.center && Math.abs(position.x - this.center.x) <= radius && Math.abs(position.z - this.center.z) <= radius;\n }\n\n private key(position: ChunkPosition): string {\n return `${position.x},${position.z}`;\n }\n}\n"],"mappings":"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","names":["position"]}
@@ -783,7 +783,10 @@ declare function toRadians(degrees: number): number;
783
783
  declare function toDegrees(radians: number): number;
784
784
  /** Resolves after a delay in milliseconds. */
785
785
  declare function sleep(timeout: number): Promise<void>;
786
- /** Yields to the next macrotask without timer clamping when the platform supports it. */
786
+ /**
787
+ * Yields to the next macrotask without timer clamping when the platform supports it.
788
+ * Not `scheduler.yield()`: Chrome runs its continuations ahead of rendering, so chained yields delay frames.
789
+ */
787
790
  declare function yieldToEventLoop(): Promise<void>;
788
791
  /** Reduces an angle modulo 360, retaining a negative sign for negative inputs. */
789
792
  declare function clampRotationDegrees(deg: number): number;
@@ -4019,6 +4022,8 @@ declare class SectionModels {
4019
4022
  pick(position?: Readonly<TripleArray>): SectionMeshTemplate[];
4020
4023
  biomeTint?: boolean;
4021
4024
  } | undefined>;
4025
+ /** Starts loading the state's models and atlases so a later `prepareState` with the same properties finds them loaded. */
4026
+ preload(blockState: BlockState, properties?: BlockStateProperties): Promise<void>;
4022
4027
  private state;
4023
4028
  /** Returns the shared still/flow atlas used by per-block fluid objects. */
4024
4029
  fluidAtlas(kind: FluidKind, root?: string): Promise<TextureAtlas>;
@@ -4133,6 +4138,7 @@ declare class Chunk<SectionMeshing extends boolean = false> {
4133
4138
  /** Replaces every cell from parsed section data. The caller refreshes neighbor culling. */
4134
4139
  placeSection(data: ChunkData): Promise<void>;
4135
4140
  private replaceSection;
4141
+ private createBlockObject;
4136
4142
  private positionAt;
4137
4143
  /** Refreshes saved lighting after adjacent columns load or unload, including columns with only light data. */
4138
4144
  refreshLight(indices?: Iterable<number>): Promise<void>;
@@ -4282,6 +4288,12 @@ declare class MineRenderWorld<SectionMeshing extends boolean = false> {
4282
4288
  private placeChunkGroups;
4283
4289
  /** Replaces a chunk column's blocks, biomes, and saved light and, with `renderEntities`, its supported saved mobs. */
4284
4290
  placeChunk(chunk: AnvilChunk, executor?: BatchedExecutor): Promise<void>;
4291
+ /**
4292
+ * Replaces parsed columns in input order, then refreshes neighboring geometry once.
4293
+ * Duplicate coordinates retain their order. A failed column stops placement; queued refreshes finish before rejection.
4294
+ */
4295
+ placeChunks(chunks: readonly AnvilChunk[]): Promise<void>;
4296
+ private placeColumn;
4285
4297
  /**
4286
4298
  * Removes blocks and owned entities at chunk-column coordinates, then refreshes neighboring faces and fluids.
4287
4299
  * Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
@@ -4314,7 +4326,7 @@ declare class MineRenderWorld<SectionMeshing extends boolean = false> {
4314
4326
  interface MineRenderWorldOptions<SectionMeshing extends boolean = boolean> {
4315
4327
  /** Render supported saved mobs at their position and yaw, including supported appearance fields. Defaults to false. */
4316
4328
  renderEntities?: boolean;
4317
- /** Merge static opaque cubes into section meshes. Merged blocks have no individual object. */
4329
+ /** Merge block models of any shape and fluid surfaces into per-section meshes; block entities keep individual objects. Merged blocks have no individual object. */
4318
4330
  sectionMeshing?: SectionMeshing;
4319
4331
  /** Shade blocks and fluids with saved sky/block light. Missing or invalid data keeps preview brightness. Defaults to false. Requires `sectionMeshing`. */
4320
4332
  savedLighting?: boolean;
@@ -4324,7 +4336,7 @@ interface MineRenderWorldOptions<SectionMeshing extends boolean = boolean> {
4324
4336
  ambientOcclusion?: boolean;
4325
4337
  /** Average neighboring biome colors within this horizontal radius, from 0 to 7 blocks. Defaults to 0 (off). Requires `sectionMeshing`. */
4326
4338
  biomeBlendRadius?: number;
4327
- /** Maximum width and height of each section atlas page, in pixels. */
4339
+ /** Maximum width and height of each section atlas page, in pixels. Defaults to 2048. */
4328
4340
  maxAtlasSize?: number;
4329
4341
  }
4330
4342
 
@@ -4369,10 +4381,11 @@ declare class WorldStreamer {
4369
4381
  private desired;
4370
4382
  private center?;
4371
4383
  private loading?;
4384
+ private readonly reads;
4372
4385
  private running?;
4373
4386
  private disposal?;
4374
4387
  private disposed;
4375
- constructor(world: Pick<MineRenderWorld<boolean>, "placeChunk" | "unloadChunkColumns">, source: WorldChunkSource, options?: WorldStreamerOptions);
4388
+ constructor(world: Pick<MineRenderWorld<boolean>, "placeChunk" | "unloadChunkColumns"> & Partial<Pick<MineRenderWorld<boolean>, "placeChunks">>, source: WorldChunkSource, options?: WorldStreamerOptions);
4376
4389
  /** Returns absolute coordinates of successfully placed columns, including retained columns. */
4377
4390
  get loadedChunks(): readonly {
4378
4391
  x: number;
@@ -4405,6 +4418,10 @@ declare class WorldStreamer {
4405
4418
  /** Aborts source reads, waits for active work, then unloads owned columns. The world and source remain caller-owned. */
4406
4419
  dispose(): Promise<void>;
4407
4420
  private drain;
4421
+ private fillReadSlots;
4422
+ /** Places only the ready prefix, so later source reads cannot delay the nearest ready column. */
4423
+ private placeReadyChunks;
4424
+ private placeOwnedColumns;
4408
4425
  private needsLoad;
4409
4426
  private unloadMany;
4410
4427
  private validateChunk;