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.
- package/dist/browser/esm/index.browser.d.mts +17 -6
- package/dist/browser/esm/world/Chunk.mjs +8 -5
- package/dist/browser/esm/world/Chunk.mjs.map +1 -1
- package/dist/browser/esm/world/MineRenderWorld.mjs +92 -52
- package/dist/browser/esm/world/MineRenderWorld.mjs.map +1 -1
- package/dist/browser/esm/world/WorldStreamer.mjs +21 -20
- package/dist/browser/esm/world/WorldStreamer.mjs.map +1 -1
- package/dist/browser/index.d.ts +17 -6
- package/dist/browser/index.js +121 -77
- package/dist/browser/index.js.map +1 -1
- package/dist/bundle.js +121 -77
- package/dist/bundle.js.map +1 -1
- package/dist/node/index.d.mts +17 -6
- package/dist/node/index.d.ts +17 -6
- package/dist/node/index.js +121 -77
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +121 -77
- package/dist/node/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -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\" | \"unloadChunkColumn\">,\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 let failure: unknown;\n let failed = false;\n for (const position of this.owned.values()) {\n try {\n await this.unload(position);\n } catch (error) {\n failed = true;\n failure ??= error;\n }\n }\n this.missing.clear();\n this.failed.clear();\n if (failed) throw failure;\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()].find(position =>\n !this.loaded.has(this.key(position)) || !this.within(position, this.unloadRadius));\n if (obsolete) {\n await this.unload(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.unload(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 unload(position: ChunkPosition): Promise<void> {\n await this.world.unloadChunkColumn(position.x, position.z);\n this.owned.delete(this.key(position));\n this.loaded.delete(this.key(position));\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\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":";AAsBO,IAAM,gBAAN,MAAoB;AAAA,EAcvB,YACqB,OACA,QACjB,UAAgC,CAAC,GACnC;AAHmB;AACA;AAbrB,SAAiB,SAAS,oBAAI,IAA2B;AACzD,SAAiB,QAAQ,oBAAI,IAA2B;AACxD,SAAiB,UAAU,oBAAI,IAA2B;AAC1D,SAAiB,SAAS,oBAAI,IAAgD;AAC9E,SAAQ,UAA2B,CAAC;AAKpC,SAAQ,WAAW;AAOf,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,eAAe,QAAQ,gBAAgB,KAAK,IAAI,IAAI,KAAK,aAAa,CAAC;AAC5E,QAAI,CAAC,CAAC,KAAK,YAAY,KAAK,YAAY,EAAE,MAAM,WAAS,OAAO,UAAU,KAAK,KAAK,SAAS,KAAK,SAAS,EAAE,KACtG,KAAK,eAAe,KAAK,YAAY;AACxC,YAAM,IAAI,WAAW,kFAAkF;AAAA,IAC3G;AAAA,EACJ;AAAA;AAAA,EAGA,IAAW,eAAoD;AAC3D,WAAO,CAAC,GAAG,KAAK,OAAO,OAAO,CAAC,EAAE,IAAI,eAAa,EAAE,GAAG,SAAS,EAAE;AAAA,EACtE;AAAA;AAAA,EAGA,IAAW,eAAoE;AAC3E,WAAO,CAAC,GAAG,KAAK,OAAO,OAAO,CAAC,EAAE,IAAI,cAAY,EAAE,GAAG,QAAQ,EAAE;AAAA,EACpE;AAAA;AAAA,EAGA,IAAW,gBAAwB;AAC/B,WAAO,KAAK,QAAQ,OAAO,cAAY,KAAK,UAAU,QAAQ,CAAC,EAAE;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAa,OAAO,GAAW,GAA0B;AACrD,QAAI,KAAK,SAAU,OAAM,IAAI,MAAM,2BAA2B;AAC9D,QAAI,CAAC,CAAC,GAAG,CAAC,EAAE,MAAM,WAAS,OAAO,cAAc,KAAK,KAC9C,OAAO,cAAc,QAAQ,KAAK,YAAY,KAAK,OAAO,cAAc,QAAQ,KAAK,YAAY,CAAC,GAAG;AACxG,YAAM,IAAI,WAAW,qEAAqE;AAAA,IAC9F;AACA,QAAI,CAAC,KAAK,UAAU,KAAK,OAAO,MAAM,KAAK,KAAK,OAAO,MAAM,GAAG;AAC5D,WAAK,SAAS,EAAE,GAAG,EAAE;AACrB,WAAK,UAAU,CAAC;AAChB,eAAS,KAAK,CAAC,KAAK,YAAY,MAAM,KAAK,YAAY,MAAM;AACzD,iBAAS,KAAK,CAAC,KAAK,YAAY,MAAM,KAAK,YAAY,MAAM;AACzD,eAAK,QAAQ,KAAK,EAAE,GAAG,IAAI,IAAI,GAAG,IAAI,GAAG,CAAC;AAAA,QAC9C;AAAA,MACJ;AACA,WAAK,QAAQ,KAAK,CAAC,GAAG,OAAO,EAAE,IAAI,MAAM,KAAK,EAAE,IAAI,MAAM,KAAK,EAAE,IAAI,MAAM,KAAK,EAAE,IAAI,MAAM,CAAC;AAC7F,UAAI,KAAK,WAAW,CAAC,KAAK,OAAO,KAAK,QAAQ,UAAU,KAAK,UAAU,GAAG;AACtE,aAAK,QAAQ,WAAW,MAAM;AAAA,MAClC;AAAA,IACJ;AACA,WAAO,KAAK,YAAL,KAAK,UAAY,QAAQ,QAAQ,EAAE,KAAK,MAAM,KAAK,MAAM,CAAC;AAAA,EACrE;AAAA;AAAA,EAGA,MAAa,eAAe,UAAmD;AAC3E,QAAI,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC,EAAE,MAAM,OAAO,QAAQ,EAAG,OAAM,IAAI,WAAW,kCAAkC;AAC7G,WAAO,KAAK,OAAO,KAAK,MAAM,SAAS,IAAI,GAAG,GAAG,KAAK,MAAM,SAAS,IAAI,GAAG,CAAC;AAAA,EACjF;AAAA;AAAA,EAGA,MAAa,oBAAmC;AAC5C,QAAI,KAAK,SAAU,OAAM,IAAI,MAAM,2BAA2B;AAC9D,SAAK,OAAO,MAAM;AAClB,QAAI,KAAK,OAAQ,OAAM,KAAK,OAAO,KAAK,OAAO,GAAG,KAAK,OAAO,CAAC;AAAA,EACnE;AAAA;AAAA,EAGO,UAAyB;AAC5B,SAAK,WAAW;AAChB,SAAK,UAAU,CAAC;AAChB,SAAK,SAAS;AACd,SAAK,SAAS,WAAW,MAAM;AAC/B,WAAO,KAAK,aAAL,KAAK,WAAa,QAAQ,QAAQ,EAAE,KAAK,YAAY;AACxD,YAAM,KAAK,SAAS,MAAM,MAAM,MAAS;AACzC,UAAI;AACA,cAAM,KAAK,WAAW,CAAC,GAAG,KAAK,MAAM,OAAO,CAAC,CAAC;AAAA,MAClD,UAAE;AACE,aAAK,QAAQ,MAAM;AACnB,aAAK,OAAO,MAAM;AAAA,MACtB;AAAA,IACJ,CAAC,EAAE,MAAM,WAAS;AACd,WAAK,WAAW;AAChB,YAAM;AAAA,IACV,CAAC;AAAA,EACL;AAAA,EAEA,MAAc,QAAuB;AACjC,QAAI;AACA,aAAO,CAAC,KAAK,UAAU;AACnB,mBAAW,CAAC,KAAKA,SAAQ,KAAK,KAAK,SAAS;AACxC,cAAI,CAAC,KAAK,OAAOA,WAAU,KAAK,YAAY,EAAG,MAAK,QAAQ,OAAO,GAAG;AAAA,QAC1E;AACA,mBAAW,CAAC,KAAKA,SAAQ,KAAK,KAAK,QAAQ;AACvC,cAAI,CAAC,KAAK,OAAOA,WAAU,KAAK,YAAY,EAAG,MAAK,OAAO,OAAO,GAAG;AAAA,QACzE;AAEA,cAAM,WAAW,CAAC,GAAG,KAAK,MAAM,OAAO,CAAC,EAAE,OAAO,CAAAA,cAC7C,CAAC,KAAK,OAAO,IAAI,KAAK,IAAIA,SAAQ,CAAC,KAAK,CAAC,KAAK,OAAOA,WAAU,KAAK,YAAY,CAAC;AACrF,YAAI,SAAS,QAAQ;AACjB,gBAAM,KAAK,WAAW,QAAQ;AAC9B;AAAA,QACJ;AACA,cAAM,WAAW,KAAK,QAAQ,KAAK,CAAAA,cAAY,KAAK,UAAUA,SAAQ,CAAC;AACvE,YAAI,CAAC,SAAU;AACf,YAAI;AACJ,cAAM,aAAa,IAAI,gBAAgB;AACvC,aAAK,UAAU,EAAE,UAAU,WAAW;AACtC,YAAI;AACA,kBAAQ,MAAM,KAAK,OAAO,SAAS,SAAS,GAAG,SAAS,GAAG,WAAW,MAAM;AAC5E,cAAI,MAAO,MAAK,cAAc,OAAO,QAAQ;AAAA,QACjD,SAAS,OAAO;AACZ,cAAI,CAAC,WAAW,OAAO,WAAW,CAAC,KAAK,YAAY,KAAK,OAAO,UAAU,KAAK,UAAU,GAAG;AACxF,iBAAK,OAAO,IAAI,KAAK,IAAI,QAAQ,GAAG,EAAE,GAAG,UAAU,MAAM,CAAC;AAAA,UAC9D;AACA;AAAA,QACJ,UAAE;AACE,eAAK,UAAU;AAAA,QACnB;AACA,YAAI,WAAW,OAAO,WAAW,KAAK,YAAY,CAAC,KAAK,OAAO,UAAU,KAAK,UAAU,EAAG;AAC3F,YAAI,CAAC,OAAO;AACR,eAAK,QAAQ,IAAI,KAAK,IAAI,QAAQ,GAAG,QAAQ;AAC7C;AAAA,QACJ;AACA,aAAK,MAAM,IAAI,KAAK,IAAI,QAAQ,GAAG,QAAQ;AAC3C,YAAI;AACA,gBAAM,KAAK,MAAM,WAAW,KAAK;AACjC,eAAK,OAAO,IAAI,KAAK,IAAI,QAAQ,GAAG,QAAQ;AAAA,QAChD,SAAS,OAAO;AACZ,cAAI;AACA,kBAAM,KAAK,WAAW,CAAC,QAAQ,CAAC;AAAA,UACpC,SAAS,cAAc;AACnB,kBAAM,UAAU,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AACxE,gBAAI;AACA,qBAAO,eAAe,SAAS,SAAS,EAAE,OAAO,cAAc,cAAc,KAAK,CAAC;AAAA,YACvF,QAAQ;AACJ,oBAAM,OAAO,OAAO,IAAI,MAAM,QAAQ,OAAO,GAAG,EAAE,OAAO,aAAa,CAAC;AAAA,YAC3E;AACA,kBAAM;AAAA,UACV;AACA,gBAAM;AAAA,QACV;AAAA,MACJ;AAAA,IACJ,UAAE;AACE,WAAK,UAAU;AAAA,IACnB;AAAA,EACJ;AAAA,EAEQ,UAAU,UAAkC;AAChD,UAAM,MAAM,KAAK,IAAI,QAAQ;AAC7B,WAAO,CAAC,KAAK,OAAO,IAAI,GAAG,KAAK,CAAC,KAAK,QAAQ,IAAI,GAAG,KAAK,CAAC,KAAK,OAAO,IAAI,GAAG;AAAA,EAClF;AAAA,EAEA,MAAc,WAAW,WAAoD;AACzE,QAAI,CAAC,UAAU,OAAQ;AACvB,QAAI;AACA,YAAM,KAAK,MAAM,mBAAmB,UAAU,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC;AAAA,IAC/E,SAAS,OAAO;AAEZ,iBAAW,YAAY,UAAW,MAAK,OAAO,OAAO,KAAK,IAAI,QAAQ,CAAC;AACvE,YAAM;AAAA,IACV;AACA,eAAW,YAAY,WAAW;AAC9B,WAAK,MAAM,OAAO,KAAK,IAAI,QAAQ,CAAC;AACpC,WAAK,OAAO,OAAO,KAAK,IAAI,QAAQ,CAAC;AAAA,IACzC;AAAA,EACJ;AAAA,EAEQ,cAAc,OAAmB,UAA+B;AACpE,QAAI,MAAM,MAAM,SAAS,KAAK,MAAM,MAAM,SAAS,GAAG;AAClD,YAAM,IAAI,MAAM,+BAA+B,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,SAAS,CAAC,IAAI,SAAS,CAAC,EAAE;AAAA,IACvG;AAAA,EACJ;AAAA,EAEQ,OAAO,UAAyB,QAAyB;AAC7D,WAAO,CAAC,CAAC,KAAK,UAAU,KAAK,IAAI,SAAS,IAAI,KAAK,OAAO,CAAC,KAAK,UAAU,KAAK,IAAI,SAAS,IAAI,KAAK,OAAO,CAAC,KAAK;AAAA,EACtH;AAAA,EAEQ,IAAI,UAAiC;AACzC,WAAO,GAAG,SAAS,CAAC,IAAI,SAAS,CAAC;AAAA,EACtC;AACJ;","names":["position"]}
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -4066,7 +4066,7 @@ declare class Chunk<SectionMeshing extends boolean = false> {
|
|
|
4066
4066
|
private ambientOcclusionCells?;
|
|
4067
4067
|
private readonly blockChanges;
|
|
4068
4068
|
private biomeChange?;
|
|
4069
|
-
constructor(scene: MineRenderScene, x: number, y: number, z: number, onBlocksChanged?: ((positions: Vector3[]) => Promise<void>) | undefined, sectionModels?: SectionModels | undefined, neighborCell?: ((x: number, y: number, z: number) => number) | undefined, biomeAt?: ((x: number, y: number, z: number) => Maybe<string>) | undefined, onBiomesChanged?: ((biomes?: readonly string[]) => Promise<void>) | undefined, lightAt?: ((x: number, y: number, z: number) => number) | undefined, onLightCleared?: (() => Promise<void>) | undefined, ambientLight?: number, biomeBlend?: (() => BiomeBlend | undefined) | undefined);
|
|
4069
|
+
constructor(scene: MineRenderScene, x: number, y: number, z: number, onBlocksChanged?: ((positions: Vector3[], deferCulling?: boolean) => Promise<void>) | undefined, sectionModels?: SectionModels | undefined, neighborCell?: ((x: number, y: number, z: number) => number) | undefined, biomeAt?: ((x: number, y: number, z: number) => Maybe<string>) | undefined, onBiomesChanged?: ((biomes?: readonly string[]) => Promise<void>) | undefined, lightAt?: ((x: number, y: number, z: number) => number) | undefined, onLightCleared?: (() => Promise<void>) | undefined, ambientLight?: number, biomeBlend?: (() => BiomeBlend | undefined) | undefined);
|
|
4070
4070
|
/** Finds the nearest owned block hit across sections, testing each shared mesh once. */
|
|
4071
4071
|
static raycast<SectionMeshing extends boolean>(chunks: Iterable<Chunk<SectionMeshing>>, raycaster: Raycaster): WorldBlockIntersection | undefined;
|
|
4072
4072
|
getBlockAt(x: number, y: number, z: number): Maybe<BlockInfo<SectionMeshing>>;
|
|
@@ -4078,11 +4078,14 @@ declare class Chunk<SectionMeshing extends boolean = false> {
|
|
|
4078
4078
|
getBiomeAt(pos: TripleArray): Maybe<string>;
|
|
4079
4079
|
/** Copies 64 biome IDs in x + z * 4 + y * 16 order and refreshes placed blocks. Omit the array to clear biome data. */
|
|
4080
4080
|
setBiomes(biomes?: readonly string[]): Promise<void>;
|
|
4081
|
-
/**
|
|
4081
|
+
/**
|
|
4082
|
+
* Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects.
|
|
4083
|
+
* The owning world can defer neighbor culling until all affected chunks are refreshed.
|
|
4084
|
+
*/
|
|
4082
4085
|
refreshBiomes(columns?: readonly {
|
|
4083
4086
|
x: number;
|
|
4084
4087
|
z: number;
|
|
4085
|
-
}[]): Promise<void>;
|
|
4088
|
+
}[], deferCulling?: boolean): Promise<void>;
|
|
4086
4089
|
private refreshBiomeBlocks;
|
|
4087
4090
|
/**
|
|
4088
4091
|
* Places a block at integer world block coordinates. Pass `undefined` to remove it.
|
|
@@ -4093,7 +4096,7 @@ declare class Chunk<SectionMeshing extends boolean = false> {
|
|
|
4093
4096
|
/**
|
|
4094
4097
|
* Places a block at integer chunk-local coordinates from 0 to 15.
|
|
4095
4098
|
*/
|
|
4096
|
-
setBlockInChunkAt(pos: Vector3, block?: Block, worldPos?: Vector3, onBlocksChanged?: ((positions: Vector3[]) => Promise<void>) | undefined): Promise<Maybe<BlockInfo<SectionMeshing>>>;
|
|
4099
|
+
setBlockInChunkAt(pos: Vector3, block?: Block, worldPos?: Vector3, onBlocksChanged?: ((positions: Vector3[], deferCulling?: boolean) => Promise<void>) | undefined): Promise<Maybe<BlockInfo<SectionMeshing>>>;
|
|
4097
4100
|
/** Places cells in input order and returns changed world positions without refreshing neighbors. */
|
|
4098
4101
|
placeBlocks(blocks: readonly {
|
|
4099
4102
|
index: number;
|
|
@@ -4258,6 +4261,14 @@ declare class MineRenderWorld<SectionMeshing extends boolean = false> {
|
|
|
4258
4261
|
* Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
|
|
4259
4262
|
*/
|
|
4260
4263
|
unloadChunkColumn(x: number, z: number): Promise<void>;
|
|
4264
|
+
/**
|
|
4265
|
+
* Removes chunk columns together and refreshes surviving neighbors once.
|
|
4266
|
+
* Coordinates are absolute chunk columns. Duplicate and missing columns are ignored.
|
|
4267
|
+
*/
|
|
4268
|
+
unloadChunkColumns(positions: readonly {
|
|
4269
|
+
x: number;
|
|
4270
|
+
z: number;
|
|
4271
|
+
}[]): Promise<void>;
|
|
4261
4272
|
private clearChunkColumn;
|
|
4262
4273
|
private markSectionChanged;
|
|
4263
4274
|
/** Removes all chunks and releases their blocks, section meshes, and owned entities. */
|
|
@@ -4335,7 +4346,7 @@ declare class WorldStreamer {
|
|
|
4335
4346
|
private running?;
|
|
4336
4347
|
private disposal?;
|
|
4337
4348
|
private disposed;
|
|
4338
|
-
constructor(world: Pick<MineRenderWorld<boolean>, "placeChunk" | "
|
|
4349
|
+
constructor(world: Pick<MineRenderWorld<boolean>, "placeChunk" | "unloadChunkColumns">, source: WorldChunkSource, options?: WorldStreamerOptions);
|
|
4339
4350
|
/** Returns absolute coordinates of successfully placed columns, including retained columns. */
|
|
4340
4351
|
get loadedChunks(): readonly {
|
|
4341
4352
|
x: number;
|
|
@@ -4369,7 +4380,7 @@ declare class WorldStreamer {
|
|
|
4369
4380
|
dispose(): Promise<void>;
|
|
4370
4381
|
private drain;
|
|
4371
4382
|
private needsLoad;
|
|
4372
|
-
private
|
|
4383
|
+
private unloadMany;
|
|
4373
4384
|
private validateChunk;
|
|
4374
4385
|
private within;
|
|
4375
4386
|
private key;
|
package/dist/browser/index.js
CHANGED
|
@@ -18686,8 +18686,14 @@ var MineRenderWorld = class {
|
|
|
18686
18686
|
}
|
|
18687
18687
|
return queued;
|
|
18688
18688
|
}
|
|
18689
|
-
async refreshBiomes() {
|
|
18690
|
-
if (this.biomeRefreshing)
|
|
18689
|
+
async refreshBiomes(deferCulling = false) {
|
|
18690
|
+
if (this.biomeRefreshing) {
|
|
18691
|
+
await this.biomeRefreshing;
|
|
18692
|
+
if (!deferCulling) {
|
|
18693
|
+
if (this.pendingCulling.size) await this.updateCulling([]);
|
|
18694
|
+
else await this.culling;
|
|
18695
|
+
}
|
|
18696
|
+
}
|
|
18691
18697
|
if (!this.pendingBiomes.size) return /* @__PURE__ */ new Map();
|
|
18692
18698
|
return this.biomeRefreshing ?? (this.biomeRefreshing = Promise.resolve().then(async () => {
|
|
18693
18699
|
const failures = /* @__PURE__ */ new Map();
|
|
@@ -18699,7 +18705,7 @@ var MineRenderWorld = class {
|
|
|
18699
18705
|
this.pendingBiomes.delete(chunk);
|
|
18700
18706
|
if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) !== chunk) continue;
|
|
18701
18707
|
try {
|
|
18702
|
-
await chunk.refreshBiomes(columns);
|
|
18708
|
+
await chunk.refreshBiomes(columns, deferCulling);
|
|
18703
18709
|
} catch (error) {
|
|
18704
18710
|
failures.set(chunk, error);
|
|
18705
18711
|
if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingBiomes.set(chunk, void 0);
|
|
@@ -18961,15 +18967,35 @@ var MineRenderWorld = class {
|
|
|
18961
18967
|
* Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
|
|
18962
18968
|
*/
|
|
18963
18969
|
async unloadChunkColumn(x, z) {
|
|
18964
|
-
|
|
18965
|
-
|
|
18970
|
+
return this.unloadChunkColumns([{ x, z }]);
|
|
18971
|
+
}
|
|
18972
|
+
/**
|
|
18973
|
+
* Removes chunk columns together and refreshes surviving neighbors once.
|
|
18974
|
+
* Coordinates are absolute chunk columns. Duplicate and missing columns are ignored.
|
|
18975
|
+
*/
|
|
18976
|
+
async unloadChunkColumns(positions) {
|
|
18977
|
+
const columns = /* @__PURE__ */ new Map();
|
|
18978
|
+
for (const position of positions) {
|
|
18979
|
+
const { x, z } = position ?? {};
|
|
18980
|
+
if (!Number.isInteger(x) || !Number.isInteger(z)) {
|
|
18981
|
+
throw new RangeError("Chunk column coordinates must be integers");
|
|
18982
|
+
}
|
|
18983
|
+
columns.set(`${x}_${z}`, { x, z });
|
|
18966
18984
|
}
|
|
18967
|
-
|
|
18985
|
+
if (!columns.size) return;
|
|
18968
18986
|
await this.culling;
|
|
18987
|
+
let sliceStart = performance.now();
|
|
18969
18988
|
try {
|
|
18970
|
-
|
|
18989
|
+
for (const { x, z } of columns.values()) {
|
|
18990
|
+
this.entities?.clearColumn(x, z);
|
|
18991
|
+
await this.clearChunkColumn(x, z);
|
|
18992
|
+
if (performance.now() - sliceStart > 8) {
|
|
18993
|
+
await yieldToEventLoop();
|
|
18994
|
+
sliceStart = performance.now();
|
|
18995
|
+
}
|
|
18996
|
+
}
|
|
18971
18997
|
} finally {
|
|
18972
|
-
const failures = await this.refreshBiomes();
|
|
18998
|
+
const failures = await this.refreshBiomes(true);
|
|
18973
18999
|
if (failures.size) log19.warn("Failed to refresh biome colors", failures.values().next().value);
|
|
18974
19000
|
await this.updateCulling([]);
|
|
18975
19001
|
}
|
|
@@ -19032,7 +19058,7 @@ var MineRenderWorld = class {
|
|
|
19032
19058
|
}
|
|
19033
19059
|
this.sectionModels?.clear();
|
|
19034
19060
|
}
|
|
19035
|
-
updateCulling(positions) {
|
|
19061
|
+
updateCulling(positions, defer = false) {
|
|
19036
19062
|
const neighbor = new import_three45.Vector3();
|
|
19037
19063
|
for (const pos of positions) {
|
|
19038
19064
|
const cx = Math.floor(pos.x / 16), cy = Math.floor(pos.y / 16), cz = Math.floor(pos.z / 16);
|
|
@@ -19054,58 +19080,72 @@ var MineRenderWorld = class {
|
|
|
19054
19080
|
}
|
|
19055
19081
|
}
|
|
19056
19082
|
}
|
|
19083
|
+
if (defer) return Promise.resolve();
|
|
19057
19084
|
return this.culling ?? (this.culling = Promise.resolve().then(async () => {
|
|
19058
19085
|
let sliceStart = performance.now();
|
|
19059
19086
|
try {
|
|
19060
19087
|
const culling = await loadBlockCulling();
|
|
19061
19088
|
while (this.pendingCulling.size) {
|
|
19062
|
-
const batch =
|
|
19089
|
+
const batch = new Map(this.pendingCulling);
|
|
19063
19090
|
this.pendingCulling.clear();
|
|
19064
|
-
|
|
19065
|
-
const
|
|
19066
|
-
|
|
19067
|
-
const
|
|
19068
|
-
|
|
19069
|
-
|
|
19070
|
-
|
|
19071
|
-
|
|
19072
|
-
const
|
|
19073
|
-
|
|
19074
|
-
|
|
19075
|
-
|
|
19076
|
-
|
|
19077
|
-
|
|
19078
|
-
|
|
19079
|
-
|
|
19080
|
-
|
|
19081
|
-
|
|
19082
|
-
|
|
19083
|
-
|
|
19084
|
-
|
|
19085
|
-
|
|
19086
|
-
|
|
19087
|
-
|
|
19088
|
-
|
|
19089
|
-
|
|
19090
|
-
|
|
19091
|
-
|
|
19092
|
-
|
|
19093
|
-
|
|
19094
|
-
|
|
19095
|
-
|
|
19096
|
-
|
|
19097
|
-
|
|
19091
|
+
try {
|
|
19092
|
+
for (const [chunk, indices] of batch) {
|
|
19093
|
+
const neighbors = CUBE_FACE_OFFSETS.map(([x, y, z]) => this._chunks.get(`${chunk.x + x}_${chunk.y + y}_${chunk.z + z}`));
|
|
19094
|
+
const neighborAt = (x, y, z) => {
|
|
19095
|
+
const lx = x - chunk.x * 16, ly = y - chunk.y * 16, lz = z - chunk.z * 16;
|
|
19096
|
+
const section = neighbors[lx >= 16 ? 0 : lx < 0 ? 1 : ly >= 16 ? 2 : ly < 0 ? 3 : lz >= 16 ? 4 : 5];
|
|
19097
|
+
return [section, (ly & 15) * 256 + (lz & 15) * 16 + (lx & 15)];
|
|
19098
|
+
};
|
|
19099
|
+
const masks = chunk.updateCullMasks(indices === "all" ? void 0 : indices, (x, y, z) => {
|
|
19100
|
+
const [section, i] = neighborAt(x, y, z);
|
|
19101
|
+
return section?.isOccludingIndex(i) ?? false;
|
|
19102
|
+
}, culling, (x, y, z) => {
|
|
19103
|
+
const [section, i] = neighborAt(x, y, z);
|
|
19104
|
+
return section?.visibleBlockIndex(i);
|
|
19105
|
+
});
|
|
19106
|
+
const fluidIndices = indices === "all" ? function* () {
|
|
19107
|
+
for (let index = 0; index < 4096; index++) yield index;
|
|
19108
|
+
}() : indices;
|
|
19109
|
+
for (const index of fluidIndices) {
|
|
19110
|
+
if (!chunk.fluidByteIndex(index) || !chunk.isBlockVisibleIndex(index)) continue;
|
|
19111
|
+
const lx = index % 16, ly = Math.floor(index / 256), lz = Math.floor(index / 16) % 16;
|
|
19112
|
+
const wx = chunk.x * 16 + lx, wy = chunk.y * 16 + ly, wz = chunk.z * 16 + lz;
|
|
19113
|
+
const block = chunk.getBlockAt(neighbor.set(wx, wy, wz));
|
|
19114
|
+
if (block.object?.fluidKind) {
|
|
19115
|
+
await block.object.updateFluid((x, y, z) => {
|
|
19116
|
+
const nx = lx + x, ny = ly + y, nz = lz + z;
|
|
19117
|
+
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)}`);
|
|
19118
|
+
const neighborIndex = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
|
|
19119
|
+
const byte = section?.fluidByteIndex(neighborIndex) ?? 0;
|
|
19120
|
+
return {
|
|
19121
|
+
fluid: fluidKindOf(byte),
|
|
19122
|
+
level: byte & 15,
|
|
19123
|
+
solid: section?.isOccludingIndex(neighborIndex) ?? false
|
|
19124
|
+
};
|
|
19125
|
+
});
|
|
19126
|
+
}
|
|
19127
|
+
}
|
|
19128
|
+
for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
|
|
19129
|
+
await chunk.refreshLight(indices === "all" ? void 0 : indices);
|
|
19130
|
+
chunk.refreshAmbientOcclusion(this._ambientOcclusion);
|
|
19131
|
+
if ((this._ambientOcclusion || this.ambientOcclusionPending) && performance.now() - sliceStart > 8) {
|
|
19132
|
+
await yieldToEventLoop();
|
|
19133
|
+
sliceStart = performance.now();
|
|
19098
19134
|
}
|
|
19099
19135
|
}
|
|
19100
|
-
|
|
19101
|
-
|
|
19102
|
-
|
|
19103
|
-
|
|
19104
|
-
|
|
19105
|
-
|
|
19136
|
+
const builds = await Promise.allSettled([...batch.keys()].map(async (chunk) => {
|
|
19137
|
+
await chunk.rebuildSectionMesh();
|
|
19138
|
+
batch.delete(chunk);
|
|
19139
|
+
}));
|
|
19140
|
+
const failure = builds.find((result) => result.status === "rejected");
|
|
19141
|
+
if (failure?.status === "rejected") throw failure.reason;
|
|
19142
|
+
} catch (error) {
|
|
19143
|
+
for (const [chunk, indices] of batch) {
|
|
19144
|
+
const pending = this.pendingCulling.get(chunk);
|
|
19145
|
+
this.pendingCulling.set(chunk, indices === "all" || pending === "all" ? "all" : /* @__PURE__ */ new Set([...indices, ...pending ?? []]));
|
|
19106
19146
|
}
|
|
19147
|
+
throw error;
|
|
19107
19148
|
}
|
|
19108
|
-
await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
|
|
19109
19149
|
}
|
|
19110
19150
|
} finally {
|
|
19111
19151
|
this.culling = void 0;
|
|
@@ -19122,7 +19162,7 @@ var MineRenderWorld = class {
|
|
|
19122
19162
|
x,
|
|
19123
19163
|
y,
|
|
19124
19164
|
z,
|
|
19125
|
-
(positions) => this.updateCulling(positions),
|
|
19165
|
+
(positions, defer) => this.updateCulling(positions, defer),
|
|
19126
19166
|
this.sectionModels,
|
|
19127
19167
|
(x2, y2, z2) => {
|
|
19128
19168
|
const cx = Math.floor(x2 / 16), cy = Math.floor(y2 / 16), cz = Math.floor(z2 / 16);
|
|
@@ -19856,11 +19896,14 @@ var Chunk = class _Chunk {
|
|
|
19856
19896
|
this.biomes = copy;
|
|
19857
19897
|
return this.refreshBiomes();
|
|
19858
19898
|
}
|
|
19859
|
-
/**
|
|
19860
|
-
|
|
19861
|
-
|
|
19899
|
+
/**
|
|
19900
|
+
* Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects.
|
|
19901
|
+
* The owning world can defer neighbor culling until all affected chunks are refreshed.
|
|
19902
|
+
*/
|
|
19903
|
+
refreshBiomes(columns, deferCulling = false) {
|
|
19904
|
+
return this.changeBlocks(() => this.refreshBiomeBlocks(columns, deferCulling), true);
|
|
19862
19905
|
}
|
|
19863
|
-
async refreshBiomeBlocks(columns) {
|
|
19906
|
+
async refreshBiomeBlocks(columns, deferCulling = false) {
|
|
19864
19907
|
const blend = columns && this.biomeBlend?.();
|
|
19865
19908
|
const ownColumn = columns?.some((column) => column.x === this.x && column.z === this.z);
|
|
19866
19909
|
const affected = /* @__PURE__ */ new Map();
|
|
@@ -19917,7 +19960,7 @@ var Chunk = class _Chunk {
|
|
|
19917
19960
|
this.renderedBlocks.get(index)?.object?.setVisible(false);
|
|
19918
19961
|
}
|
|
19919
19962
|
const positions = blocks.map(({ index }) => new import_three47.Vector3(...this.positionAt(index)));
|
|
19920
|
-
if (this.onBlocksChanged) await this.onBlocksChanged(positions);
|
|
19963
|
+
if (this.onBlocksChanged) await this.onBlocksChanged(positions, deferCulling);
|
|
19921
19964
|
else await this.rebuildSectionMesh();
|
|
19922
19965
|
}
|
|
19923
19966
|
}
|
|
@@ -20571,19 +20614,12 @@ var WorldStreamer = class {
|
|
|
20571
20614
|
this.loading?.controller.abort();
|
|
20572
20615
|
return this.disposal ?? (this.disposal = Promise.resolve().then(async () => {
|
|
20573
20616
|
await this.running?.catch(() => void 0);
|
|
20574
|
-
|
|
20575
|
-
|
|
20576
|
-
|
|
20577
|
-
|
|
20578
|
-
|
|
20579
|
-
} catch (error) {
|
|
20580
|
-
failed = true;
|
|
20581
|
-
failure ?? (failure = error);
|
|
20582
|
-
}
|
|
20617
|
+
try {
|
|
20618
|
+
await this.unloadMany([...this.owned.values()]);
|
|
20619
|
+
} finally {
|
|
20620
|
+
this.missing.clear();
|
|
20621
|
+
this.failed.clear();
|
|
20583
20622
|
}
|
|
20584
|
-
this.missing.clear();
|
|
20585
|
-
this.failed.clear();
|
|
20586
|
-
if (failed) throw failure;
|
|
20587
20623
|
}).catch((error) => {
|
|
20588
20624
|
this.disposal = void 0;
|
|
20589
20625
|
throw error;
|
|
@@ -20598,9 +20634,9 @@ var WorldStreamer = class {
|
|
|
20598
20634
|
for (const [key, position2] of this.failed) {
|
|
20599
20635
|
if (!this.within(position2, this.unloadRadius)) this.failed.delete(key);
|
|
20600
20636
|
}
|
|
20601
|
-
const obsolete = [...this.owned.values()].
|
|
20602
|
-
if (obsolete) {
|
|
20603
|
-
await this.
|
|
20637
|
+
const obsolete = [...this.owned.values()].filter((position2) => !this.loaded.has(this.key(position2)) || !this.within(position2, this.unloadRadius));
|
|
20638
|
+
if (obsolete.length) {
|
|
20639
|
+
await this.unloadMany(obsolete);
|
|
20604
20640
|
continue;
|
|
20605
20641
|
}
|
|
20606
20642
|
const position = this.desired.find((position2) => this.needsLoad(position2));
|
|
@@ -20630,7 +20666,7 @@ var WorldStreamer = class {
|
|
|
20630
20666
|
this.loaded.set(this.key(position), position);
|
|
20631
20667
|
} catch (error) {
|
|
20632
20668
|
try {
|
|
20633
|
-
await this.
|
|
20669
|
+
await this.unloadMany([position]);
|
|
20634
20670
|
} catch (cleanupError) {
|
|
20635
20671
|
const failure = error instanceof Error ? error : new Error(String(error));
|
|
20636
20672
|
try {
|
|
@@ -20651,10 +20687,18 @@ var WorldStreamer = class {
|
|
|
20651
20687
|
const key = this.key(position);
|
|
20652
20688
|
return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);
|
|
20653
20689
|
}
|
|
20654
|
-
async
|
|
20655
|
-
|
|
20656
|
-
|
|
20657
|
-
|
|
20690
|
+
async unloadMany(positions) {
|
|
20691
|
+
if (!positions.length) return;
|
|
20692
|
+
try {
|
|
20693
|
+
await this.world.unloadChunkColumns(positions.map(({ x, z }) => ({ x, z })));
|
|
20694
|
+
} catch (error) {
|
|
20695
|
+
for (const position of positions) this.loaded.delete(this.key(position));
|
|
20696
|
+
throw error;
|
|
20697
|
+
}
|
|
20698
|
+
for (const position of positions) {
|
|
20699
|
+
this.owned.delete(this.key(position));
|
|
20700
|
+
this.loaded.delete(this.key(position));
|
|
20701
|
+
}
|
|
20658
20702
|
}
|
|
20659
20703
|
validateChunk(chunk, position) {
|
|
20660
20704
|
if (chunk.x !== position.x || chunk.z !== position.z) {
|