minerender 2.1.0-alpha.142 → 2.1.0-alpha.144
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/dist/browser/esm/index.browser.d.mts +21 -4
- package/dist/browser/esm/util/util.mjs +0 -2
- package/dist/browser/esm/util/util.mjs.map +1 -1
- package/dist/browser/esm/world/Chunk.mjs +62 -33
- package/dist/browser/esm/world/Chunk.mjs.map +1 -1
- package/dist/browser/esm/world/MineRenderWorld.mjs +98 -13
- package/dist/browser/esm/world/MineRenderWorld.mjs.map +1 -1
- package/dist/browser/esm/world/SectionModels.mjs +21 -15
- package/dist/browser/esm/world/SectionModels.mjs.map +1 -1
- package/dist/browser/esm/world/WorldStreamer.mjs +109 -15
- package/dist/browser/esm/world/WorldStreamer.mjs.map +1 -1
- package/dist/browser/index.d.ts +21 -4
- package/dist/browser/index.js +289 -78
- package/dist/browser/index.js.map +1 -1
- package/dist/bundle.js +289 -78
- package/dist/bundle.js.map +1 -1
- package/dist/node/index.d.mts +21 -4
- package/dist/node/index.d.ts +21 -4
- package/dist/node/index.js +289 -78
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +289 -78
- package/dist/node/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/browser/index.js
CHANGED
|
@@ -264,8 +264,6 @@ async function sleep(timeout) {
|
|
|
264
264
|
});
|
|
265
265
|
}
|
|
266
266
|
function yieldToEventLoop() {
|
|
267
|
-
const scheduler = globalThis.scheduler;
|
|
268
|
-
if (scheduler?.yield) return scheduler.yield();
|
|
269
267
|
return new Promise((resolve) => {
|
|
270
268
|
if (typeof MessageChannel !== "undefined") {
|
|
271
269
|
const channel = new MessageChannel();
|
|
@@ -18065,6 +18063,7 @@ var AnvilWorldSource = class {
|
|
|
18065
18063
|
};
|
|
18066
18064
|
|
|
18067
18065
|
// src/world/Chunk.ts
|
|
18066
|
+
init_BlockObject();
|
|
18068
18067
|
var import_three47 = require("three");
|
|
18069
18068
|
init_BlockStates();
|
|
18070
18069
|
init_AssetKey();
|
|
@@ -18075,6 +18074,7 @@ init_Model();
|
|
|
18075
18074
|
init_util();
|
|
18076
18075
|
init_AssetKey();
|
|
18077
18076
|
init_BlockStates();
|
|
18077
|
+
init_BlockEntities();
|
|
18078
18078
|
init_CubeFace();
|
|
18079
18079
|
|
|
18080
18080
|
// src/world/SectionModels.ts
|
|
@@ -18172,6 +18172,10 @@ var SectionModels = class {
|
|
|
18172
18172
|
biomeTint: source ? [...templates.values()].some((template) => template.biomeTint?.some((value) => value === 1)) || void 0 : void 0
|
|
18173
18173
|
};
|
|
18174
18174
|
}
|
|
18175
|
+
/** Starts loading the state's models and atlases so a later `prepareState` with the same properties finds them loaded. */
|
|
18176
|
+
async preload(blockState3, properties = {}) {
|
|
18177
|
+
await this.state(blockState3, properties);
|
|
18178
|
+
}
|
|
18175
18179
|
async state(blockState3, properties) {
|
|
18176
18180
|
let states = this.states.get(blockState3);
|
|
18177
18181
|
if (!states) this.states.set(blockState3, states = /* @__PURE__ */ new Map());
|
|
@@ -18194,22 +18198,24 @@ var SectionModels = class {
|
|
|
18194
18198
|
async prepare(blockState3, properties) {
|
|
18195
18199
|
const state = { ...await BlockStateResolver.defaults(blockState3), ...properties };
|
|
18196
18200
|
const groups = BlockStateResolver.matching(blockState3, state);
|
|
18197
|
-
if (!groups.length) return void 0;
|
|
18201
|
+
if (!groups.length || groups.some((group) => Array.isArray(group) && !group.length)) return void 0;
|
|
18202
|
+
const variants = groups.flatMap((group) => Array.isArray(group) ? group : [group]);
|
|
18203
|
+
if (variants.some((variant) => !variant.model)) return void 0;
|
|
18204
|
+
const settled = await Promise.allSettled(variants.map(async (variant) => {
|
|
18205
|
+
const modelKey = AssetKey.parse("models", variant.model);
|
|
18206
|
+
modelKey.root = blockState3.key?.root;
|
|
18207
|
+
const model = await Models.getMerged(modelKey);
|
|
18208
|
+
if (!model?.elements?.length) return void 0;
|
|
18209
|
+
const atlas = await UVMapper.getAtlas(model);
|
|
18210
|
+
const rotation = BlockStateResolver.rotation(variant);
|
|
18211
|
+
if (!atlas || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return void 0;
|
|
18212
|
+
return { model, atlas, rotation };
|
|
18213
|
+
}));
|
|
18198
18214
|
const prepared = /* @__PURE__ */ new Map();
|
|
18199
|
-
for (const
|
|
18200
|
-
|
|
18201
|
-
if (!
|
|
18202
|
-
|
|
18203
|
-
if (!variant.model) return void 0;
|
|
18204
|
-
const modelKey = AssetKey.parse("models", variant.model);
|
|
18205
|
-
modelKey.root = blockState3.key?.root;
|
|
18206
|
-
const model = await Models.getMerged(modelKey);
|
|
18207
|
-
if (!model?.elements?.length) return void 0;
|
|
18208
|
-
const atlas = await UVMapper.getAtlas(model);
|
|
18209
|
-
const rotation = BlockStateResolver.rotation(variant);
|
|
18210
|
-
if (!atlas || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return void 0;
|
|
18211
|
-
prepared.set(variant, { model, atlas, rotation });
|
|
18212
|
-
}
|
|
18215
|
+
for (const [i, result] of settled.entries()) {
|
|
18216
|
+
if (result.status === "rejected") throw result.reason;
|
|
18217
|
+
if (!result.value) return void 0;
|
|
18218
|
+
prepared.set(variants[i], result.value);
|
|
18213
18219
|
}
|
|
18214
18220
|
return { state, groups, choices: prepared };
|
|
18215
18221
|
}
|
|
@@ -18669,6 +18675,7 @@ function skipsBlockFace(data, rule, block, neighbor, face) {
|
|
|
18669
18675
|
// src/world/MineRenderWorld.ts
|
|
18670
18676
|
init_log();
|
|
18671
18677
|
var log19 = logger("MineRenderWorld");
|
|
18678
|
+
var YIELD_INTERVAL_MS = 8;
|
|
18672
18679
|
var MineRenderWorld = class {
|
|
18673
18680
|
constructor(scene, options = {}) {
|
|
18674
18681
|
this._chunks = /* @__PURE__ */ new Map();
|
|
@@ -18782,7 +18789,7 @@ var MineRenderWorld = class {
|
|
|
18782
18789
|
failures.set(chunk, error);
|
|
18783
18790
|
if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingBiomes.set(chunk, void 0);
|
|
18784
18791
|
}
|
|
18785
|
-
if (performance.now() - sliceStart >
|
|
18792
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
18786
18793
|
await yieldToEventLoop();
|
|
18787
18794
|
sliceStart = performance.now();
|
|
18788
18795
|
}
|
|
@@ -18883,15 +18890,18 @@ var MineRenderWorld = class {
|
|
|
18883
18890
|
const key = AssetKey.parse("blockstates", block.type);
|
|
18884
18891
|
keys.set(key.serialize(), key);
|
|
18885
18892
|
}
|
|
18886
|
-
await BlockStates.getAll(keys.values());
|
|
18893
|
+
const loaded = await BlockStates.getAll(keys.values());
|
|
18894
|
+
const states = new Map([...keys.keys()].map((key, i) => [key, loaded[i]]));
|
|
18887
18895
|
if (!useBatches) {
|
|
18888
18896
|
for (const block of multiblock.blocks) await place(block);
|
|
18889
18897
|
return;
|
|
18890
18898
|
}
|
|
18891
18899
|
if (!executor) {
|
|
18892
18900
|
const groups2 = /* @__PURE__ */ new Map();
|
|
18901
|
+
const distinct = /* @__PURE__ */ new Map();
|
|
18893
18902
|
const position = new import_three45.Vector3();
|
|
18894
18903
|
for (const block of multiblock.blocks) {
|
|
18904
|
+
if (this.sectionModels && !Chunk.isAir(block)) distinct.set(ChunkData.paletteKey(block), block);
|
|
18895
18905
|
const [x, y, z] = block.position;
|
|
18896
18906
|
this.validatePosBounds(position.set(x, y, z));
|
|
18897
18907
|
const cx = Math.floor(x / 16), cy = Math.floor(y / 16), cz = Math.floor(z / 16);
|
|
@@ -18902,6 +18912,16 @@ var MineRenderWorld = class {
|
|
|
18902
18912
|
if (!group) groups2.set(chunk, group = []);
|
|
18903
18913
|
group.push({ index: (y - cy * 16) * 256 + (z - cz * 16) * 16 + x - cx * 16, block });
|
|
18904
18914
|
}
|
|
18915
|
+
if (this.sectionModels) {
|
|
18916
|
+
for (const block of distinct.values()) {
|
|
18917
|
+
const state = states.get(AssetKey.parse("blockstates", block.type).serialize());
|
|
18918
|
+
if (!state) continue;
|
|
18919
|
+
void (async () => {
|
|
18920
|
+
if (state.key && BlockEntities.entry(await BlockEntities.getIndex(state.key.root), state.key.toNamespacedString())) return;
|
|
18921
|
+
await this.sectionModels.preload(state, block.properties);
|
|
18922
|
+
})().catch(() => void 0);
|
|
18923
|
+
}
|
|
18924
|
+
}
|
|
18905
18925
|
await this.placeChunkGroups(groups2, changes);
|
|
18906
18926
|
return;
|
|
18907
18927
|
}
|
|
@@ -18937,7 +18957,7 @@ var MineRenderWorld = class {
|
|
|
18937
18957
|
)));
|
|
18938
18958
|
for (const pos of positions) changes.set(`${pos.x},${pos.y},${pos.z}`, pos);
|
|
18939
18959
|
}
|
|
18940
|
-
if (performance.now() - sliceStart >
|
|
18960
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
18941
18961
|
await yieldToEventLoop();
|
|
18942
18962
|
sliceStart = performance.now();
|
|
18943
18963
|
}
|
|
@@ -18945,6 +18965,62 @@ var MineRenderWorld = class {
|
|
|
18945
18965
|
}
|
|
18946
18966
|
/** Replaces a chunk column's blocks, biomes, and saved light and, with `renderEntities`, its supported saved mobs. */
|
|
18947
18967
|
async placeChunk(chunk, executor) {
|
|
18968
|
+
return this.placeColumn(chunk, executor);
|
|
18969
|
+
}
|
|
18970
|
+
/**
|
|
18971
|
+
* Replaces parsed columns in input order, then refreshes neighboring geometry once.
|
|
18972
|
+
* Duplicate coordinates retain their order. A failed column stops placement; queued refreshes finish before rejection.
|
|
18973
|
+
*/
|
|
18974
|
+
async placeChunks(chunks) {
|
|
18975
|
+
const columns = [];
|
|
18976
|
+
for (const chunk of chunks) {
|
|
18977
|
+
const { x, z } = chunk ?? {};
|
|
18978
|
+
if (![x, z].every(Number.isSafeInteger)) {
|
|
18979
|
+
throw new RangeError("Chunk column coordinates must be safe integers");
|
|
18980
|
+
}
|
|
18981
|
+
const sections = Array.from(chunk.sections, (section) => {
|
|
18982
|
+
const { y } = section ?? {};
|
|
18983
|
+
if (!Number.isSafeInteger(y)) throw new RangeError("Chunk section coordinates must be safe integers");
|
|
18984
|
+
return { ...section, y };
|
|
18985
|
+
});
|
|
18986
|
+
columns.push({ ...chunk, x, z, sections });
|
|
18987
|
+
}
|
|
18988
|
+
if (!columns.length) return;
|
|
18989
|
+
await this.culling;
|
|
18990
|
+
let failed = false, failure, sliceStart = performance.now();
|
|
18991
|
+
try {
|
|
18992
|
+
for (const column of columns) {
|
|
18993
|
+
await this.placeColumn(column, void 0, true);
|
|
18994
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
18995
|
+
await yieldToEventLoop();
|
|
18996
|
+
sliceStart = performance.now();
|
|
18997
|
+
}
|
|
18998
|
+
}
|
|
18999
|
+
} catch (error) {
|
|
19000
|
+
failed = true;
|
|
19001
|
+
failure = error;
|
|
19002
|
+
} finally {
|
|
19003
|
+
try {
|
|
19004
|
+
const failures = await this.refreshBiomes(true);
|
|
19005
|
+
if (failures.size) log19.warn("Failed to refresh biome colors", failures.values().next().value);
|
|
19006
|
+
} catch (error) {
|
|
19007
|
+
if (!failed) {
|
|
19008
|
+
failed = true;
|
|
19009
|
+
failure = error;
|
|
19010
|
+
}
|
|
19011
|
+
}
|
|
19012
|
+
try {
|
|
19013
|
+
await this.updateCulling([]);
|
|
19014
|
+
} catch (error) {
|
|
19015
|
+
if (!failed) {
|
|
19016
|
+
failed = true;
|
|
19017
|
+
failure = error;
|
|
19018
|
+
}
|
|
19019
|
+
}
|
|
19020
|
+
}
|
|
19021
|
+
if (failed) throw failure;
|
|
19022
|
+
}
|
|
19023
|
+
async placeColumn(chunk, executor, deferCulling = false) {
|
|
18948
19024
|
if (![chunk.x, chunk.z].every(Number.isSafeInteger)) {
|
|
18949
19025
|
throw new RangeError("Chunk column coordinates must be safe integers");
|
|
18950
19026
|
}
|
|
@@ -19006,7 +19082,7 @@ var MineRenderWorld = class {
|
|
|
19006
19082
|
} finally {
|
|
19007
19083
|
this.markSectionChanged(target);
|
|
19008
19084
|
}
|
|
19009
|
-
if (performance.now() - sliceStart >
|
|
19085
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
19010
19086
|
await yieldToEventLoop();
|
|
19011
19087
|
sliceStart = performance.now();
|
|
19012
19088
|
}
|
|
@@ -19029,9 +19105,12 @@ var MineRenderWorld = class {
|
|
|
19029
19105
|
await placeEntities?.();
|
|
19030
19106
|
} finally {
|
|
19031
19107
|
this.markBiomesChanged(chunk.x, chunk.z, void 0, false);
|
|
19032
|
-
|
|
19033
|
-
|
|
19034
|
-
|
|
19108
|
+
if (deferCulling) await this.updateCulling([...changes.values()], true);
|
|
19109
|
+
else {
|
|
19110
|
+
const failures = await this.refreshBiomes();
|
|
19111
|
+
if (failures.size) log19.warn("Failed to refresh biome colors", failures.values().next().value);
|
|
19112
|
+
await this.updateCulling([...changes.values()]);
|
|
19113
|
+
}
|
|
19035
19114
|
}
|
|
19036
19115
|
}
|
|
19037
19116
|
/**
|
|
@@ -19061,7 +19140,7 @@ var MineRenderWorld = class {
|
|
|
19061
19140
|
for (const { x, z } of columns.values()) {
|
|
19062
19141
|
this.entities?.clearColumn(x, z);
|
|
19063
19142
|
await this.clearChunkColumn(x, z);
|
|
19064
|
-
if (performance.now() - sliceStart >
|
|
19143
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
19065
19144
|
await yieldToEventLoop();
|
|
19066
19145
|
sliceStart = performance.now();
|
|
19067
19146
|
}
|
|
@@ -19200,15 +19279,25 @@ var MineRenderWorld = class {
|
|
|
19200
19279
|
for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
|
|
19201
19280
|
await chunk.refreshLight(indices === "all" ? void 0 : indices);
|
|
19202
19281
|
chunk.refreshAmbientOcclusion(this._ambientOcclusion);
|
|
19203
|
-
if (
|
|
19282
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
19204
19283
|
await yieldToEventLoop();
|
|
19205
19284
|
sliceStart = performance.now();
|
|
19206
19285
|
}
|
|
19207
19286
|
}
|
|
19208
|
-
const
|
|
19209
|
-
|
|
19210
|
-
|
|
19211
|
-
|
|
19287
|
+
const pending = [];
|
|
19288
|
+
for (const chunk of [...batch.keys()]) {
|
|
19289
|
+
const build = (async () => {
|
|
19290
|
+
await chunk.rebuildSectionMesh();
|
|
19291
|
+
batch.delete(chunk);
|
|
19292
|
+
})();
|
|
19293
|
+
void build.catch(() => void 0);
|
|
19294
|
+
pending.push(build);
|
|
19295
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
19296
|
+
await yieldToEventLoop();
|
|
19297
|
+
sliceStart = performance.now();
|
|
19298
|
+
}
|
|
19299
|
+
}
|
|
19300
|
+
const builds = await Promise.allSettled(pending);
|
|
19212
19301
|
const failure = builds.find((result) => result.status === "rejected");
|
|
19213
19302
|
if (failure?.status === "rejected") throw failure.reason;
|
|
19214
19303
|
} catch (error) {
|
|
@@ -20159,8 +20248,10 @@ var Chunk = class _Chunk {
|
|
|
20159
20248
|
if (rejected?.status === "rejected") throw rejected.reason;
|
|
20160
20249
|
}
|
|
20161
20250
|
const positions = [];
|
|
20162
|
-
|
|
20163
|
-
|
|
20251
|
+
const jobs = /* @__PURE__ */ new Map();
|
|
20252
|
+
let failureOrder = Infinity, failure;
|
|
20253
|
+
for (const [order, { index, key, block }] of placements.entries()) {
|
|
20254
|
+
jobs.delete(index);
|
|
20164
20255
|
this.hiddenBlocks.delete(index);
|
|
20165
20256
|
this.data.assign(index, this.data.intern(block), block?.nbt);
|
|
20166
20257
|
const readBlock = this.data.snapshot(index);
|
|
@@ -20176,7 +20267,6 @@ var Chunk = class _Chunk {
|
|
|
20176
20267
|
this.z * 16 + Math.floor(index / 16) % 16
|
|
20177
20268
|
);
|
|
20178
20269
|
positions.push(worldPos);
|
|
20179
|
-
let object4;
|
|
20180
20270
|
try {
|
|
20181
20271
|
if (!readBlock) continue;
|
|
20182
20272
|
const resolved = resolutions.get(key);
|
|
@@ -20198,7 +20288,7 @@ var Chunk = class _Chunk {
|
|
|
20198
20288
|
this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0, biomeTint: context?.tint })));
|
|
20199
20289
|
this.meshDirty = true;
|
|
20200
20290
|
} else {
|
|
20201
|
-
|
|
20291
|
+
jobs.set(index, { order, readBlock, blockState: blockState3, position: MineRenderWorld.worldToScenePosition(worldPos), options: {
|
|
20202
20292
|
mergeMeshes: true,
|
|
20203
20293
|
instanceMeshes: true,
|
|
20204
20294
|
maxInstanceCount: 2e3,
|
|
@@ -20206,13 +20296,12 @@ var Chunk = class _Chunk {
|
|
|
20206
20296
|
variantPosition,
|
|
20207
20297
|
biome: context?.biome,
|
|
20208
20298
|
biomePosition: this.biomePosition(index, blockState3, stored)
|
|
20209
|
-
});
|
|
20210
|
-
|
|
20299
|
+
} });
|
|
20300
|
+
continue;
|
|
20211
20301
|
}
|
|
20212
20302
|
this.renderedBlocks.set(index, { get block() {
|
|
20213
20303
|
return readBlock();
|
|
20214
|
-
}, object:
|
|
20215
|
-
if (object4) this.objectBlocks.add(index);
|
|
20304
|
+
}, object: void 0 });
|
|
20216
20305
|
} catch (error) {
|
|
20217
20306
|
if (preserveDataOnFailure && readBlock) {
|
|
20218
20307
|
this.renderedBlocks.set(index, { get block() {
|
|
@@ -20220,12 +20309,35 @@ var Chunk = class _Chunk {
|
|
|
20220
20309
|
}, object: void 0 });
|
|
20221
20310
|
} else this.data.assign(index, 0);
|
|
20222
20311
|
this.fluidCells[index] = 0;
|
|
20223
|
-
|
|
20224
|
-
|
|
20225
|
-
|
|
20312
|
+
if (order < failureOrder) {
|
|
20313
|
+
failureOrder = order;
|
|
20314
|
+
failure = error;
|
|
20315
|
+
}
|
|
20226
20316
|
}
|
|
20227
20317
|
}
|
|
20228
|
-
|
|
20318
|
+
const pending = [...jobs];
|
|
20319
|
+
const objects = await Promise.allSettled(pending.map(([, job]) => this.createBlockObject(job.blockState, job.position, job.options)));
|
|
20320
|
+
for (const [i, [index, { order, readBlock }]] of pending.entries()) {
|
|
20321
|
+
const result = objects[i];
|
|
20322
|
+
if (result.status === "fulfilled") {
|
|
20323
|
+
this.renderedBlocks.set(index, { get block() {
|
|
20324
|
+
return readBlock();
|
|
20325
|
+
}, object: result.value });
|
|
20326
|
+
this.objectBlocks.add(index);
|
|
20327
|
+
} else {
|
|
20328
|
+
if (preserveDataOnFailure) {
|
|
20329
|
+
this.renderedBlocks.set(index, { get block() {
|
|
20330
|
+
return readBlock();
|
|
20331
|
+
}, object: void 0 });
|
|
20332
|
+
} else this.data.assign(index, 0);
|
|
20333
|
+
this.fluidCells[index] = 0;
|
|
20334
|
+
if (order < failureOrder) {
|
|
20335
|
+
failureOrder = order;
|
|
20336
|
+
failure = result.reason;
|
|
20337
|
+
}
|
|
20338
|
+
}
|
|
20339
|
+
}
|
|
20340
|
+
if (failureOrder < Infinity) throw failure;
|
|
20229
20341
|
return positions;
|
|
20230
20342
|
}
|
|
20231
20343
|
/** Replaces every cell from parsed section data. The caller refreshes neighbor culling. */
|
|
@@ -20296,37 +20408,42 @@ var Chunk = class _Chunk {
|
|
|
20296
20408
|
failed = true;
|
|
20297
20409
|
}
|
|
20298
20410
|
}
|
|
20299
|
-
|
|
20300
|
-
|
|
20301
|
-
|
|
20302
|
-
|
|
20303
|
-
|
|
20304
|
-
|
|
20305
|
-
|
|
20306
|
-
|
|
20307
|
-
|
|
20308
|
-
|
|
20309
|
-
|
|
20310
|
-
|
|
20311
|
-
|
|
20312
|
-
|
|
20313
|
-
|
|
20314
|
-
|
|
20315
|
-
)
|
|
20316
|
-
this.renderedBlocks.get(index).object = object4;
|
|
20411
|
+
const objects = await Promise.allSettled(perBlocks.map(({ index, blockState: blockState3, biome }) => this.createBlockObject(blockState3, new import_three47.Vector3(
|
|
20412
|
+
this.x * 256 + index % 16 * 16,
|
|
20413
|
+
this.y * 256 + Math.floor(index / 256) * 16,
|
|
20414
|
+
this.z * 256 + Math.floor(index / 16) % 16 * 16
|
|
20415
|
+
), {
|
|
20416
|
+
mergeMeshes: true,
|
|
20417
|
+
instanceMeshes: true,
|
|
20418
|
+
maxInstanceCount: 2e3,
|
|
20419
|
+
initialState: this.data.stateAt(this.data.idAt(index)).properties,
|
|
20420
|
+
variantPosition: this.positionAt(index),
|
|
20421
|
+
biome,
|
|
20422
|
+
biomePosition: this.biomePosition(index, blockState3, this.data.stateAt(this.data.idAt(index)))
|
|
20423
|
+
})));
|
|
20424
|
+
for (const [i, { index }] of perBlocks.entries()) {
|
|
20425
|
+
const result = objects[i];
|
|
20426
|
+
if (result.status === "fulfilled") {
|
|
20427
|
+
this.renderedBlocks.get(index).object = result.value;
|
|
20317
20428
|
this.objectBlocks.add(index);
|
|
20318
|
-
}
|
|
20429
|
+
} else {
|
|
20319
20430
|
this.data.assign(index, 0);
|
|
20320
20431
|
this.fluidCells[index] = 0;
|
|
20321
20432
|
this.renderedBlocks.delete(index);
|
|
20322
20433
|
this.objectBlocks.delete(index);
|
|
20323
|
-
|
|
20324
|
-
if (!failed) failure = error;
|
|
20434
|
+
if (!failed) failure = result.reason;
|
|
20325
20435
|
failed = true;
|
|
20326
20436
|
}
|
|
20327
20437
|
}
|
|
20328
20438
|
if (failed) throw failure;
|
|
20329
20439
|
}
|
|
20440
|
+
createBlockObject(blockState3, position, options) {
|
|
20441
|
+
return this.scene.addSceneObject(blockState3, () => {
|
|
20442
|
+
const object4 = new BlockObject(blockState3, options);
|
|
20443
|
+
object4.position.copy(position);
|
|
20444
|
+
return object4;
|
|
20445
|
+
}, options);
|
|
20446
|
+
}
|
|
20330
20447
|
positionAt(index) {
|
|
20331
20448
|
return [this.x * 16 + index % 16, this.y * 16 + Math.floor(index / 256), this.z * 16 + Math.floor(index / 16) % 16];
|
|
20332
20449
|
}
|
|
@@ -20667,6 +20784,7 @@ var Chunk = class _Chunk {
|
|
|
20667
20784
|
};
|
|
20668
20785
|
|
|
20669
20786
|
// src/world/WorldStreamer.ts
|
|
20787
|
+
var MAX_PREFETCHED_COLUMNS = 4;
|
|
20670
20788
|
var WorldStreamer = class {
|
|
20671
20789
|
constructor(world, source, options = {}) {
|
|
20672
20790
|
this.world = world;
|
|
@@ -20676,6 +20794,7 @@ var WorldStreamer = class {
|
|
|
20676
20794
|
this.missing = /* @__PURE__ */ new Map();
|
|
20677
20795
|
this.failed = /* @__PURE__ */ new Map();
|
|
20678
20796
|
this.desired = [];
|
|
20797
|
+
this.reads = /* @__PURE__ */ new Map();
|
|
20679
20798
|
this.disposed = false;
|
|
20680
20799
|
this.loadRadius = options.loadRadius ?? 2;
|
|
20681
20800
|
this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);
|
|
@@ -20720,6 +20839,12 @@ var WorldStreamer = class {
|
|
|
20720
20839
|
if (this.loading && !this.within(this.loading.position, this.loadRadius)) {
|
|
20721
20840
|
this.loading.controller.abort();
|
|
20722
20841
|
}
|
|
20842
|
+
for (const [key, read] of this.reads) {
|
|
20843
|
+
if (!this.within(read.position, this.loadRadius)) {
|
|
20844
|
+
read.controller.abort();
|
|
20845
|
+
if (read.ready) this.reads.delete(key);
|
|
20846
|
+
}
|
|
20847
|
+
}
|
|
20723
20848
|
}
|
|
20724
20849
|
return this.running ?? (this.running = Promise.resolve().then(() => this.drain()));
|
|
20725
20850
|
}
|
|
@@ -20740,8 +20865,11 @@ var WorldStreamer = class {
|
|
|
20740
20865
|
this.desired = [];
|
|
20741
20866
|
this.center = void 0;
|
|
20742
20867
|
this.loading?.controller.abort();
|
|
20868
|
+
for (const read of this.reads.values()) read.controller.abort();
|
|
20743
20869
|
return this.disposal ?? (this.disposal = Promise.resolve().then(async () => {
|
|
20744
20870
|
await this.running?.catch(() => void 0);
|
|
20871
|
+
await Promise.all([...this.reads.values()].map((read) => read.promise));
|
|
20872
|
+
this.reads.clear();
|
|
20745
20873
|
try {
|
|
20746
20874
|
await this.unloadMany([...this.owned.values()]);
|
|
20747
20875
|
} finally {
|
|
@@ -20767,6 +20895,10 @@ var WorldStreamer = class {
|
|
|
20767
20895
|
await this.unloadMany(obsolete);
|
|
20768
20896
|
continue;
|
|
20769
20897
|
}
|
|
20898
|
+
if (this.world.placeChunks) {
|
|
20899
|
+
if (await this.placeReadyChunks()) continue;
|
|
20900
|
+
return;
|
|
20901
|
+
}
|
|
20770
20902
|
const position = this.desired.find((position2) => this.needsLoad(position2));
|
|
20771
20903
|
if (!position) return;
|
|
20772
20904
|
let chunk;
|
|
@@ -20789,26 +20921,105 @@ var WorldStreamer = class {
|
|
|
20789
20921
|
continue;
|
|
20790
20922
|
}
|
|
20791
20923
|
this.owned.set(this.key(position), position);
|
|
20924
|
+
await this.placeOwnedColumns([position], () => this.world.placeChunk(chunk));
|
|
20925
|
+
}
|
|
20926
|
+
} finally {
|
|
20927
|
+
for (const [key, read] of this.reads) {
|
|
20928
|
+
read.controller.abort();
|
|
20929
|
+
if (read.ready) this.reads.delete(key);
|
|
20930
|
+
}
|
|
20931
|
+
this.running = void 0;
|
|
20932
|
+
}
|
|
20933
|
+
}
|
|
20934
|
+
fillReadSlots() {
|
|
20935
|
+
while (!this.disposed && this.reads.size < MAX_PREFETCHED_COLUMNS) {
|
|
20936
|
+
const position = this.desired.find((position2) => this.needsLoad(position2) && !this.reads.has(this.key(position2)));
|
|
20937
|
+
if (!position) return;
|
|
20938
|
+
const key = this.key(position);
|
|
20939
|
+
const read = {
|
|
20940
|
+
position,
|
|
20941
|
+
controller: new AbortController(),
|
|
20942
|
+
promise: Promise.resolve(),
|
|
20943
|
+
ready: false,
|
|
20944
|
+
failed: false
|
|
20945
|
+
};
|
|
20946
|
+
this.reads.set(key, read);
|
|
20947
|
+
read.promise = (async () => {
|
|
20792
20948
|
try {
|
|
20793
|
-
await this.
|
|
20794
|
-
this.
|
|
20949
|
+
read.chunk = await this.source.getChunk(position.x, position.z, read.controller.signal);
|
|
20950
|
+
if (read.chunk) this.validateChunk(read.chunk, position);
|
|
20795
20951
|
} catch (error) {
|
|
20796
|
-
|
|
20797
|
-
|
|
20798
|
-
|
|
20799
|
-
|
|
20800
|
-
|
|
20801
|
-
|
|
20802
|
-
} catch {
|
|
20803
|
-
throw Object.assign(new Error(failure.message), { cause: cleanupError });
|
|
20804
|
-
}
|
|
20805
|
-
throw failure;
|
|
20952
|
+
read.failed = true;
|
|
20953
|
+
read.error = error;
|
|
20954
|
+
} finally {
|
|
20955
|
+
read.ready = true;
|
|
20956
|
+
if (read.controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) {
|
|
20957
|
+
this.reads.delete(key);
|
|
20806
20958
|
}
|
|
20807
|
-
|
|
20959
|
+
}
|
|
20960
|
+
})();
|
|
20961
|
+
}
|
|
20962
|
+
}
|
|
20963
|
+
/** Places only the ready prefix, so later source reads cannot delay the nearest ready column. */
|
|
20964
|
+
async placeReadyChunks() {
|
|
20965
|
+
this.fillReadSlots();
|
|
20966
|
+
const position = this.desired.find((position2) => this.needsLoad(position2));
|
|
20967
|
+
if (!position) {
|
|
20968
|
+
if (!this.reads.size) return false;
|
|
20969
|
+
await Promise.all([...this.reads.values()].map((read) => read.promise));
|
|
20970
|
+
return true;
|
|
20971
|
+
}
|
|
20972
|
+
const head = this.reads.get(this.key(position));
|
|
20973
|
+
if (!head) {
|
|
20974
|
+
for (let index = this.desired.length - 1; index >= 0; index--) {
|
|
20975
|
+
const key = this.key(this.desired[index]);
|
|
20976
|
+
if (this.reads.get(key)?.ready) {
|
|
20977
|
+
this.reads.delete(key);
|
|
20978
|
+
return true;
|
|
20808
20979
|
}
|
|
20809
20980
|
}
|
|
20810
|
-
|
|
20811
|
-
|
|
20981
|
+
await Promise.race([...this.reads.values()].map((read) => read.promise));
|
|
20982
|
+
return true;
|
|
20983
|
+
}
|
|
20984
|
+
if (!head.ready) {
|
|
20985
|
+
await head.promise;
|
|
20986
|
+
return true;
|
|
20987
|
+
}
|
|
20988
|
+
const chunks = [], positions = [];
|
|
20989
|
+
for (const desired of this.desired) {
|
|
20990
|
+
if (!this.needsLoad(desired)) continue;
|
|
20991
|
+
const key = this.key(desired), read = this.reads.get(key);
|
|
20992
|
+
if (!read?.ready) break;
|
|
20993
|
+
this.reads.delete(key);
|
|
20994
|
+
if (read.failed) this.failed.set(key, { ...desired, error: read.error });
|
|
20995
|
+
else if (!read.chunk) this.missing.set(key, desired);
|
|
20996
|
+
else {
|
|
20997
|
+
chunks.push(read.chunk);
|
|
20998
|
+
positions.push(desired);
|
|
20999
|
+
}
|
|
21000
|
+
}
|
|
21001
|
+
if (!chunks.length) return true;
|
|
21002
|
+
for (const position2 of positions) this.owned.set(this.key(position2), position2);
|
|
21003
|
+
await this.placeOwnedColumns(positions, () => this.world.placeChunks(chunks));
|
|
21004
|
+
return true;
|
|
21005
|
+
}
|
|
21006
|
+
async placeOwnedColumns(positions, place) {
|
|
21007
|
+
try {
|
|
21008
|
+
await place();
|
|
21009
|
+
for (const position of positions) this.loaded.set(this.key(position), position);
|
|
21010
|
+
} catch (error) {
|
|
21011
|
+
try {
|
|
21012
|
+
await this.unloadMany(positions);
|
|
21013
|
+
} catch (cleanupError) {
|
|
21014
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
21015
|
+
try {
|
|
21016
|
+
Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
|
|
21017
|
+
} catch {
|
|
21018
|
+
throw Object.assign(new Error(failure.message), { cause: cleanupError });
|
|
21019
|
+
}
|
|
21020
|
+
throw failure;
|
|
21021
|
+
}
|
|
21022
|
+
throw error;
|
|
20812
21023
|
}
|
|
20813
21024
|
}
|
|
20814
21025
|
needsLoad(position) {
|