minecraft-renderer 0.1.29 → 0.1.30

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecraft-renderer",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "description": "The most Modular Minecraft world renderer with Three.js WebGL backend",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -4,7 +4,6 @@ import { Vec3 } from 'vec3'
4
4
  import nbt from 'prismarine-nbt'
5
5
  import { MesherGeometryOutput, IS_FULL_WORLD_SECTION } from '../mesher/shared'
6
6
  import { getBannerTexture, createBannerMesh, releaseBannerTexture } from './bannerRenderer'
7
- import { disposeObject } from './threeJsUtils'
8
7
  import type { WorldRendererThree } from './worldRendererThree'
9
8
 
10
9
  export interface SectionObject extends THREE.Object3D {
@@ -17,6 +16,9 @@ export class WorldBlockGeometry {
17
16
  sectionObjects: Record<string, SectionObject> = {}
18
17
  waitingChunksToDisplay: { [chunkKey: string]: string[] } = {}
19
18
  estimatedMemoryUsage = 0
19
+ private pendingUpdates: Map<string, { geometry: MesherGeometryOutput; key: string; type: string }> = new Map()
20
+ private pendingBufferStartTime: number | null = null
21
+ private static readonly MAX_BUFFER_MS = 500
20
22
 
21
23
  constructor(
22
24
  private readonly worldRenderer: WorldRendererThree,
@@ -26,21 +28,84 @@ export class WorldBlockGeometry {
26
28
  ) { }
27
29
 
28
30
  handleWorkerGeometryMessage(data: { geometry: MesherGeometryOutput; key: string; type: string }): void {
29
- let object: THREE.Object3D = this.sectionObjects[data.key]
30
- if (object) {
31
- // Track memory usage removal for existing section
32
- this.removeSectionMemoryUsage(object)
33
- // Cleanup banner textures before disposing
34
- object.traverse((child) => {
35
- if ((child as any).bannerTexture) {
36
- releaseBannerTexture((child as any).bannerTexture)
31
+ const isUpdate = !!this.sectionObjects[data.key]
32
+
33
+ if (isUpdate) {
34
+ // Buffer updates for existing sections — keep old mesh visible
35
+ this.pendingUpdates.set(data.key, data)
36
+ if (this.pendingBufferStartTime === null) {
37
+ this.pendingBufferStartTime = performance.now()
38
+ }
39
+ return
40
+ }
41
+
42
+ // Initial load — apply immediately
43
+ this._applySectionGeometry(data)
44
+ }
45
+
46
+ applyPendingUpdates(): void {
47
+ if (this.pendingUpdates.size === 0) return
48
+
49
+ const now = performance.now()
50
+ const sinceFirst = now - (this.pendingBufferStartTime ?? now)
51
+
52
+ // Wait for neighboring sections still being meshed (unless max timeout reached)
53
+ if (sinceFirst < WorldBlockGeometry.MAX_BUFFER_MS) {
54
+ const sectionHeight = this.worldRenderer.getSectionHeight()
55
+ for (const key of this.pendingUpdates.keys()) {
56
+ const [sx, sy, sz] = key.split(',').map(Number)
57
+ const neighborKeys = [
58
+ `${sx - 16},${sy},${sz}`, `${sx + 16},${sy},${sz}`,
59
+ `${sx},${sy - sectionHeight},${sz}`, `${sx},${sy + sectionHeight},${sz}`,
60
+ `${sx},${sy},${sz - 16}`, `${sx},${sy},${sz + 16}`,
61
+ ]
62
+ for (const neighborKey of neighborKeys) {
63
+ // Wait if neighbor is being meshed, hasn't arrived yet, and has a loaded mesh
64
+ if (this.worldRenderer.sectionsWaiting.has(neighborKey) &&
65
+ !this.pendingUpdates.has(neighborKey) &&
66
+ this.sectionObjects[neighborKey]) {
67
+ return
68
+ }
37
69
  }
38
- })
39
- this.worldRenderer.sceneOrigin.removeAndUntrackAll(object)
40
- disposeObject(object)
41
- delete this.sectionObjects[data.key]
70
+ }
42
71
  }
43
72
 
73
+ // Flush all pending updates atomically
74
+ for (const [key, data] of this.pendingUpdates) {
75
+ this._removeSectionSafely(key)
76
+ this._applySectionGeometry(data)
77
+ }
78
+
79
+ this.pendingUpdates.clear()
80
+ this.pendingBufferStartTime = null
81
+ }
82
+
83
+ private disposeSectionObject(obj: THREE.Object3D): void {
84
+ if (obj instanceof THREE.Mesh) {
85
+ obj.geometry?.dispose?.()
86
+ // Don't dispose material - it's shared across all sections
87
+ }
88
+ if (obj.children) {
89
+ obj.children.forEach(child => this.disposeSectionObject(child))
90
+ }
91
+ }
92
+
93
+ private _removeSectionSafely(key: string): void {
94
+ const object = this.sectionObjects[key]
95
+ if (!object) return
96
+
97
+ this.removeSectionMemoryUsage(object)
98
+ object.traverse((child) => {
99
+ if ((child as any).bannerTexture) {
100
+ releaseBannerTexture((child as any).bannerTexture)
101
+ }
102
+ })
103
+ this.worldRenderer.sceneOrigin.removeAndUntrackAll(object)
104
+ this.disposeSectionObject(object)
105
+ delete this.sectionObjects[key]
106
+ }
107
+
108
+ private _applySectionGeometry(data: { geometry: MesherGeometryOutput; key: string; type: string }): void {
44
109
  const chunkCoords = data.key.split(',')
45
110
  if (
46
111
  !this.worldRenderer.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] ||
@@ -69,7 +134,7 @@ export class WorldBlockGeometry {
69
134
  this.worldRenderer.sceneOrigin.track(mesh, { updateMatrix: true })
70
135
  mesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz)
71
136
  mesh.name = 'mesh'
72
- object = new THREE.Group()
137
+ const object: THREE.Object3D = new THREE.Group()
73
138
  object.add(mesh)
74
139
  // mesh with static dimensions: 16x16xsectionHeight
75
140
  const sectionHeight = data.geometry.sectionEndY - data.geometry.sectionStartY
@@ -238,6 +303,8 @@ export class WorldBlockGeometry {
238
303
 
239
304
  // Reset memory tracking since all sections are cleared
240
305
  this.estimatedMemoryUsage = 0
306
+ this.pendingUpdates.clear()
307
+ this.pendingBufferStartTime = null
241
308
  }
242
309
 
243
310
  removeColumn(x: number, z: number) {
@@ -259,9 +326,10 @@ export class WorldBlockGeometry {
259
326
  }
260
327
  })
261
328
  this.worldRenderer.sceneOrigin.removeAndUntrackAll(mesh)
262
- disposeObject(mesh)
329
+ this.disposeSectionObject(mesh)
263
330
  }
264
331
  delete this.sectionObjects[key]
332
+ this.pendingUpdates.delete(key)
265
333
  } else {
266
334
  for (let y = worldMinY; y < this.worldRenderer.worldSizeParams.worldHeight; y += sectionHeight) {
267
335
  this.worldRenderer.setSectionDirty(new Vec3(x, y, z), false)
@@ -277,9 +345,10 @@ export class WorldBlockGeometry {
277
345
  }
278
346
  })
279
347
  this.worldRenderer.sceneOrigin.removeAndUntrackAll(mesh)
280
- disposeObject(mesh)
348
+ this.disposeSectionObject(mesh)
281
349
  }
282
350
  delete this.sectionObjects[key]
351
+ this.pendingUpdates.delete(key)
283
352
  }
284
353
  }
285
354
  }
@@ -1065,6 +1065,8 @@ export class WorldRendererThree extends WorldRendererCommon {
1065
1065
 
1066
1066
  // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
1067
1067
  const cam = this.cameraGroupVr instanceof THREE.Group ? this.cameraGroupVr.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera
1068
+ // Flush buffered geometry updates atomically before rendering
1069
+ this.worldBlockGeometry.applyPendingUpdates()
1068
1070
  this.renderer.render(this.scene, cam)
1069
1071
 
1070
1072
  if (