minecraft-renderer 0.1.25 → 0.1.27
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/package.json
CHANGED
|
@@ -119,7 +119,7 @@ export interface GraphicsBackend {
|
|
|
119
119
|
updateCamera(pos: Vec3 | null, yaw: number, pitch: number): void
|
|
120
120
|
soundSystem?: any
|
|
121
121
|
backendMethods?: any
|
|
122
|
-
getDebugOverlay?(): { entitiesString?: string, right?: string }
|
|
122
|
+
getDebugOverlay?(): { entitiesString?: string, left?: Record<string, string>, right?: Record<string, string> }
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/** Graphics backend loader function type */
|
|
@@ -108,6 +108,13 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
108
108
|
protocolCustomBlocks = new Map<string, CustomBlockModels>()
|
|
109
109
|
private heightmapDebounceTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
|
110
110
|
|
|
111
|
+
// Geometry throttle: first dirty per section is instant, subsequent within window are grouped
|
|
112
|
+
private sectionDirtyCount = new Map<string, number>()
|
|
113
|
+
private sectionDirtyTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
|
114
|
+
private sectionDirtyPendingArgs = new Map<string, { pos: Vec3; value: boolean; useChangeWorker: boolean }>()
|
|
115
|
+
private static readonly GEOMETRY_THROTTLE_THRESHOLD = 1
|
|
116
|
+
private static readonly GEOMETRY_THROTTLE_DELAY = 100 // ms
|
|
117
|
+
|
|
111
118
|
blockStateModelInfo = new Map<string, BlockStateModelInfo>()
|
|
112
119
|
|
|
113
120
|
abstract outputFormat: 'threeJs' | 'webgpu'
|
|
@@ -682,6 +689,15 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
682
689
|
clearTimeout(pendingTimer)
|
|
683
690
|
this.heightmapDebounceTimers.delete(debounceKey)
|
|
684
691
|
}
|
|
692
|
+
// Cancel any pending geometry throttle timers for sections in this chunk
|
|
693
|
+
for (const [key, timer] of this.sectionDirtyTimers) {
|
|
694
|
+
if (key.startsWith(`${x},`) && key.endsWith(`,${z}`)) {
|
|
695
|
+
clearTimeout(timer)
|
|
696
|
+
this.sectionDirtyTimers.delete(key)
|
|
697
|
+
this.sectionDirtyCount.delete(key)
|
|
698
|
+
this.sectionDirtyPendingArgs.delete(key)
|
|
699
|
+
}
|
|
700
|
+
}
|
|
685
701
|
for (const worker of this.workers) {
|
|
686
702
|
worker.postMessage({ type: 'unloadChunk', x, z })
|
|
687
703
|
}
|
|
@@ -955,14 +971,65 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
955
971
|
if (!this.forceCallFromMesherReplayer && this.mesherLogReader) return
|
|
956
972
|
|
|
957
973
|
if (this.viewDistance === -1) throw new Error('viewDistance not set')
|
|
958
|
-
|
|
974
|
+
|
|
959
975
|
const distance = this.getDistance(pos)
|
|
960
976
|
// todo shouldnt we check loadedChunks instead?
|
|
961
977
|
if (!this.workers.length || distance[0] > this.viewDistance || distance[1] > this.viewDistance) return
|
|
978
|
+
|
|
979
|
+
// When unloading chunks (value=false) — always immediate, no throttle
|
|
980
|
+
if (!value) {
|
|
981
|
+
this._dispatchDirtyImmediate(pos, value, useChangeWorker)
|
|
982
|
+
return
|
|
983
|
+
}
|
|
984
|
+
|
|
962
985
|
const CHUNK_SIZE = 16
|
|
963
986
|
const sectionHeight = this.getSectionHeight()
|
|
964
987
|
const key = `${Math.floor(pos.x / CHUNK_SIZE) * CHUNK_SIZE},${Math.floor(pos.y / sectionHeight) * sectionHeight},${Math.floor(pos.z / CHUNK_SIZE) * CHUNK_SIZE}`
|
|
965
|
-
|
|
988
|
+
|
|
989
|
+
const currentCount = (this.sectionDirtyCount.get(key) ?? 0) + 1
|
|
990
|
+
this.sectionDirtyCount.set(key, currentCount)
|
|
991
|
+
|
|
992
|
+
if (currentCount <= WorldRendererCommon.GEOMETRY_THROTTLE_THRESHOLD) {
|
|
993
|
+
// First request in window — dispatch immediately for instant feedback
|
|
994
|
+
this._dispatchDirtyImmediate(pos, value, useChangeWorker)
|
|
995
|
+
|
|
996
|
+
// Schedule trailing dispatch after throttle window
|
|
997
|
+
if (!this.sectionDirtyTimers.has(key)) {
|
|
998
|
+
this.sectionDirtyTimers.set(key, setTimeout(() => {
|
|
999
|
+
const args = this.sectionDirtyPendingArgs.get(key)
|
|
1000
|
+
this.sectionDirtyCount.delete(key)
|
|
1001
|
+
this.sectionDirtyTimers.delete(key)
|
|
1002
|
+
this.sectionDirtyPendingArgs.delete(key)
|
|
1003
|
+
if (args) {
|
|
1004
|
+
this._dispatchDirtyImmediate(args.pos, args.value, args.useChangeWorker)
|
|
1005
|
+
}
|
|
1006
|
+
}, WorldRendererCommon.GEOMETRY_THROTTLE_DELAY))
|
|
1007
|
+
}
|
|
1008
|
+
} else {
|
|
1009
|
+
// Subsequent requests — throttle: store latest args, existing timer will dispatch
|
|
1010
|
+
this.sectionDirtyPendingArgs.set(key, { pos, value, useChangeWorker })
|
|
1011
|
+
|
|
1012
|
+
if (!this.sectionDirtyTimers.has(key)) {
|
|
1013
|
+
this.sectionDirtyTimers.set(key, setTimeout(() => {
|
|
1014
|
+
const args = this.sectionDirtyPendingArgs.get(key)
|
|
1015
|
+
this.sectionDirtyCount.delete(key)
|
|
1016
|
+
this.sectionDirtyTimers.delete(key)
|
|
1017
|
+
this.sectionDirtyPendingArgs.delete(key)
|
|
1018
|
+
if (args) {
|
|
1019
|
+
this._dispatchDirtyImmediate(args.pos, args.value, args.useChangeWorker)
|
|
1020
|
+
}
|
|
1021
|
+
}, WorldRendererCommon.GEOMETRY_THROTTLE_DELAY))
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/** Dispatch dirty message to worker without throttle (original logic) */
|
|
1027
|
+
private _dispatchDirtyImmediate(pos: Vec3, value: boolean, useChangeWorker: boolean) {
|
|
1028
|
+
this.reactiveState.world.mesherWork = true
|
|
1029
|
+
const CHUNK_SIZE = 16
|
|
1030
|
+
const sectionHeight = this.getSectionHeight()
|
|
1031
|
+
const key = `${Math.floor(pos.x / CHUNK_SIZE) * CHUNK_SIZE},${Math.floor(pos.y / sectionHeight) * sectionHeight},${Math.floor(pos.z / CHUNK_SIZE) * CHUNK_SIZE}`
|
|
1032
|
+
|
|
966
1033
|
this.renderUpdateEmitter.emit('dirty', pos, value)
|
|
967
1034
|
// Dispatch sections to workers based on position
|
|
968
1035
|
// This guarantees uniformity accross workers and that a given section
|
|
@@ -981,7 +1048,6 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
981
1048
|
} else {
|
|
982
1049
|
this.toWorkerMessagesQueue[hash] ??= []
|
|
983
1050
|
this.toWorkerMessagesQueue[hash].push({
|
|
984
|
-
// this.workers[hash].postMessage({
|
|
985
1051
|
type: 'dirty',
|
|
986
1052
|
x: pos.x,
|
|
987
1053
|
y: pos.y,
|
|
@@ -1054,6 +1120,14 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
1054
1120
|
}
|
|
1055
1121
|
this.heightmapDebounceTimers.clear()
|
|
1056
1122
|
|
|
1123
|
+
// Cancel all pending geometry throttle timers
|
|
1124
|
+
for (const timer of this.sectionDirtyTimers.values()) {
|
|
1125
|
+
clearTimeout(timer)
|
|
1126
|
+
}
|
|
1127
|
+
this.sectionDirtyTimers.clear()
|
|
1128
|
+
this.sectionDirtyCount.clear()
|
|
1129
|
+
this.sectionDirtyPendingArgs.clear()
|
|
1130
|
+
|
|
1057
1131
|
// Stop all workers
|
|
1058
1132
|
for (const worker of this.workers) {
|
|
1059
1133
|
worker.terminate()
|
|
@@ -215,6 +215,11 @@ export const createGraphicsBackendBase = () => {
|
|
|
215
215
|
get entitiesString() {
|
|
216
216
|
return worldRenderer?.entities.getDebugString()
|
|
217
217
|
},
|
|
218
|
+
get left() {
|
|
219
|
+
return {
|
|
220
|
+
'Geo Memory': worldRenderer?.worldBlockGeometry.getEstimatedMemoryUsage().readable ?? '-'
|
|
221
|
+
}
|
|
222
|
+
},
|
|
218
223
|
}),
|
|
219
224
|
updateCamera(pos: Vec3 | null, yaw: number, pitch: number) {
|
|
220
225
|
// Mark camera update event for frame timing visualization
|