minerender 2.1.0-alpha.142 → 2.1.0-alpha.143
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 +16 -2
- package/dist/browser/esm/util/util.mjs +0 -2
- package/dist/browser/esm/util/util.mjs.map +1 -1
- package/dist/browser/esm/world/MineRenderWorld.mjs +82 -12
- package/dist/browser/esm/world/MineRenderWorld.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 +16 -2
- package/dist/browser/index.js +191 -29
- package/dist/browser/index.js.map +1 -1
- package/dist/bundle.js +191 -29
- package/dist/bundle.js.map +1 -1
- package/dist/node/index.d.mts +16 -2
- package/dist/node/index.d.ts +16 -2
- package/dist/node/index.js +191 -29
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +191 -29
- package/dist/node/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/node/index.mjs
CHANGED
|
@@ -184,8 +184,6 @@ async function sleep(timeout) {
|
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
186
|
function yieldToEventLoop() {
|
|
187
|
-
const scheduler = globalThis.scheduler;
|
|
188
|
-
if (scheduler?.yield) return scheduler.yield();
|
|
189
187
|
return new Promise((resolve) => {
|
|
190
188
|
if (typeof MessageChannel !== "undefined") {
|
|
191
189
|
const channel = new MessageChannel();
|
|
@@ -18528,6 +18526,7 @@ function skipsBlockFace(data, rule, block, neighbor, face) {
|
|
|
18528
18526
|
// src/world/MineRenderWorld.ts
|
|
18529
18527
|
init_log();
|
|
18530
18528
|
var log18 = logger("MineRenderWorld");
|
|
18529
|
+
var YIELD_INTERVAL_MS = 8;
|
|
18531
18530
|
var MineRenderWorld = class {
|
|
18532
18531
|
constructor(scene, options = {}) {
|
|
18533
18532
|
this._chunks = /* @__PURE__ */ new Map();
|
|
@@ -18641,7 +18640,7 @@ var MineRenderWorld = class {
|
|
|
18641
18640
|
failures.set(chunk, error);
|
|
18642
18641
|
if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingBiomes.set(chunk, void 0);
|
|
18643
18642
|
}
|
|
18644
|
-
if (performance.now() - sliceStart >
|
|
18643
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
18645
18644
|
await yieldToEventLoop();
|
|
18646
18645
|
sliceStart = performance.now();
|
|
18647
18646
|
}
|
|
@@ -18796,7 +18795,7 @@ var MineRenderWorld = class {
|
|
|
18796
18795
|
));
|
|
18797
18796
|
for (const pos of positions) changes.set(`${pos.x},${pos.y},${pos.z}`, pos);
|
|
18798
18797
|
}
|
|
18799
|
-
if (performance.now() - sliceStart >
|
|
18798
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
18800
18799
|
await yieldToEventLoop();
|
|
18801
18800
|
sliceStart = performance.now();
|
|
18802
18801
|
}
|
|
@@ -18804,6 +18803,62 @@ var MineRenderWorld = class {
|
|
|
18804
18803
|
}
|
|
18805
18804
|
/** Replaces a chunk column's blocks, biomes, and saved light and, with `renderEntities`, its supported saved mobs. */
|
|
18806
18805
|
async placeChunk(chunk, executor) {
|
|
18806
|
+
return this.placeColumn(chunk, executor);
|
|
18807
|
+
}
|
|
18808
|
+
/**
|
|
18809
|
+
* Replaces parsed columns in input order, then refreshes neighboring geometry once.
|
|
18810
|
+
* Duplicate coordinates retain their order. A failed column stops placement; queued refreshes finish before rejection.
|
|
18811
|
+
*/
|
|
18812
|
+
async placeChunks(chunks) {
|
|
18813
|
+
const columns = [];
|
|
18814
|
+
for (const chunk of chunks) {
|
|
18815
|
+
const { x, z } = chunk ?? {};
|
|
18816
|
+
if (![x, z].every(Number.isSafeInteger)) {
|
|
18817
|
+
throw new RangeError("Chunk column coordinates must be safe integers");
|
|
18818
|
+
}
|
|
18819
|
+
const sections = Array.from(chunk.sections, (section) => {
|
|
18820
|
+
const { y } = section ?? {};
|
|
18821
|
+
if (!Number.isSafeInteger(y)) throw new RangeError("Chunk section coordinates must be safe integers");
|
|
18822
|
+
return { ...section, y };
|
|
18823
|
+
});
|
|
18824
|
+
columns.push({ ...chunk, x, z, sections });
|
|
18825
|
+
}
|
|
18826
|
+
if (!columns.length) return;
|
|
18827
|
+
await this.culling;
|
|
18828
|
+
let failed = false, failure, sliceStart = performance.now();
|
|
18829
|
+
try {
|
|
18830
|
+
for (const column of columns) {
|
|
18831
|
+
await this.placeColumn(column, void 0, true);
|
|
18832
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
18833
|
+
await yieldToEventLoop();
|
|
18834
|
+
sliceStart = performance.now();
|
|
18835
|
+
}
|
|
18836
|
+
}
|
|
18837
|
+
} catch (error) {
|
|
18838
|
+
failed = true;
|
|
18839
|
+
failure = error;
|
|
18840
|
+
} finally {
|
|
18841
|
+
try {
|
|
18842
|
+
const failures = await this.refreshBiomes(true);
|
|
18843
|
+
if (failures.size) log18.warn("Failed to refresh biome colors", failures.values().next().value);
|
|
18844
|
+
} catch (error) {
|
|
18845
|
+
if (!failed) {
|
|
18846
|
+
failed = true;
|
|
18847
|
+
failure = error;
|
|
18848
|
+
}
|
|
18849
|
+
}
|
|
18850
|
+
try {
|
|
18851
|
+
await this.updateCulling([]);
|
|
18852
|
+
} catch (error) {
|
|
18853
|
+
if (!failed) {
|
|
18854
|
+
failed = true;
|
|
18855
|
+
failure = error;
|
|
18856
|
+
}
|
|
18857
|
+
}
|
|
18858
|
+
}
|
|
18859
|
+
if (failed) throw failure;
|
|
18860
|
+
}
|
|
18861
|
+
async placeColumn(chunk, executor, deferCulling = false) {
|
|
18807
18862
|
if (![chunk.x, chunk.z].every(Number.isSafeInteger)) {
|
|
18808
18863
|
throw new RangeError("Chunk column coordinates must be safe integers");
|
|
18809
18864
|
}
|
|
@@ -18865,7 +18920,7 @@ var MineRenderWorld = class {
|
|
|
18865
18920
|
} finally {
|
|
18866
18921
|
this.markSectionChanged(target);
|
|
18867
18922
|
}
|
|
18868
|
-
if (performance.now() - sliceStart >
|
|
18923
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
18869
18924
|
await yieldToEventLoop();
|
|
18870
18925
|
sliceStart = performance.now();
|
|
18871
18926
|
}
|
|
@@ -18888,9 +18943,12 @@ var MineRenderWorld = class {
|
|
|
18888
18943
|
await placeEntities?.();
|
|
18889
18944
|
} finally {
|
|
18890
18945
|
this.markBiomesChanged(chunk.x, chunk.z, void 0, false);
|
|
18891
|
-
|
|
18892
|
-
|
|
18893
|
-
|
|
18946
|
+
if (deferCulling) await this.updateCulling([...changes.values()], true);
|
|
18947
|
+
else {
|
|
18948
|
+
const failures = await this.refreshBiomes();
|
|
18949
|
+
if (failures.size) log18.warn("Failed to refresh biome colors", failures.values().next().value);
|
|
18950
|
+
await this.updateCulling([...changes.values()]);
|
|
18951
|
+
}
|
|
18894
18952
|
}
|
|
18895
18953
|
}
|
|
18896
18954
|
/**
|
|
@@ -18920,7 +18978,7 @@ var MineRenderWorld = class {
|
|
|
18920
18978
|
for (const { x, z } of columns.values()) {
|
|
18921
18979
|
this.entities?.clearColumn(x, z);
|
|
18922
18980
|
await this.clearChunkColumn(x, z);
|
|
18923
|
-
if (performance.now() - sliceStart >
|
|
18981
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
18924
18982
|
await yieldToEventLoop();
|
|
18925
18983
|
sliceStart = performance.now();
|
|
18926
18984
|
}
|
|
@@ -19059,15 +19117,25 @@ var MineRenderWorld = class {
|
|
|
19059
19117
|
for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
|
|
19060
19118
|
await chunk.refreshLight(indices === "all" ? void 0 : indices);
|
|
19061
19119
|
chunk.refreshAmbientOcclusion(this._ambientOcclusion);
|
|
19062
|
-
if (
|
|
19120
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
19063
19121
|
await yieldToEventLoop();
|
|
19064
19122
|
sliceStart = performance.now();
|
|
19065
19123
|
}
|
|
19066
19124
|
}
|
|
19067
|
-
const
|
|
19068
|
-
|
|
19069
|
-
|
|
19070
|
-
|
|
19125
|
+
const pending = [];
|
|
19126
|
+
for (const chunk of [...batch.keys()]) {
|
|
19127
|
+
const build = (async () => {
|
|
19128
|
+
await chunk.rebuildSectionMesh();
|
|
19129
|
+
batch.delete(chunk);
|
|
19130
|
+
})();
|
|
19131
|
+
void build.catch(() => void 0);
|
|
19132
|
+
pending.push(build);
|
|
19133
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
19134
|
+
await yieldToEventLoop();
|
|
19135
|
+
sliceStart = performance.now();
|
|
19136
|
+
}
|
|
19137
|
+
}
|
|
19138
|
+
const builds = await Promise.allSettled(pending);
|
|
19071
19139
|
const failure = builds.find((result) => result.status === "rejected");
|
|
19072
19140
|
if (failure?.status === "rejected") throw failure.reason;
|
|
19073
19141
|
} catch (error) {
|
|
@@ -20527,6 +20595,7 @@ var Chunk = class _Chunk {
|
|
|
20527
20595
|
};
|
|
20528
20596
|
|
|
20529
20597
|
// src/world/WorldStreamer.ts
|
|
20598
|
+
var MAX_PREFETCHED_COLUMNS = 4;
|
|
20530
20599
|
var WorldStreamer = class {
|
|
20531
20600
|
constructor(world, source, options = {}) {
|
|
20532
20601
|
this.world = world;
|
|
@@ -20536,6 +20605,7 @@ var WorldStreamer = class {
|
|
|
20536
20605
|
this.missing = /* @__PURE__ */ new Map();
|
|
20537
20606
|
this.failed = /* @__PURE__ */ new Map();
|
|
20538
20607
|
this.desired = [];
|
|
20608
|
+
this.reads = /* @__PURE__ */ new Map();
|
|
20539
20609
|
this.disposed = false;
|
|
20540
20610
|
this.loadRadius = options.loadRadius ?? 2;
|
|
20541
20611
|
this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);
|
|
@@ -20580,6 +20650,12 @@ var WorldStreamer = class {
|
|
|
20580
20650
|
if (this.loading && !this.within(this.loading.position, this.loadRadius)) {
|
|
20581
20651
|
this.loading.controller.abort();
|
|
20582
20652
|
}
|
|
20653
|
+
for (const [key, read] of this.reads) {
|
|
20654
|
+
if (!this.within(read.position, this.loadRadius)) {
|
|
20655
|
+
read.controller.abort();
|
|
20656
|
+
if (read.ready) this.reads.delete(key);
|
|
20657
|
+
}
|
|
20658
|
+
}
|
|
20583
20659
|
}
|
|
20584
20660
|
return this.running ??= Promise.resolve().then(() => this.drain());
|
|
20585
20661
|
}
|
|
@@ -20600,8 +20676,11 @@ var WorldStreamer = class {
|
|
|
20600
20676
|
this.desired = [];
|
|
20601
20677
|
this.center = void 0;
|
|
20602
20678
|
this.loading?.controller.abort();
|
|
20679
|
+
for (const read of this.reads.values()) read.controller.abort();
|
|
20603
20680
|
return this.disposal ??= Promise.resolve().then(async () => {
|
|
20604
20681
|
await this.running?.catch(() => void 0);
|
|
20682
|
+
await Promise.all([...this.reads.values()].map((read) => read.promise));
|
|
20683
|
+
this.reads.clear();
|
|
20605
20684
|
try {
|
|
20606
20685
|
await this.unloadMany([...this.owned.values()]);
|
|
20607
20686
|
} finally {
|
|
@@ -20627,6 +20706,10 @@ var WorldStreamer = class {
|
|
|
20627
20706
|
await this.unloadMany(obsolete);
|
|
20628
20707
|
continue;
|
|
20629
20708
|
}
|
|
20709
|
+
if (this.world.placeChunks) {
|
|
20710
|
+
if (await this.placeReadyChunks()) continue;
|
|
20711
|
+
return;
|
|
20712
|
+
}
|
|
20630
20713
|
const position = this.desired.find((position2) => this.needsLoad(position2));
|
|
20631
20714
|
if (!position) return;
|
|
20632
20715
|
let chunk;
|
|
@@ -20649,26 +20732,105 @@ var WorldStreamer = class {
|
|
|
20649
20732
|
continue;
|
|
20650
20733
|
}
|
|
20651
20734
|
this.owned.set(this.key(position), position);
|
|
20735
|
+
await this.placeOwnedColumns([position], () => this.world.placeChunk(chunk));
|
|
20736
|
+
}
|
|
20737
|
+
} finally {
|
|
20738
|
+
for (const [key, read] of this.reads) {
|
|
20739
|
+
read.controller.abort();
|
|
20740
|
+
if (read.ready) this.reads.delete(key);
|
|
20741
|
+
}
|
|
20742
|
+
this.running = void 0;
|
|
20743
|
+
}
|
|
20744
|
+
}
|
|
20745
|
+
fillReadSlots() {
|
|
20746
|
+
while (!this.disposed && this.reads.size < MAX_PREFETCHED_COLUMNS) {
|
|
20747
|
+
const position = this.desired.find((position2) => this.needsLoad(position2) && !this.reads.has(this.key(position2)));
|
|
20748
|
+
if (!position) return;
|
|
20749
|
+
const key = this.key(position);
|
|
20750
|
+
const read = {
|
|
20751
|
+
position,
|
|
20752
|
+
controller: new AbortController(),
|
|
20753
|
+
promise: Promise.resolve(),
|
|
20754
|
+
ready: false,
|
|
20755
|
+
failed: false
|
|
20756
|
+
};
|
|
20757
|
+
this.reads.set(key, read);
|
|
20758
|
+
read.promise = (async () => {
|
|
20652
20759
|
try {
|
|
20653
|
-
await this.
|
|
20654
|
-
this.
|
|
20760
|
+
read.chunk = await this.source.getChunk(position.x, position.z, read.controller.signal);
|
|
20761
|
+
if (read.chunk) this.validateChunk(read.chunk, position);
|
|
20655
20762
|
} catch (error) {
|
|
20656
|
-
|
|
20657
|
-
|
|
20658
|
-
|
|
20659
|
-
|
|
20660
|
-
|
|
20661
|
-
|
|
20662
|
-
} catch {
|
|
20663
|
-
throw Object.assign(new Error(failure.message), { cause: cleanupError });
|
|
20664
|
-
}
|
|
20665
|
-
throw failure;
|
|
20763
|
+
read.failed = true;
|
|
20764
|
+
read.error = error;
|
|
20765
|
+
} finally {
|
|
20766
|
+
read.ready = true;
|
|
20767
|
+
if (read.controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) {
|
|
20768
|
+
this.reads.delete(key);
|
|
20666
20769
|
}
|
|
20667
|
-
|
|
20770
|
+
}
|
|
20771
|
+
})();
|
|
20772
|
+
}
|
|
20773
|
+
}
|
|
20774
|
+
/** Places only the ready prefix, so later source reads cannot delay the nearest ready column. */
|
|
20775
|
+
async placeReadyChunks() {
|
|
20776
|
+
this.fillReadSlots();
|
|
20777
|
+
const position = this.desired.find((position2) => this.needsLoad(position2));
|
|
20778
|
+
if (!position) {
|
|
20779
|
+
if (!this.reads.size) return false;
|
|
20780
|
+
await Promise.all([...this.reads.values()].map((read) => read.promise));
|
|
20781
|
+
return true;
|
|
20782
|
+
}
|
|
20783
|
+
const head = this.reads.get(this.key(position));
|
|
20784
|
+
if (!head) {
|
|
20785
|
+
for (let index = this.desired.length - 1; index >= 0; index--) {
|
|
20786
|
+
const key = this.key(this.desired[index]);
|
|
20787
|
+
if (this.reads.get(key)?.ready) {
|
|
20788
|
+
this.reads.delete(key);
|
|
20789
|
+
return true;
|
|
20668
20790
|
}
|
|
20669
20791
|
}
|
|
20670
|
-
|
|
20671
|
-
|
|
20792
|
+
await Promise.race([...this.reads.values()].map((read) => read.promise));
|
|
20793
|
+
return true;
|
|
20794
|
+
}
|
|
20795
|
+
if (!head.ready) {
|
|
20796
|
+
await head.promise;
|
|
20797
|
+
return true;
|
|
20798
|
+
}
|
|
20799
|
+
const chunks = [], positions = [];
|
|
20800
|
+
for (const desired of this.desired) {
|
|
20801
|
+
if (!this.needsLoad(desired)) continue;
|
|
20802
|
+
const key = this.key(desired), read = this.reads.get(key);
|
|
20803
|
+
if (!read?.ready) break;
|
|
20804
|
+
this.reads.delete(key);
|
|
20805
|
+
if (read.failed) this.failed.set(key, { ...desired, error: read.error });
|
|
20806
|
+
else if (!read.chunk) this.missing.set(key, desired);
|
|
20807
|
+
else {
|
|
20808
|
+
chunks.push(read.chunk);
|
|
20809
|
+
positions.push(desired);
|
|
20810
|
+
}
|
|
20811
|
+
}
|
|
20812
|
+
if (!chunks.length) return true;
|
|
20813
|
+
for (const position2 of positions) this.owned.set(this.key(position2), position2);
|
|
20814
|
+
await this.placeOwnedColumns(positions, () => this.world.placeChunks(chunks));
|
|
20815
|
+
return true;
|
|
20816
|
+
}
|
|
20817
|
+
async placeOwnedColumns(positions, place) {
|
|
20818
|
+
try {
|
|
20819
|
+
await place();
|
|
20820
|
+
for (const position of positions) this.loaded.set(this.key(position), position);
|
|
20821
|
+
} catch (error) {
|
|
20822
|
+
try {
|
|
20823
|
+
await this.unloadMany(positions);
|
|
20824
|
+
} catch (cleanupError) {
|
|
20825
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
20826
|
+
try {
|
|
20827
|
+
Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
|
|
20828
|
+
} catch {
|
|
20829
|
+
throw Object.assign(new Error(failure.message), { cause: cleanupError });
|
|
20830
|
+
}
|
|
20831
|
+
throw failure;
|
|
20832
|
+
}
|
|
20833
|
+
throw error;
|
|
20672
20834
|
}
|
|
20673
20835
|
}
|
|
20674
20836
|
needsLoad(position) {
|