minecraft-renderer 0.1.62 → 0.1.64
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 +1 -1
- package/dist/mesherWasm.js +22 -22
- package/dist/minecraft-renderer.js +54 -54
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +407 -407
- package/package.json +1 -1
- package/src/graphicsBackend/config.ts +3 -3
- package/src/graphicsBackend/rendererDefaultOptions.ts +41 -24
- package/src/graphicsBackend/rendererOptionsSync.ts +23 -23
- package/src/graphicsBackend/types.ts +3 -3
- package/src/index.ts +8 -8
- package/src/lib/bindAbortableListener.test.ts +65 -0
- package/src/lib/bindAbortableListener.ts +41 -0
- package/src/lib/workerProxy.ts +238 -118
- package/src/lib/workerSyncOps.test.ts +154 -0
- package/src/lib/worldrendererCommon.removeColumn.test.ts +182 -0
- package/src/lib/worldrendererCommon.ts +86 -54
- package/src/three/documentRenderer.ts +1 -1
- package/src/three/entities.ts +21 -11
- package/src/three/graphicsBackendBase.ts +18 -8
- package/src/three/menuBackground/activeView.ts +1 -1
- package/src/three/menuBackground/config.ts +9 -9
- package/src/three/menuBackground/index.ts +10 -10
- package/src/three/menuBackground/renderer.ts +12 -12
- package/src/three/menuBackground/types.ts +9 -9
- package/src/three/menuBackground/{futuristic.ts → v2.ts} +110 -59
- package/src/three/menuBackground/{futuristicMeta.ts → v2Meta.ts} +6 -6
- package/src/three/modules/rain.ts +1 -1
- package/src/three/worldRendererThree.ts +2 -1
- package/src/wasm-mesher/tests/mesherWasmRequestTracker.test.ts +29 -0
- package/src/wasm-mesher/worker/mesherWasm.ts +7 -0
- package/src/wasm-mesher/worker/mesherWasmRequestTracker.ts +10 -0
- package/src/worldView/worldView.spiral.test.ts +38 -0
- package/src/worldView/worldView.ts +41 -8
- package/src/worldView/worldViewWorkerBridge.test.ts +59 -0
- package/src/lib/workerProxy.restore.test.ts +0 -29
|
@@ -3,14 +3,14 @@ import * as THREE from 'three'
|
|
|
3
3
|
import type { GraphicsInitOptions } from '../../graphicsBackend/types'
|
|
4
4
|
import type { DocumentRenderer } from '../documentRenderer'
|
|
5
5
|
import { ClassicMenuBackground } from './classic'
|
|
6
|
-
import {
|
|
6
|
+
import { V2MenuBackground } from './v2'
|
|
7
7
|
import { WorldBlocksMenuBackground } from './worldBlocks'
|
|
8
8
|
import type { MenuBackgroundView } from './activeView'
|
|
9
9
|
import type { MenuBackgroundOptions } from './types'
|
|
10
10
|
import { resolveMenuBackgroundMode } from './types'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Orchestrates main-menu background rendering (dispatches to classic /
|
|
13
|
+
* Orchestrates main-menu background rendering (dispatches to classic / v2 / world-blocks).
|
|
14
14
|
*/
|
|
15
15
|
export class MenuBackgroundRenderer {
|
|
16
16
|
private active?: MenuBackgroundView
|
|
@@ -27,9 +27,9 @@ export class MenuBackgroundRenderer {
|
|
|
27
27
|
this.mode = resolveMenuBackgroundMode(menuBackgroundOptions, singleFileBuild)
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
/** Active
|
|
31
|
-
get
|
|
32
|
-
return this.active instanceof
|
|
30
|
+
/** Active v2 instance when that style is running. */
|
|
31
|
+
get v2(): V2MenuBackground | undefined {
|
|
32
|
+
return this.active instanceof V2MenuBackground ? this.active : undefined
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
get scene(): THREE.Scene | undefined {
|
|
@@ -64,16 +64,16 @@ export class MenuBackgroundRenderer {
|
|
|
64
64
|
|
|
65
65
|
private createImplementation(options: MenuBackgroundOptions): MenuBackgroundView {
|
|
66
66
|
switch (this.mode) {
|
|
67
|
-
case '
|
|
68
|
-
return new
|
|
67
|
+
case 'v2':
|
|
68
|
+
return new V2MenuBackground(
|
|
69
69
|
this.documentRenderer,
|
|
70
70
|
{
|
|
71
71
|
useMinecraftTextures: options.useMinecraftTextures,
|
|
72
|
-
initialScene: options.
|
|
73
|
-
initialCamera: options.
|
|
74
|
-
initialBlockGroup: options.
|
|
75
|
-
initialCameraSpeed: options.
|
|
76
|
-
initialBlockSpeed: options.
|
|
72
|
+
initialScene: options.v2Scene,
|
|
73
|
+
initialCamera: options.v2Camera,
|
|
74
|
+
initialBlockGroup: options.v2BlockGroup,
|
|
75
|
+
initialCameraSpeed: options.v2CameraSpeed,
|
|
76
|
+
initialBlockSpeed: options.v2BlockSpeed,
|
|
77
77
|
resourcesManager: options.resourcesManager
|
|
78
78
|
},
|
|
79
79
|
this.abortController.signal
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import type { ResourcesManager } from '../../resourcesManager/resourcesManager'
|
|
3
|
-
import type {
|
|
3
|
+
import type { V2CameraId, V2SceneId, MinecraftBlockGroupId } from './v2'
|
|
4
4
|
import { MENU_BACKGROUND_OPTION_DEFAULTS } from './config'
|
|
5
5
|
|
|
6
|
-
export type {
|
|
6
|
+
export type { V2CameraId, V2SceneId, MinecraftBlockGroupId } from './v2'
|
|
7
7
|
|
|
8
|
-
export type MenuBackgroundMode = 'classic' | '
|
|
8
|
+
export type MenuBackgroundMode = 'classic' | 'v2' | 'worldBlocks'
|
|
9
9
|
|
|
10
10
|
export interface MenuBackgroundOptions {
|
|
11
11
|
/** Visual style. Defaults to {@link MENU_BACKGROUND_OPTION_DEFAULTS.mode}, or `worldBlocks` in single-file build. */
|
|
12
12
|
mode?: MenuBackgroundMode
|
|
13
|
-
/**
|
|
13
|
+
/** V2 style: load block atlas and render textured cubes (requires assets / mcData). */
|
|
14
14
|
useMinecraftTextures?: boolean
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
v2Scene?: V2SceneId
|
|
16
|
+
v2Camera?: V2CameraId
|
|
17
17
|
/** Block pool when {@link useMinecraftTextures} is enabled. */
|
|
18
|
-
|
|
18
|
+
v2BlockGroup?: MinecraftBlockGroupId
|
|
19
19
|
/** Camera path speed (1 = 100%). */
|
|
20
|
-
|
|
20
|
+
v2CameraSpeed?: number
|
|
21
21
|
/** Block fly-through + sky drift speed (1 = 100%). */
|
|
22
|
-
|
|
22
|
+
v2BlockSpeed?: number
|
|
23
23
|
/**
|
|
24
24
|
* Optional shared resource manager (e.g. appViewer.resourcesManager).
|
|
25
25
|
* Caller should run `updateAssetsData` after mcData is loaded when using textured cubes.
|
|
@@ -8,31 +8,31 @@ import { resizeMenuBackgroundCamera } from './activeView'
|
|
|
8
8
|
import { loadThreeJsTextureFromBitmap } from '../threeJsUtils'
|
|
9
9
|
import { MENU_BACKGROUND_MOTION_DEFAULTS, MENU_BACKGROUND_OPTION_DEFAULTS } from './config'
|
|
10
10
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
V2_CAMERA_IDS,
|
|
12
|
+
V2_SCENE_IDS,
|
|
13
13
|
MINECRAFT_BLOCK_GROUP_IDS,
|
|
14
|
-
type
|
|
15
|
-
type
|
|
14
|
+
type V2CameraId,
|
|
15
|
+
type V2SceneId,
|
|
16
16
|
type MinecraftBlockGroupId,
|
|
17
|
-
} from './
|
|
17
|
+
} from './v2Meta'
|
|
18
18
|
|
|
19
19
|
export {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
V2_SCENE_IDS,
|
|
21
|
+
V2_SCENE_LABELS,
|
|
22
|
+
V2_CAMERA_IDS,
|
|
23
|
+
V2_CAMERA_LABELS,
|
|
24
24
|
MINECRAFT_BLOCK_GROUP_IDS,
|
|
25
25
|
MINECRAFT_BLOCK_GROUP_LABELS,
|
|
26
|
-
} from './
|
|
27
|
-
export type {
|
|
26
|
+
} from './v2Meta'
|
|
27
|
+
export type { V2SceneId, V2CameraId, MinecraftBlockGroupId } from './v2Meta'
|
|
28
28
|
|
|
29
29
|
/** Mouse parallax scale (HTML prototype uses 1). */
|
|
30
30
|
const MOUSE_INFLUENCE = 0.1
|
|
31
31
|
|
|
32
|
-
export interface
|
|
32
|
+
export interface V2MenuBackgroundOptions {
|
|
33
33
|
useMinecraftTextures?: boolean
|
|
34
|
-
initialScene?:
|
|
35
|
-
initialCamera?:
|
|
34
|
+
initialScene?: V2SceneId
|
|
35
|
+
initialCamera?: V2CameraId
|
|
36
36
|
initialBlockGroup?: MinecraftBlockGroupId
|
|
37
37
|
/** Camera path speed multiplier (0 = frozen path; mouse parallax unchanged). */
|
|
38
38
|
initialCameraSpeed?: number
|
|
@@ -41,7 +41,7 @@ export interface FuturisticMenuBackgroundOptions {
|
|
|
41
41
|
resourcesManager?: ResourcesManager
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/** Block pools for textured floating cubes (selected via {@link
|
|
44
|
+
/** Block pools for textured floating cubes (selected via {@link V2MenuBackground.setBlockGroup}). */
|
|
45
45
|
export const MINECRAFT_BLOCK_GROUPS = {
|
|
46
46
|
mixed: [
|
|
47
47
|
'white_wool', 'cyan_wool', 'blue_wool', 'purple_wool',
|
|
@@ -123,7 +123,7 @@ interface FloatingBlock {
|
|
|
123
123
|
minecraftBlockName?: string
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
const PAL: Record<
|
|
126
|
+
const PAL: Record<V2SceneId, ScenePalette> = {
|
|
127
127
|
galaxy: {
|
|
128
128
|
bg: 0x02_04_12, fog: 0x02_04_12, fogD: 0.011,
|
|
129
129
|
blocks: [0x00_f0_ff, 0x00_d4_ff, 0x00_b8_ff, 0x00_e8_ff, 0x22_cc_ff, 0x00_a8_ff],
|
|
@@ -192,7 +192,7 @@ const PAL: Record<FuturisticSceneId, ScenePalette> = {
|
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
const CAMS: Record<
|
|
195
|
+
const CAMS: Record<V2CameraId, CameraMode> = {
|
|
196
196
|
cruise: {
|
|
197
197
|
pos: (t, mx, my) => ({ x: Math.sin(t * 0.28) * 18 + Math.cos(t * 0.11) * 7 + mx * 10, y: Math.sin(t * 0.19) * 6 + Math.cos(t * 0.31) * 3 + my * 6, z: 0 }),
|
|
198
198
|
look: (t, mx, my) => ({ x: Math.sin((t + 0.18) * 0.28) * 18 + mx * 8, y: Math.sin((t + 0.18) * 0.19) * 6 + my * 4, z: -25 }),
|
|
@@ -232,7 +232,7 @@ const CAMS: Record<FuturisticCameraId, CameraMode> = {
|
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
const CAM_SPD: Record<
|
|
235
|
+
const CAM_SPD: Record<V2CameraId, number> = {
|
|
236
236
|
cruise: 1,
|
|
237
237
|
barrel: 1.6,
|
|
238
238
|
dive: 2.2,
|
|
@@ -270,7 +270,7 @@ const makeSkyGradientTexture = (gradient: NonNullable<ScenePalette['gradientBg']
|
|
|
270
270
|
return tex
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
export class
|
|
273
|
+
export class V2MenuBackground implements MenuBackgroundView {
|
|
274
274
|
readonly scene: THREE.Scene
|
|
275
275
|
readonly camera: THREE.PerspectiveCamera
|
|
276
276
|
|
|
@@ -288,8 +288,8 @@ export class FuturisticMenuBackground implements MenuBackgroundView {
|
|
|
288
288
|
private readonly bGeo = new THREE.BoxGeometry(1, 1, 1)
|
|
289
289
|
private readonly eGeo = new THREE.EdgesGeometry(this.bGeo)
|
|
290
290
|
|
|
291
|
-
private curScene:
|
|
292
|
-
private curCam:
|
|
291
|
+
private curScene: V2SceneId
|
|
292
|
+
private curCam: V2CameraId
|
|
293
293
|
private blockGroup: MinecraftBlockGroupId
|
|
294
294
|
private cameraSpeed: number
|
|
295
295
|
private blockSpeed: number
|
|
@@ -307,16 +307,19 @@ export class FuturisticMenuBackground implements MenuBackgroundView {
|
|
|
307
307
|
private gradientSkyTexture: THREE.CanvasTexture | null = null
|
|
308
308
|
private disposed = false
|
|
309
309
|
private animTime = 0
|
|
310
|
+
private texturesApplied = false
|
|
311
|
+
private textureLoadInProgress = false
|
|
312
|
+
private onAssetsTexturesUpdated?: () => void
|
|
310
313
|
|
|
311
314
|
constructor(
|
|
312
315
|
private readonly documentRenderer: DocumentRenderer,
|
|
313
|
-
options:
|
|
316
|
+
options: V2MenuBackgroundOptions = {},
|
|
314
317
|
private readonly abortSignal?: AbortSignal
|
|
315
318
|
) {
|
|
316
319
|
const d = MENU_BACKGROUND_OPTION_DEFAULTS
|
|
317
|
-
this.curScene = options.initialScene ?? d.
|
|
318
|
-
this.curCam = options.initialCamera ?? d.
|
|
319
|
-
this.blockGroup = options.initialBlockGroup ?? d.
|
|
320
|
+
this.curScene = options.initialScene ?? d.v2Scene
|
|
321
|
+
this.curCam = options.initialCamera ?? d.v2Camera
|
|
322
|
+
this.blockGroup = options.initialBlockGroup ?? d.v2BlockGroup
|
|
320
323
|
this.cameraSpeed = options.initialCameraSpeed ?? MENU_BACKGROUND_MOTION_DEFAULTS.camera
|
|
321
324
|
this.blockSpeed = options.initialBlockSpeed ?? MENU_BACKGROUND_MOTION_DEFAULTS.block
|
|
322
325
|
this.useMinecraftTextures = options.useMinecraftTextures ?? d.minecraftTextures
|
|
@@ -420,13 +423,79 @@ export class FuturisticMenuBackground implements MenuBackgroundView {
|
|
|
420
423
|
}
|
|
421
424
|
|
|
422
425
|
async init() {
|
|
423
|
-
if (this.useMinecraftTextures)
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
426
|
+
if (!this.useMinecraftTextures) return
|
|
427
|
+
void this.scheduleMinecraftTextureLoad()
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
private scheduleMinecraftTextureLoad() {
|
|
431
|
+
if (!this.useMinecraftTextures || this.disposed || this.texturesApplied || this.textureLoadInProgress) return
|
|
432
|
+
void this.tryApplyMinecraftTextures()
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private attachAssetsListener() {
|
|
436
|
+
const rm = this.resourcesManager
|
|
437
|
+
if (!rm || this.onAssetsTexturesUpdated) return
|
|
438
|
+
this.onAssetsTexturesUpdated = () => this.scheduleMinecraftTextureLoad()
|
|
439
|
+
rm.on('assetsTexturesUpdated', this.onAssetsTexturesUpdated)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private detachAssetsListener() {
|
|
443
|
+
const rm = this.resourcesManager
|
|
444
|
+
if (!rm || !this.onAssetsTexturesUpdated) return
|
|
445
|
+
rm.off('assetsTexturesUpdated', this.onAssetsTexturesUpdated)
|
|
446
|
+
this.onAssetsTexturesUpdated = undefined
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private hasBlockAtlas(resourcesManager: ResourcesManager): boolean {
|
|
450
|
+
const resources = resourcesManager.currentResources
|
|
451
|
+
return !!(resources?.blocksAtlasImage && resources.blocksAtlasJson)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private async ensureAtlasReady(resourcesManager: ResourcesManager): Promise<boolean> {
|
|
455
|
+
await this.ensureMcDataLoaded()
|
|
456
|
+
if (this.hasBlockAtlas(resourcesManager)) return true
|
|
457
|
+
|
|
458
|
+
if (typeof document === 'undefined' && resourcesManager !== this.resourcesManager) {
|
|
459
|
+
return false
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
resourcesManager.currentConfig = {
|
|
463
|
+
...resourcesManager.currentConfig,
|
|
464
|
+
version: MENU_BACKGROUND_MC_VERSION,
|
|
465
|
+
noInventoryGui: true
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
try {
|
|
469
|
+
await resourcesManager.updateAssetsData?.({})
|
|
470
|
+
} catch {
|
|
471
|
+
return false
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return this.hasBlockAtlas(resourcesManager)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
private async tryApplyMinecraftTextures() {
|
|
478
|
+
if (this.disposed || !this.useMinecraftTextures || this.texturesApplied) return
|
|
479
|
+
|
|
480
|
+
this.textureLoadInProgress = true
|
|
481
|
+
try {
|
|
482
|
+
const resourcesManager = this.resourcesManager ?? new ResourcesManager()
|
|
483
|
+
const ready = await this.ensureAtlasReady(resourcesManager)
|
|
484
|
+
if (!ready) {
|
|
485
|
+
if (this.resourcesManager) this.attachAssetsListener()
|
|
486
|
+
return
|
|
429
487
|
}
|
|
488
|
+
if (this.disposed) return
|
|
489
|
+
|
|
490
|
+
this.applyMinecraftTexturesFromAtlas(resourcesManager)
|
|
491
|
+
this.texturesApplied = true
|
|
492
|
+
this.detachAssetsListener()
|
|
493
|
+
} catch (err) {
|
|
494
|
+
console.warn('[V2MenuBackground] Failed to load Minecraft textures, using solid colors:', err)
|
|
495
|
+
this.useMinecraftTextures = false
|
|
496
|
+
this.detachAssetsListener()
|
|
497
|
+
} finally {
|
|
498
|
+
this.textureLoadInProgress = false
|
|
430
499
|
}
|
|
431
500
|
}
|
|
432
501
|
|
|
@@ -626,23 +695,7 @@ export class FuturisticMenuBackground implements MenuBackgroundView {
|
|
|
626
695
|
return null
|
|
627
696
|
}
|
|
628
697
|
|
|
629
|
-
private
|
|
630
|
-
await this.ensureMcDataLoaded()
|
|
631
|
-
|
|
632
|
-
const resourcesManager = this.resourcesManager ?? new ResourcesManager()
|
|
633
|
-
const needsAssetUpdate = !resourcesManager.currentResources?.blocksAtlasImage
|
|
634
|
-
if (needsAssetUpdate) {
|
|
635
|
-
if (typeof document === 'undefined') {
|
|
636
|
-
throw new Error('Menu atlas missing in worker; pass resourcesManager from main thread')
|
|
637
|
-
}
|
|
638
|
-
resourcesManager.currentConfig = {
|
|
639
|
-
...resourcesManager.currentConfig,
|
|
640
|
-
version: MENU_BACKGROUND_MC_VERSION,
|
|
641
|
-
noInventoryGui: true
|
|
642
|
-
}
|
|
643
|
-
await resourcesManager.updateAssetsData?.({})
|
|
644
|
-
}
|
|
645
|
-
|
|
698
|
+
private applyMinecraftTexturesFromAtlas(resourcesManager: ResourcesManager) {
|
|
646
699
|
const resources = resourcesManager.currentResources
|
|
647
700
|
if (!resources?.blocksAtlasImage || !resources.blocksAtlasJson) {
|
|
648
701
|
throw new Error('Block atlas not available')
|
|
@@ -690,8 +743,8 @@ export class FuturisticMenuBackground implements MenuBackgroundView {
|
|
|
690
743
|
}
|
|
691
744
|
}
|
|
692
745
|
|
|
693
|
-
setScene(name:
|
|
694
|
-
if (!(
|
|
746
|
+
setScene(name: V2SceneId) {
|
|
747
|
+
if (!(V2_SCENE_IDS as readonly string[]).includes(name)) return
|
|
695
748
|
if (name === this.curScene || this.transitioning) return
|
|
696
749
|
this.transitioning = true
|
|
697
750
|
this.curScene = name
|
|
@@ -726,8 +779,8 @@ export class FuturisticMenuBackground implements MenuBackgroundView {
|
|
|
726
779
|
}, 150)
|
|
727
780
|
}
|
|
728
781
|
|
|
729
|
-
setCamera(name:
|
|
730
|
-
if (!(
|
|
782
|
+
setCamera(name: V2CameraId) {
|
|
783
|
+
if (!(V2_CAMERA_IDS as readonly string[]).includes(name)) return
|
|
731
784
|
this.curCam = name
|
|
732
785
|
}
|
|
733
786
|
|
|
@@ -749,18 +802,15 @@ export class FuturisticMenuBackground implements MenuBackgroundView {
|
|
|
749
802
|
mat.dispose()
|
|
750
803
|
}
|
|
751
804
|
this.blockMaterialPool.clear()
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
} catch (err) {
|
|
755
|
-
console.warn('[FuturisticMenuBackground] Failed to reload block group textures:', err)
|
|
756
|
-
}
|
|
805
|
+
this.texturesApplied = false
|
|
806
|
+
this.scheduleMinecraftTextureLoad()
|
|
757
807
|
}
|
|
758
808
|
|
|
759
|
-
getSceneId():
|
|
809
|
+
getSceneId(): V2SceneId {
|
|
760
810
|
return this.curScene
|
|
761
811
|
}
|
|
762
812
|
|
|
763
|
-
getCameraId():
|
|
813
|
+
getCameraId(): V2CameraId {
|
|
764
814
|
return this.curCam
|
|
765
815
|
}
|
|
766
816
|
|
|
@@ -830,6 +880,7 @@ export class FuturisticMenuBackground implements MenuBackgroundView {
|
|
|
830
880
|
|
|
831
881
|
dispose() {
|
|
832
882
|
this.disposed = true
|
|
883
|
+
this.detachAssetsListener()
|
|
833
884
|
this.scene.clear()
|
|
834
885
|
this.bGeo.dispose()
|
|
835
886
|
this.eGeo.dispose()
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
/** Settings / labels only — no Three.js or DocumentRenderer (safe for defaultOptions imports). */
|
|
3
3
|
|
|
4
|
-
export const
|
|
5
|
-
export type
|
|
4
|
+
export const V2_SCENE_IDS = ['galaxy', 'nether', 'end', 'cyber', 'light'] as const
|
|
5
|
+
export type V2SceneId = typeof V2_SCENE_IDS[number]
|
|
6
6
|
|
|
7
|
-
export const
|
|
8
|
-
export type
|
|
7
|
+
export const V2_CAMERA_IDS = ['cruise', 'barrel', 'dive', 'orbit', 'snake'] as const
|
|
8
|
+
export type V2CameraId = typeof V2_CAMERA_IDS[number]
|
|
9
9
|
|
|
10
|
-
export const
|
|
10
|
+
export const V2_SCENE_LABELS: Record<V2SceneId, string> = {
|
|
11
11
|
galaxy: 'Galaxy',
|
|
12
12
|
nether: 'Nether',
|
|
13
13
|
end: 'The End',
|
|
@@ -15,7 +15,7 @@ export const FUTURISTIC_SCENE_LABELS: Record<FuturisticSceneId, string> = {
|
|
|
15
15
|
light: 'Light Space'
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export const
|
|
18
|
+
export const V2_CAMERA_LABELS: Record<V2CameraId, string> = {
|
|
19
19
|
cruise: 'Cruise',
|
|
20
20
|
barrel: 'Barrel',
|
|
21
21
|
dive: 'Dive',
|
|
@@ -93,7 +93,7 @@ export class RainModule implements RendererModuleController {
|
|
|
93
93
|
const chunkX = Math.floor(worldX / 16)
|
|
94
94
|
const chunkZ = Math.floor(worldZ / 16)
|
|
95
95
|
if (chunkX !== prevChunkX || chunkZ !== prevChunkZ) {
|
|
96
|
-
cachedHeightmap = heightmaps
|
|
96
|
+
cachedHeightmap = heightmaps[`${chunkX},${chunkZ}`]
|
|
97
97
|
prevChunkX = chunkX
|
|
98
98
|
prevChunkZ = chunkZ
|
|
99
99
|
}
|
|
@@ -167,7 +167,8 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
167
167
|
this.performanceMonitor = new PerformanceMonitor(this.reactiveState.world.instabilityFactors)
|
|
168
168
|
|
|
169
169
|
this.renderer = renderer
|
|
170
|
-
|
|
170
|
+
const rendererInfo = WorldRendererThree.getRendererInfo(renderer) ?? '...'
|
|
171
|
+
displayOptions.rendererState.renderer = rendererInfo
|
|
171
172
|
|
|
172
173
|
// Initialize chunk mesh manager
|
|
173
174
|
this.chunkMeshManager = new ChunkMeshManager(this, this.scene, this.material, this.worldSizeParams.worldHeight, this.viewDistance)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { describe, expect, test } from 'vitest'
|
|
3
|
+
import { SectionRequestTracker } from '../worker/mesherWasmRequestTracker'
|
|
4
|
+
|
|
5
|
+
describe('SectionRequestTracker.clearColumn', () => {
|
|
6
|
+
test('removes all pending keys for the column', () => {
|
|
7
|
+
const tracker = new SectionRequestTracker()
|
|
8
|
+
tracker.addRequest('160,64,0')
|
|
9
|
+
tracker.addRequest('160,80,0')
|
|
10
|
+
tracker.addRequest('0,64,0')
|
|
11
|
+
|
|
12
|
+
tracker.clearColumn(160, 0)
|
|
13
|
+
|
|
14
|
+
expect(tracker.hasPending('160,64,0')).toBe(false)
|
|
15
|
+
expect(tracker.hasPending('160,80,0')).toBe(false)
|
|
16
|
+
expect(tracker.hasPending('0,64,0')).toBe(true)
|
|
17
|
+
expect(tracker.size()).toBe(1)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('is a no-op when the column has no pending keys', () => {
|
|
21
|
+
const tracker = new SectionRequestTracker()
|
|
22
|
+
tracker.addRequest('0,64,0')
|
|
23
|
+
|
|
24
|
+
tracker.clearColumn(160, 0)
|
|
25
|
+
|
|
26
|
+
expect(tracker.hasPending('0,64,0')).toBe(true)
|
|
27
|
+
expect(tracker.size()).toBe(1)
|
|
28
|
+
})
|
|
29
|
+
})
|
|
@@ -767,6 +767,13 @@ const handleMessage = async (data: any) => {
|
|
|
767
767
|
if (!world) break
|
|
768
768
|
world.removeColumn(data.x, data.z)
|
|
769
769
|
world.customBlockModels.delete(`${data.x},${data.z}`)
|
|
770
|
+
requestTracker.clearColumn(data.x, data.z)
|
|
771
|
+
for (const key of [...dirtySections.keys()]) {
|
|
772
|
+
const [sx, , sz] = key.split(',').map(Number)
|
|
773
|
+
if (sx === data.x && sz === data.z) {
|
|
774
|
+
dirtySections.delete(key)
|
|
775
|
+
}
|
|
776
|
+
}
|
|
770
777
|
if (Object.keys(world.columns).length === 0) softCleanup()
|
|
771
778
|
break
|
|
772
779
|
}
|
|
@@ -49,6 +49,16 @@ export class SectionRequestTracker {
|
|
|
49
49
|
this.counts.clear()
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/** Drop all pending requests for one column (`cx`,`cz` = column origin in block coords). */
|
|
53
|
+
clearColumn (cx: number, cz: number): void {
|
|
54
|
+
for (const key of [...this.counts.keys()]) {
|
|
55
|
+
const [x, , z] = key.split(',').map(Number)
|
|
56
|
+
if (x === cx && z === cz) {
|
|
57
|
+
this.counts.delete(key)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
52
62
|
/** Number of distinct keys with pending requests. */
|
|
53
63
|
size (): number {
|
|
54
64
|
return this.counts.size
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { Vec3 } from 'vec3'
|
|
4
|
+
import { WorldView } from './worldView'
|
|
5
|
+
|
|
6
|
+
describe('WorldView._loadChunks spiral guard', () => {
|
|
7
|
+
it('does not let a superseded spiral reset inLoading or panic state', async () => {
|
|
8
|
+
const world = {
|
|
9
|
+
getColumnAt: () => null,
|
|
10
|
+
setBlockStateId: vi.fn(),
|
|
11
|
+
}
|
|
12
|
+
const view = new WorldView(world, 8, new Vec3(0, 64, 0))
|
|
13
|
+
view.addWaitTime = 0
|
|
14
|
+
view.loadChunk = vi.fn(async () => {})
|
|
15
|
+
|
|
16
|
+
const positions = [new Vec3(0, 0, 0)]
|
|
17
|
+
const spiralA = view._loadChunks(positions, new Vec3(0, 64, 0))
|
|
18
|
+
await Promise.resolve()
|
|
19
|
+
|
|
20
|
+
expect(view.inLoading).toBe(true)
|
|
21
|
+
expect(view.spiralNumber).toBe(1)
|
|
22
|
+
|
|
23
|
+
const spiralB = view._loadChunks(positions, new Vec3(0, 64, 0))
|
|
24
|
+
await Promise.resolve()
|
|
25
|
+
|
|
26
|
+
expect(view.spiralNumber).toBe(2)
|
|
27
|
+
expect(view.inLoading).toBe(true)
|
|
28
|
+
expect(view.gotPanicLastTime).toBe(false)
|
|
29
|
+
|
|
30
|
+
await spiralA
|
|
31
|
+
expect(view.inLoading).toBe(true)
|
|
32
|
+
expect(view.gotPanicLastTime).toBe(false)
|
|
33
|
+
|
|
34
|
+
view.waitingSpiralChunksLoad['0,0']?.(true)
|
|
35
|
+
await spiralB
|
|
36
|
+
expect(view.inLoading).toBe(false)
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -44,6 +44,13 @@ export const delayedIterator = async <T>(
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
type WorkerWorldViewBridge = {
|
|
48
|
+
activeWorldView: WorldViewWorker
|
|
49
|
+
handler: (event: MessageEvent) => void
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const workerWorldViewBridges = new WeakMap<Worker, WorkerWorldViewBridge>()
|
|
53
|
+
|
|
47
54
|
/**
|
|
48
55
|
* WorldView for worker thread communication.
|
|
49
56
|
* This is a lightweight version that receives events from the main thread.
|
|
@@ -51,19 +58,43 @@ export const delayedIterator = async <T>(
|
|
|
51
58
|
export class WorldViewWorker extends (EventEmitter as new () => TypedEmitter<WorldViewEvents>) {
|
|
52
59
|
static readonly restorerName = 'WorldViewWorker'
|
|
53
60
|
|
|
54
|
-
static restoreTransferred(
|
|
61
|
+
static restoreTransferred(_data: any, worker?: Worker): WorldViewWorker {
|
|
55
62
|
const worldView = new WorldViewWorker()
|
|
56
|
-
if (worker) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
if (!worker) {
|
|
64
|
+
return worldView
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let bridge = workerWorldViewBridges.get(worker)
|
|
68
|
+
if (!bridge) {
|
|
69
|
+
const handler = ({ data }: MessageEvent) => {
|
|
70
|
+
const state = workerWorldViewBridges.get(worker)
|
|
71
|
+
const active = state?.activeWorldView
|
|
72
|
+
if (!active || data?.class !== WorldViewWorker.restorerName) return
|
|
73
|
+
if (data.type === 'event') {
|
|
74
|
+
active.emit(data.eventName, ...data.args)
|
|
62
75
|
}
|
|
63
|
-
}
|
|
76
|
+
}
|
|
77
|
+
worker.addEventListener('message', handler as EventListener)
|
|
78
|
+
bridge = { activeWorldView: worldView, handler }
|
|
79
|
+
workerWorldViewBridges.set(worker, bridge)
|
|
80
|
+
} else {
|
|
81
|
+
bridge.activeWorldView = worldView
|
|
64
82
|
}
|
|
65
83
|
return worldView
|
|
66
84
|
}
|
|
85
|
+
|
|
86
|
+
/** @internal vitest — remove bridge listener from worker */
|
|
87
|
+
static clearWorkerBridgeForTest(worker: Worker): void {
|
|
88
|
+
const bridge = workerWorldViewBridges.get(worker)
|
|
89
|
+
if (!bridge) return
|
|
90
|
+
worker.removeEventListener('message', bridge.handler as EventListener)
|
|
91
|
+
workerWorldViewBridges.delete(worker)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** @internal vitest — count of bridge listeners on worker */
|
|
95
|
+
static getWorkerBridgeListenerCountForTest(worker: Worker): number {
|
|
96
|
+
return workerWorldViewBridges.has(worker) ? 1 : 0
|
|
97
|
+
}
|
|
67
98
|
}
|
|
68
99
|
|
|
69
100
|
/**
|
|
@@ -234,6 +265,8 @@ export class WorldView extends (EventEmitter as new () => TypedEmitter<WorldView
|
|
|
234
265
|
this.chunkProgress()
|
|
235
266
|
})
|
|
236
267
|
|
|
268
|
+
if (spiralNumber !== this.spiralNumber) return
|
|
269
|
+
|
|
237
270
|
if (this.panicTimeout) clearTimeout(this.panicTimeout)
|
|
238
271
|
this.inLoading = false
|
|
239
272
|
this.gotPanicLastTime = false
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { WorldViewWorker } from './worldView'
|
|
4
|
+
|
|
5
|
+
describe('WorldViewWorker.restoreTransferred bridge', () => {
|
|
6
|
+
it('registers only one message listener per worker across restores', () => {
|
|
7
|
+
const addSpy = vi.fn()
|
|
8
|
+
const worker = {
|
|
9
|
+
addEventListener: addSpy,
|
|
10
|
+
removeEventListener: vi.fn(),
|
|
11
|
+
} as unknown as Worker
|
|
12
|
+
|
|
13
|
+
WorldViewWorker.restoreTransferred({}, worker)
|
|
14
|
+
WorldViewWorker.restoreTransferred({}, worker)
|
|
15
|
+
WorldViewWorker.restoreTransferred({}, worker)
|
|
16
|
+
|
|
17
|
+
expect(addSpy).toHaveBeenCalledTimes(1)
|
|
18
|
+
expect(WorldViewWorker.getWorkerBridgeListenerCountForTest(worker)).toBe(1)
|
|
19
|
+
|
|
20
|
+
WorldViewWorker.clearWorkerBridgeForTest(worker)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('routes events to the latest restored worldView only', () => {
|
|
24
|
+
const listeners: Array<(event: MessageEvent) => void> = []
|
|
25
|
+
const worker = {
|
|
26
|
+
addEventListener: (_type: string, handler: (event: MessageEvent) => void) => {
|
|
27
|
+
listeners.push(handler)
|
|
28
|
+
},
|
|
29
|
+
removeEventListener: () => {},
|
|
30
|
+
} as unknown as Worker
|
|
31
|
+
|
|
32
|
+
const first = WorldViewWorker.restoreTransferred({}, worker)
|
|
33
|
+
const second = WorldViewWorker.restoreTransferred({}, worker)
|
|
34
|
+
|
|
35
|
+
let firstCalls = 0
|
|
36
|
+
let secondCalls = 0
|
|
37
|
+
first.on('loadChunk', () => {
|
|
38
|
+
firstCalls++
|
|
39
|
+
})
|
|
40
|
+
second.on('loadChunk', () => {
|
|
41
|
+
secondCalls++
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const handler = listeners[0]!
|
|
45
|
+
handler({
|
|
46
|
+
data: {
|
|
47
|
+
class: WorldViewWorker.restorerName,
|
|
48
|
+
type: 'event',
|
|
49
|
+
eventName: 'loadChunk',
|
|
50
|
+
args: [{ x: 0, z: 0, chunk: {}, worldConfig: {}, isLightUpdate: false }],
|
|
51
|
+
},
|
|
52
|
+
} as MessageEvent)
|
|
53
|
+
|
|
54
|
+
expect(firstCalls).toBe(0)
|
|
55
|
+
expect(secondCalls).toBe(1)
|
|
56
|
+
|
|
57
|
+
WorldViewWorker.clearWorkerBridgeForTest(worker)
|
|
58
|
+
})
|
|
59
|
+
})
|