minerender 2.1.0-alpha.137 → 2.1.0-alpha.139
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 +29 -8
- package/dist/browser/esm/model/multiblock/StructureParser.mjs +7 -4
- package/dist/browser/esm/model/multiblock/StructureParser.mjs.map +1 -1
- 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 +29 -8
- package/dist/browser/index.js +128 -81
- package/dist/browser/index.js.map +1 -1
- package/dist/bundle.js +128 -81
- package/dist/bundle.js.map +1 -1
- package/dist/node/index.d.mts +29 -8
- package/dist/node/index.d.ts +29 -8
- package/dist/node/index.js +128 -81
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +128 -81
- 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":"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","names":["position"]}
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -2491,11 +2491,21 @@ interface Palette {
|
|
|
2491
2491
|
};
|
|
2492
2492
|
}
|
|
2493
2493
|
interface PaletteEntry {
|
|
2494
|
-
Name
|
|
2494
|
+
Name?: {
|
|
2495
2495
|
type: "string";
|
|
2496
2496
|
value: string;
|
|
2497
2497
|
};
|
|
2498
|
-
|
|
2498
|
+
id?: {
|
|
2499
|
+
type: "string";
|
|
2500
|
+
value: string;
|
|
2501
|
+
};
|
|
2502
|
+
Properties?: {
|
|
2503
|
+
type: "compound";
|
|
2504
|
+
value: {
|
|
2505
|
+
[property: string]: PalettePropertyValue;
|
|
2506
|
+
};
|
|
2507
|
+
};
|
|
2508
|
+
properties?: {
|
|
2499
2509
|
type: "compound";
|
|
2500
2510
|
value: {
|
|
2501
2511
|
[property: string]: PalettePropertyValue;
|
|
@@ -4066,7 +4076,7 @@ declare class Chunk<SectionMeshing extends boolean = false> {
|
|
|
4066
4076
|
private ambientOcclusionCells?;
|
|
4067
4077
|
private readonly blockChanges;
|
|
4068
4078
|
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);
|
|
4079
|
+
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
4080
|
/** Finds the nearest owned block hit across sections, testing each shared mesh once. */
|
|
4071
4081
|
static raycast<SectionMeshing extends boolean>(chunks: Iterable<Chunk<SectionMeshing>>, raycaster: Raycaster): WorldBlockIntersection | undefined;
|
|
4072
4082
|
getBlockAt(x: number, y: number, z: number): Maybe<BlockInfo<SectionMeshing>>;
|
|
@@ -4078,11 +4088,14 @@ declare class Chunk<SectionMeshing extends boolean = false> {
|
|
|
4078
4088
|
getBiomeAt(pos: TripleArray): Maybe<string>;
|
|
4079
4089
|
/** Copies 64 biome IDs in x + z * 4 + y * 16 order and refreshes placed blocks. Omit the array to clear biome data. */
|
|
4080
4090
|
setBiomes(biomes?: readonly string[]): Promise<void>;
|
|
4081
|
-
/**
|
|
4091
|
+
/**
|
|
4092
|
+
* Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects.
|
|
4093
|
+
* The owning world can defer neighbor culling until all affected chunks are refreshed.
|
|
4094
|
+
*/
|
|
4082
4095
|
refreshBiomes(columns?: readonly {
|
|
4083
4096
|
x: number;
|
|
4084
4097
|
z: number;
|
|
4085
|
-
}[]): Promise<void>;
|
|
4098
|
+
}[], deferCulling?: boolean): Promise<void>;
|
|
4086
4099
|
private refreshBiomeBlocks;
|
|
4087
4100
|
/**
|
|
4088
4101
|
* Places a block at integer world block coordinates. Pass `undefined` to remove it.
|
|
@@ -4093,7 +4106,7 @@ declare class Chunk<SectionMeshing extends boolean = false> {
|
|
|
4093
4106
|
/**
|
|
4094
4107
|
* Places a block at integer chunk-local coordinates from 0 to 15.
|
|
4095
4108
|
*/
|
|
4096
|
-
setBlockInChunkAt(pos: Vector3, block?: Block, worldPos?: Vector3, onBlocksChanged?: ((positions: Vector3[]) => Promise<void>) | undefined): Promise<Maybe<BlockInfo<SectionMeshing>>>;
|
|
4109
|
+
setBlockInChunkAt(pos: Vector3, block?: Block, worldPos?: Vector3, onBlocksChanged?: ((positions: Vector3[], deferCulling?: boolean) => Promise<void>) | undefined): Promise<Maybe<BlockInfo<SectionMeshing>>>;
|
|
4097
4110
|
/** Places cells in input order and returns changed world positions without refreshing neighbors. */
|
|
4098
4111
|
placeBlocks(blocks: readonly {
|
|
4099
4112
|
index: number;
|
|
@@ -4258,6 +4271,14 @@ declare class MineRenderWorld<SectionMeshing extends boolean = false> {
|
|
|
4258
4271
|
* Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
|
|
4259
4272
|
*/
|
|
4260
4273
|
unloadChunkColumn(x: number, z: number): Promise<void>;
|
|
4274
|
+
/**
|
|
4275
|
+
* Removes chunk columns together and refreshes surviving neighbors once.
|
|
4276
|
+
* Coordinates are absolute chunk columns. Duplicate and missing columns are ignored.
|
|
4277
|
+
*/
|
|
4278
|
+
unloadChunkColumns(positions: readonly {
|
|
4279
|
+
x: number;
|
|
4280
|
+
z: number;
|
|
4281
|
+
}[]): Promise<void>;
|
|
4261
4282
|
private clearChunkColumn;
|
|
4262
4283
|
private markSectionChanged;
|
|
4263
4284
|
/** Removes all chunks and releases their blocks, section meshes, and owned entities. */
|
|
@@ -4335,7 +4356,7 @@ declare class WorldStreamer {
|
|
|
4335
4356
|
private running?;
|
|
4336
4357
|
private disposal?;
|
|
4337
4358
|
private disposed;
|
|
4338
|
-
constructor(world: Pick<MineRenderWorld<boolean>, "placeChunk" | "
|
|
4359
|
+
constructor(world: Pick<MineRenderWorld<boolean>, "placeChunk" | "unloadChunkColumns">, source: WorldChunkSource, options?: WorldStreamerOptions);
|
|
4339
4360
|
/** Returns absolute coordinates of successfully placed columns, including retained columns. */
|
|
4340
4361
|
get loadedChunks(): readonly {
|
|
4341
4362
|
x: number;
|
|
@@ -4369,7 +4390,7 @@ declare class WorldStreamer {
|
|
|
4369
4390
|
dispose(): Promise<void>;
|
|
4370
4391
|
private drain;
|
|
4371
4392
|
private needsLoad;
|
|
4372
|
-
private
|
|
4393
|
+
private unloadMany;
|
|
4373
4394
|
private validateChunk;
|
|
4374
4395
|
private within;
|
|
4375
4396
|
private key;
|
package/dist/browser/index.js
CHANGED
|
@@ -13951,13 +13951,16 @@ var StructureParser = class {
|
|
|
13951
13951
|
const blocks = nbt.blocks.value.value.map((block) => {
|
|
13952
13952
|
const state = palette.value.value[block.state.value];
|
|
13953
13953
|
if (!state) throw new MineRenderError(`Structure palette index ${block.state.value} does not exist`);
|
|
13954
|
-
|
|
13954
|
+
const name = (state.Name ?? state.id)?.value;
|
|
13955
|
+
if (!name) throw new MineRenderError(`Structure palette entry ${block.state.value} has no block name`);
|
|
13956
|
+
if (["minecraft:air", "minecraft:cave_air", "minecraft:void_air"].includes(name)) return void 0;
|
|
13955
13957
|
const props = {};
|
|
13956
|
-
|
|
13957
|
-
|
|
13958
|
+
const properties = (state.Properties ?? state.properties)?.value;
|
|
13959
|
+
for (let k in properties) {
|
|
13960
|
+
props[k] = properties[k].value;
|
|
13958
13961
|
}
|
|
13959
13962
|
return {
|
|
13960
|
-
type:
|
|
13963
|
+
type: name,
|
|
13961
13964
|
properties: props,
|
|
13962
13965
|
position: block.pos.value.value,
|
|
13963
13966
|
nbt: block.nbt
|
|
@@ -18686,8 +18689,14 @@ var MineRenderWorld = class {
|
|
|
18686
18689
|
}
|
|
18687
18690
|
return queued;
|
|
18688
18691
|
}
|
|
18689
|
-
async refreshBiomes() {
|
|
18690
|
-
if (this.biomeRefreshing)
|
|
18692
|
+
async refreshBiomes(deferCulling = false) {
|
|
18693
|
+
if (this.biomeRefreshing) {
|
|
18694
|
+
await this.biomeRefreshing;
|
|
18695
|
+
if (!deferCulling) {
|
|
18696
|
+
if (this.pendingCulling.size) await this.updateCulling([]);
|
|
18697
|
+
else await this.culling;
|
|
18698
|
+
}
|
|
18699
|
+
}
|
|
18691
18700
|
if (!this.pendingBiomes.size) return /* @__PURE__ */ new Map();
|
|
18692
18701
|
return this.biomeRefreshing ?? (this.biomeRefreshing = Promise.resolve().then(async () => {
|
|
18693
18702
|
const failures = /* @__PURE__ */ new Map();
|
|
@@ -18699,7 +18708,7 @@ var MineRenderWorld = class {
|
|
|
18699
18708
|
this.pendingBiomes.delete(chunk);
|
|
18700
18709
|
if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) !== chunk) continue;
|
|
18701
18710
|
try {
|
|
18702
|
-
await chunk.refreshBiomes(columns);
|
|
18711
|
+
await chunk.refreshBiomes(columns, deferCulling);
|
|
18703
18712
|
} catch (error) {
|
|
18704
18713
|
failures.set(chunk, error);
|
|
18705
18714
|
if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingBiomes.set(chunk, void 0);
|
|
@@ -18961,15 +18970,35 @@ var MineRenderWorld = class {
|
|
|
18961
18970
|
* Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
|
|
18962
18971
|
*/
|
|
18963
18972
|
async unloadChunkColumn(x, z) {
|
|
18964
|
-
|
|
18965
|
-
|
|
18973
|
+
return this.unloadChunkColumns([{ x, z }]);
|
|
18974
|
+
}
|
|
18975
|
+
/**
|
|
18976
|
+
* Removes chunk columns together and refreshes surviving neighbors once.
|
|
18977
|
+
* Coordinates are absolute chunk columns. Duplicate and missing columns are ignored.
|
|
18978
|
+
*/
|
|
18979
|
+
async unloadChunkColumns(positions) {
|
|
18980
|
+
const columns = /* @__PURE__ */ new Map();
|
|
18981
|
+
for (const position of positions) {
|
|
18982
|
+
const { x, z } = position ?? {};
|
|
18983
|
+
if (!Number.isInteger(x) || !Number.isInteger(z)) {
|
|
18984
|
+
throw new RangeError("Chunk column coordinates must be integers");
|
|
18985
|
+
}
|
|
18986
|
+
columns.set(`${x}_${z}`, { x, z });
|
|
18966
18987
|
}
|
|
18967
|
-
|
|
18988
|
+
if (!columns.size) return;
|
|
18968
18989
|
await this.culling;
|
|
18990
|
+
let sliceStart = performance.now();
|
|
18969
18991
|
try {
|
|
18970
|
-
|
|
18992
|
+
for (const { x, z } of columns.values()) {
|
|
18993
|
+
this.entities?.clearColumn(x, z);
|
|
18994
|
+
await this.clearChunkColumn(x, z);
|
|
18995
|
+
if (performance.now() - sliceStart > 8) {
|
|
18996
|
+
await yieldToEventLoop();
|
|
18997
|
+
sliceStart = performance.now();
|
|
18998
|
+
}
|
|
18999
|
+
}
|
|
18971
19000
|
} finally {
|
|
18972
|
-
const failures = await this.refreshBiomes();
|
|
19001
|
+
const failures = await this.refreshBiomes(true);
|
|
18973
19002
|
if (failures.size) log19.warn("Failed to refresh biome colors", failures.values().next().value);
|
|
18974
19003
|
await this.updateCulling([]);
|
|
18975
19004
|
}
|
|
@@ -19032,7 +19061,7 @@ var MineRenderWorld = class {
|
|
|
19032
19061
|
}
|
|
19033
19062
|
this.sectionModels?.clear();
|
|
19034
19063
|
}
|
|
19035
|
-
updateCulling(positions) {
|
|
19064
|
+
updateCulling(positions, defer = false) {
|
|
19036
19065
|
const neighbor = new import_three45.Vector3();
|
|
19037
19066
|
for (const pos of positions) {
|
|
19038
19067
|
const cx = Math.floor(pos.x / 16), cy = Math.floor(pos.y / 16), cz = Math.floor(pos.z / 16);
|
|
@@ -19054,58 +19083,72 @@ var MineRenderWorld = class {
|
|
|
19054
19083
|
}
|
|
19055
19084
|
}
|
|
19056
19085
|
}
|
|
19086
|
+
if (defer) return Promise.resolve();
|
|
19057
19087
|
return this.culling ?? (this.culling = Promise.resolve().then(async () => {
|
|
19058
19088
|
let sliceStart = performance.now();
|
|
19059
19089
|
try {
|
|
19060
19090
|
const culling = await loadBlockCulling();
|
|
19061
19091
|
while (this.pendingCulling.size) {
|
|
19062
|
-
const batch =
|
|
19092
|
+
const batch = new Map(this.pendingCulling);
|
|
19063
19093
|
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
|
-
|
|
19094
|
+
try {
|
|
19095
|
+
for (const [chunk, indices] of batch) {
|
|
19096
|
+
const neighbors = CUBE_FACE_OFFSETS.map(([x, y, z]) => this._chunks.get(`${chunk.x + x}_${chunk.y + y}_${chunk.z + z}`));
|
|
19097
|
+
const neighborAt = (x, y, z) => {
|
|
19098
|
+
const lx = x - chunk.x * 16, ly = y - chunk.y * 16, lz = z - chunk.z * 16;
|
|
19099
|
+
const section = neighbors[lx >= 16 ? 0 : lx < 0 ? 1 : ly >= 16 ? 2 : ly < 0 ? 3 : lz >= 16 ? 4 : 5];
|
|
19100
|
+
return [section, (ly & 15) * 256 + (lz & 15) * 16 + (lx & 15)];
|
|
19101
|
+
};
|
|
19102
|
+
const masks = chunk.updateCullMasks(indices === "all" ? void 0 : indices, (x, y, z) => {
|
|
19103
|
+
const [section, i] = neighborAt(x, y, z);
|
|
19104
|
+
return section?.isOccludingIndex(i) ?? false;
|
|
19105
|
+
}, culling, (x, y, z) => {
|
|
19106
|
+
const [section, i] = neighborAt(x, y, z);
|
|
19107
|
+
return section?.visibleBlockIndex(i);
|
|
19108
|
+
});
|
|
19109
|
+
const fluidIndices = indices === "all" ? function* () {
|
|
19110
|
+
for (let index = 0; index < 4096; index++) yield index;
|
|
19111
|
+
}() : indices;
|
|
19112
|
+
for (const index of fluidIndices) {
|
|
19113
|
+
if (!chunk.fluidByteIndex(index) || !chunk.isBlockVisibleIndex(index)) continue;
|
|
19114
|
+
const lx = index % 16, ly = Math.floor(index / 256), lz = Math.floor(index / 16) % 16;
|
|
19115
|
+
const wx = chunk.x * 16 + lx, wy = chunk.y * 16 + ly, wz = chunk.z * 16 + lz;
|
|
19116
|
+
const block = chunk.getBlockAt(neighbor.set(wx, wy, wz));
|
|
19117
|
+
if (block.object?.fluidKind) {
|
|
19118
|
+
await block.object.updateFluid((x, y, z) => {
|
|
19119
|
+
const nx = lx + x, ny = ly + y, nz = lz + z;
|
|
19120
|
+
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)}`);
|
|
19121
|
+
const neighborIndex = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
|
|
19122
|
+
const byte = section?.fluidByteIndex(neighborIndex) ?? 0;
|
|
19123
|
+
return {
|
|
19124
|
+
fluid: fluidKindOf(byte),
|
|
19125
|
+
level: byte & 15,
|
|
19126
|
+
solid: section?.isOccludingIndex(neighborIndex) ?? false
|
|
19127
|
+
};
|
|
19128
|
+
});
|
|
19129
|
+
}
|
|
19130
|
+
}
|
|
19131
|
+
for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
|
|
19132
|
+
await chunk.refreshLight(indices === "all" ? void 0 : indices);
|
|
19133
|
+
chunk.refreshAmbientOcclusion(this._ambientOcclusion);
|
|
19134
|
+
if ((this._ambientOcclusion || this.ambientOcclusionPending) && performance.now() - sliceStart > 8) {
|
|
19135
|
+
await yieldToEventLoop();
|
|
19136
|
+
sliceStart = performance.now();
|
|
19098
19137
|
}
|
|
19099
19138
|
}
|
|
19100
|
-
|
|
19101
|
-
|
|
19102
|
-
|
|
19103
|
-
|
|
19104
|
-
|
|
19105
|
-
|
|
19139
|
+
const builds = await Promise.allSettled([...batch.keys()].map(async (chunk) => {
|
|
19140
|
+
await chunk.rebuildSectionMesh();
|
|
19141
|
+
batch.delete(chunk);
|
|
19142
|
+
}));
|
|
19143
|
+
const failure = builds.find((result) => result.status === "rejected");
|
|
19144
|
+
if (failure?.status === "rejected") throw failure.reason;
|
|
19145
|
+
} catch (error) {
|
|
19146
|
+
for (const [chunk, indices] of batch) {
|
|
19147
|
+
const pending = this.pendingCulling.get(chunk);
|
|
19148
|
+
this.pendingCulling.set(chunk, indices === "all" || pending === "all" ? "all" : /* @__PURE__ */ new Set([...indices, ...pending ?? []]));
|
|
19106
19149
|
}
|
|
19150
|
+
throw error;
|
|
19107
19151
|
}
|
|
19108
|
-
await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
|
|
19109
19152
|
}
|
|
19110
19153
|
} finally {
|
|
19111
19154
|
this.culling = void 0;
|
|
@@ -19122,7 +19165,7 @@ var MineRenderWorld = class {
|
|
|
19122
19165
|
x,
|
|
19123
19166
|
y,
|
|
19124
19167
|
z,
|
|
19125
|
-
(positions) => this.updateCulling(positions),
|
|
19168
|
+
(positions, defer) => this.updateCulling(positions, defer),
|
|
19126
19169
|
this.sectionModels,
|
|
19127
19170
|
(x2, y2, z2) => {
|
|
19128
19171
|
const cx = Math.floor(x2 / 16), cy = Math.floor(y2 / 16), cz = Math.floor(z2 / 16);
|
|
@@ -19856,11 +19899,14 @@ var Chunk = class _Chunk {
|
|
|
19856
19899
|
this.biomes = copy;
|
|
19857
19900
|
return this.refreshBiomes();
|
|
19858
19901
|
}
|
|
19859
|
-
/**
|
|
19860
|
-
|
|
19861
|
-
|
|
19902
|
+
/**
|
|
19903
|
+
* Rebuilds biome-tinted blocks while preserving hidden blocks and unrelated render objects.
|
|
19904
|
+
* The owning world can defer neighbor culling until all affected chunks are refreshed.
|
|
19905
|
+
*/
|
|
19906
|
+
refreshBiomes(columns, deferCulling = false) {
|
|
19907
|
+
return this.changeBlocks(() => this.refreshBiomeBlocks(columns, deferCulling), true);
|
|
19862
19908
|
}
|
|
19863
|
-
async refreshBiomeBlocks(columns) {
|
|
19909
|
+
async refreshBiomeBlocks(columns, deferCulling = false) {
|
|
19864
19910
|
const blend = columns && this.biomeBlend?.();
|
|
19865
19911
|
const ownColumn = columns?.some((column) => column.x === this.x && column.z === this.z);
|
|
19866
19912
|
const affected = /* @__PURE__ */ new Map();
|
|
@@ -19917,7 +19963,7 @@ var Chunk = class _Chunk {
|
|
|
19917
19963
|
this.renderedBlocks.get(index)?.object?.setVisible(false);
|
|
19918
19964
|
}
|
|
19919
19965
|
const positions = blocks.map(({ index }) => new import_three47.Vector3(...this.positionAt(index)));
|
|
19920
|
-
if (this.onBlocksChanged) await this.onBlocksChanged(positions);
|
|
19966
|
+
if (this.onBlocksChanged) await this.onBlocksChanged(positions, deferCulling);
|
|
19921
19967
|
else await this.rebuildSectionMesh();
|
|
19922
19968
|
}
|
|
19923
19969
|
}
|
|
@@ -20571,19 +20617,12 @@ var WorldStreamer = class {
|
|
|
20571
20617
|
this.loading?.controller.abort();
|
|
20572
20618
|
return this.disposal ?? (this.disposal = Promise.resolve().then(async () => {
|
|
20573
20619
|
await this.running?.catch(() => void 0);
|
|
20574
|
-
|
|
20575
|
-
|
|
20576
|
-
|
|
20577
|
-
|
|
20578
|
-
|
|
20579
|
-
} catch (error) {
|
|
20580
|
-
failed = true;
|
|
20581
|
-
failure ?? (failure = error);
|
|
20582
|
-
}
|
|
20620
|
+
try {
|
|
20621
|
+
await this.unloadMany([...this.owned.values()]);
|
|
20622
|
+
} finally {
|
|
20623
|
+
this.missing.clear();
|
|
20624
|
+
this.failed.clear();
|
|
20583
20625
|
}
|
|
20584
|
-
this.missing.clear();
|
|
20585
|
-
this.failed.clear();
|
|
20586
|
-
if (failed) throw failure;
|
|
20587
20626
|
}).catch((error) => {
|
|
20588
20627
|
this.disposal = void 0;
|
|
20589
20628
|
throw error;
|
|
@@ -20598,9 +20637,9 @@ var WorldStreamer = class {
|
|
|
20598
20637
|
for (const [key, position2] of this.failed) {
|
|
20599
20638
|
if (!this.within(position2, this.unloadRadius)) this.failed.delete(key);
|
|
20600
20639
|
}
|
|
20601
|
-
const obsolete = [...this.owned.values()].
|
|
20602
|
-
if (obsolete) {
|
|
20603
|
-
await this.
|
|
20640
|
+
const obsolete = [...this.owned.values()].filter((position2) => !this.loaded.has(this.key(position2)) || !this.within(position2, this.unloadRadius));
|
|
20641
|
+
if (obsolete.length) {
|
|
20642
|
+
await this.unloadMany(obsolete);
|
|
20604
20643
|
continue;
|
|
20605
20644
|
}
|
|
20606
20645
|
const position = this.desired.find((position2) => this.needsLoad(position2));
|
|
@@ -20630,7 +20669,7 @@ var WorldStreamer = class {
|
|
|
20630
20669
|
this.loaded.set(this.key(position), position);
|
|
20631
20670
|
} catch (error) {
|
|
20632
20671
|
try {
|
|
20633
|
-
await this.
|
|
20672
|
+
await this.unloadMany([position]);
|
|
20634
20673
|
} catch (cleanupError) {
|
|
20635
20674
|
const failure = error instanceof Error ? error : new Error(String(error));
|
|
20636
20675
|
try {
|
|
@@ -20651,10 +20690,18 @@ var WorldStreamer = class {
|
|
|
20651
20690
|
const key = this.key(position);
|
|
20652
20691
|
return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);
|
|
20653
20692
|
}
|
|
20654
|
-
async
|
|
20655
|
-
|
|
20656
|
-
|
|
20657
|
-
|
|
20693
|
+
async unloadMany(positions) {
|
|
20694
|
+
if (!positions.length) return;
|
|
20695
|
+
try {
|
|
20696
|
+
await this.world.unloadChunkColumns(positions.map(({ x, z }) => ({ x, z })));
|
|
20697
|
+
} catch (error) {
|
|
20698
|
+
for (const position of positions) this.loaded.delete(this.key(position));
|
|
20699
|
+
throw error;
|
|
20700
|
+
}
|
|
20701
|
+
for (const position of positions) {
|
|
20702
|
+
this.owned.delete(this.key(position));
|
|
20703
|
+
this.loaded.delete(this.key(position));
|
|
20704
|
+
}
|
|
20658
20705
|
}
|
|
20659
20706
|
validateChunk(chunk, position) {
|
|
20660
20707
|
if (chunk.x !== position.x || chunk.z !== position.z) {
|