minecraft-renderer 0.1.0
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 +297 -0
- package/dist/index.html +83 -0
- package/dist/static/image/arrow.6f27b59f.png +0 -0
- package/dist/static/image/blocksAtlasLatest.7850afa3.png +0 -0
- package/dist/static/image/blocksAtlasLegacy.5c76823d.png +0 -0
- package/dist/static/image/itemsAtlasLatest.36036f95.png +0 -0
- package/dist/static/image/itemsAtlasLegacy.dcb1b58d.png +0 -0
- package/dist/static/image/tipped_arrow.6f27b59f.png +0 -0
- package/dist/static/js/365.f05233ab.js +8462 -0
- package/dist/static/js/365.f05233ab.js.LICENSE.txt +52 -0
- package/dist/static/js/async/738.efa27644.js +1 -0
- package/dist/static/js/index.092ec5be.js +56 -0
- package/dist/static/js/lib-polyfill.98986ac5.js +1 -0
- package/dist/static/js/lib-react.5c9129e0.js +2 -0
- package/dist/static/js/lib-react.5c9129e0.js.LICENSE.txt +39 -0
- package/package.json +104 -0
- package/src/assets/destroy_stage_0.png +0 -0
- package/src/assets/destroy_stage_1.png +0 -0
- package/src/assets/destroy_stage_2.png +0 -0
- package/src/assets/destroy_stage_3.png +0 -0
- package/src/assets/destroy_stage_4.png +0 -0
- package/src/assets/destroy_stage_5.png +0 -0
- package/src/assets/destroy_stage_6.png +0 -0
- package/src/assets/destroy_stage_7.png +0 -0
- package/src/assets/destroy_stage_8.png +0 -0
- package/src/assets/destroy_stage_9.png +0 -0
- package/src/examples/README.md +146 -0
- package/src/examples/appViewerExample.ts +205 -0
- package/src/examples/initialMenuStart.ts +161 -0
- package/src/graphicsBackend/appViewer.ts +297 -0
- package/src/graphicsBackend/config.ts +119 -0
- package/src/graphicsBackend/index.ts +10 -0
- package/src/graphicsBackend/playerState.ts +61 -0
- package/src/graphicsBackend/types.ts +143 -0
- package/src/index.ts +97 -0
- package/src/lib/DebugGui.ts +190 -0
- package/src/lib/animationController.ts +85 -0
- package/src/lib/buildSharedConfig.mjs +1 -0
- package/src/lib/cameraBobbing.ts +94 -0
- package/src/lib/canvas2DOverlay.example.ts +361 -0
- package/src/lib/canvas2DOverlay.quickstart.ts +242 -0
- package/src/lib/canvas2DOverlay.ts +381 -0
- package/src/lib/cleanupDecorator.ts +29 -0
- package/src/lib/createPlayerObject.ts +55 -0
- package/src/lib/frameTimingCollector.ts +164 -0
- package/src/lib/guiRenderer.ts +283 -0
- package/src/lib/items.ts +140 -0
- package/src/lib/mesherlogReader.ts +131 -0
- package/src/lib/moreBlockDataGenerated.json +714 -0
- package/src/lib/preflatMap.json +1741 -0
- package/src/lib/simpleUtils.ts +40 -0
- package/src/lib/smoothSwitcher.ts +168 -0
- package/src/lib/spiral.ts +29 -0
- package/src/lib/ui/newStats.ts +120 -0
- package/src/lib/utils/proxy.ts +23 -0
- package/src/lib/utils/skins.ts +63 -0
- package/src/lib/utils.ts +76 -0
- package/src/lib/workerProxy.ts +342 -0
- package/src/lib/worldrendererCommon.ts +1088 -0
- package/src/mesher/mesher.ts +253 -0
- package/src/mesher/models.ts +769 -0
- package/src/mesher/modelsGeometryCommon.ts +142 -0
- package/src/mesher/shared.ts +80 -0
- package/src/mesher/standaloneRenderer.ts +270 -0
- package/src/mesher/test/a.ts +3 -0
- package/src/mesher/test/mesherTester.ts +76 -0
- package/src/mesher/test/playground.ts +19 -0
- package/src/mesher/test/test-perf.ts +74 -0
- package/src/mesher/test/tests.test.ts +56 -0
- package/src/mesher/world.ts +294 -0
- package/src/mesher/worldConstants.ts +1 -0
- package/src/modules/index.ts +11 -0
- package/src/modules/starfield.ts +313 -0
- package/src/modules/types.ts +110 -0
- package/src/playerState/playerState.ts +78 -0
- package/src/playerState/types.ts +36 -0
- package/src/playground/allEntitiesDebug.ts +170 -0
- package/src/playground/baseScene.ts +587 -0
- package/src/playground/mobileControls.tsx +268 -0
- package/src/playground/playground.html +83 -0
- package/src/playground/playground.ts +11 -0
- package/src/playground/playgroundUi.tsx +140 -0
- package/src/playground/reactUtils.ts +71 -0
- package/src/playground/scenes/allEntities.ts +13 -0
- package/src/playground/scenes/entities.ts +37 -0
- package/src/playground/scenes/floorRandom.ts +33 -0
- package/src/playground/scenes/frequentUpdates.ts +148 -0
- package/src/playground/scenes/geometryExport.ts +142 -0
- package/src/playground/scenes/index.ts +12 -0
- package/src/playground/scenes/lightingStarfield.ts +40 -0
- package/src/playground/scenes/main.ts +313 -0
- package/src/playground/scenes/railsCobweb.ts +14 -0
- package/src/playground/scenes/rotationIssue.ts +7 -0
- package/src/playground/scenes/slabsOptimization.ts +16 -0
- package/src/playground/scenes/transparencyIssue.ts +11 -0
- package/src/playground/shared.ts +79 -0
- package/src/resourcesManager/index.ts +5 -0
- package/src/resourcesManager/resourcesManager.ts +314 -0
- package/src/shims/minecraftData.ts +41 -0
- package/src/sign-renderer/index.html +21 -0
- package/src/sign-renderer/index.ts +216 -0
- package/src/sign-renderer/noop.js +1 -0
- package/src/sign-renderer/playground.ts +38 -0
- package/src/sign-renderer/tests.test.ts +69 -0
- package/src/sign-renderer/vite.config.ts +10 -0
- package/src/three/appShared.ts +75 -0
- package/src/three/bannerRenderer.ts +275 -0
- package/src/three/cameraShake.ts +120 -0
- package/src/three/cinimaticScript.ts +350 -0
- package/src/three/documentRenderer.ts +491 -0
- package/src/three/entities.ts +1580 -0
- package/src/three/entity/EntityMesh.ts +707 -0
- package/src/three/entity/animations.js +171 -0
- package/src/three/entity/armorModels.json +204 -0
- package/src/three/entity/armorModels.ts +36 -0
- package/src/three/entity/entities.json +6230 -0
- package/src/three/entity/exportedModels.js +38 -0
- package/src/three/entity/externalTextures.json +1 -0
- package/src/three/entity/models/allay.obj +325 -0
- package/src/three/entity/models/arrow.obj +60 -0
- package/src/three/entity/models/axolotl.obj +509 -0
- package/src/three/entity/models/blaze.obj +601 -0
- package/src/three/entity/models/boat.obj +417 -0
- package/src/three/entity/models/camel.obj +1061 -0
- package/src/three/entity/models/cat.obj +509 -0
- package/src/three/entity/models/chicken.obj +371 -0
- package/src/three/entity/models/cod.obj +371 -0
- package/src/three/entity/models/creeper.obj +279 -0
- package/src/three/entity/models/dolphin.obj +371 -0
- package/src/three/entity/models/ender_dragon.obj +2993 -0
- package/src/three/entity/models/enderman.obj +325 -0
- package/src/three/entity/models/endermite.obj +187 -0
- package/src/three/entity/models/fox.obj +463 -0
- package/src/three/entity/models/frog.obj +739 -0
- package/src/three/entity/models/ghast.obj +463 -0
- package/src/three/entity/models/goat.obj +601 -0
- package/src/three/entity/models/guardian.obj +1015 -0
- package/src/three/entity/models/horse.obj +1061 -0
- package/src/three/entity/models/llama.obj +509 -0
- package/src/three/entity/models/minecart.obj +233 -0
- package/src/three/entity/models/parrot.obj +509 -0
- package/src/three/entity/models/piglin.obj +739 -0
- package/src/three/entity/models/pillager.obj +371 -0
- package/src/three/entity/models/rabbit.obj +555 -0
- package/src/three/entity/models/sheep.obj +555 -0
- package/src/three/entity/models/shulker.obj +141 -0
- package/src/three/entity/models/sniffer.obj +693 -0
- package/src/three/entity/models/spider.obj +509 -0
- package/src/three/entity/models/tadpole.obj +95 -0
- package/src/three/entity/models/turtle.obj +371 -0
- package/src/three/entity/models/vex.obj +325 -0
- package/src/three/entity/models/villager.obj +509 -0
- package/src/three/entity/models/warden.obj +463 -0
- package/src/three/entity/models/witch.obj +647 -0
- package/src/three/entity/models/wolf.obj +509 -0
- package/src/three/entity/models/zombie_villager.obj +463 -0
- package/src/three/entity/objModels.js +1 -0
- package/src/three/fireworks.ts +661 -0
- package/src/three/fireworksRenderer.ts +434 -0
- package/src/three/globals.d.ts +7 -0
- package/src/three/graphicsBackend.ts +274 -0
- package/src/three/graphicsBackendOffThread.ts +107 -0
- package/src/three/hand.ts +89 -0
- package/src/three/holdingBlock.ts +926 -0
- package/src/three/index.ts +20 -0
- package/src/three/itemMesh.ts +427 -0
- package/src/three/modules.d.ts +14 -0
- package/src/three/panorama.ts +308 -0
- package/src/three/panoramaShared.ts +1 -0
- package/src/three/renderSlot.ts +82 -0
- package/src/three/skyboxRenderer.ts +406 -0
- package/src/three/starField.ts +13 -0
- package/src/three/threeJsMedia.ts +731 -0
- package/src/three/threeJsMethods.ts +15 -0
- package/src/three/threeJsParticles.ts +160 -0
- package/src/three/threeJsSound.ts +95 -0
- package/src/three/threeJsUtils.ts +90 -0
- package/src/three/waypointSprite.ts +435 -0
- package/src/three/waypoints.ts +163 -0
- package/src/three/world/cursorBlock.ts +172 -0
- package/src/three/world/vr.ts +257 -0
- package/src/three/worldGeometryExport.ts +259 -0
- package/src/three/worldGeometryHandler.ts +279 -0
- package/src/three/worldRendererThree.ts +1381 -0
- package/src/worldView/index.ts +6 -0
- package/src/worldView/types.ts +66 -0
- package/src/worldView/worldView.ts +424 -0
|
@@ -0,0 +1,1088 @@
|
|
|
1
|
+
/* eslint-disable guard-for-in */
|
|
2
|
+
import { EventEmitter } from 'events'
|
|
3
|
+
import { Vec3 } from 'vec3'
|
|
4
|
+
import mcDataRaw from 'minecraft-data/data.js' // note: using alias
|
|
5
|
+
import TypedEmitter from 'typed-emitter'
|
|
6
|
+
import { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
|
|
7
|
+
import { subscribeKey } from 'valtio/utils'
|
|
8
|
+
import { proxy } from 'valtio'
|
|
9
|
+
import type { ResourcesManagerTransferred } from '../resourcesManager/resourcesManager'
|
|
10
|
+
import { dynamicMcDataFiles } from './buildSharedConfig.mjs'
|
|
11
|
+
import { DisplayWorldOptions, GraphicsInitOptions, RendererReactiveState, SoundSystem } from '../graphicsBackend/types'
|
|
12
|
+
import { HighestBlockInfo, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig, MesherMainEvent } from '../mesher/shared'
|
|
13
|
+
import { chunkPos } from './simpleUtils'
|
|
14
|
+
import { addNewStat, removeAllStats, updatePanesVisibility, updateStatText } from './ui/newStats'
|
|
15
|
+
import { getPlayerStateUtils } from '../graphicsBackend/playerState'
|
|
16
|
+
// TODO: Fix PlayerStateRenderer and PlayerStateUtils imports
|
|
17
|
+
type PlayerStateUtils = ReturnType<typeof getPlayerStateUtils>
|
|
18
|
+
import { MesherLogReader } from './mesherlogReader'
|
|
19
|
+
import { setSkinsConfig } from './utils/skins'
|
|
20
|
+
import { generateSpiralMatrix, WorldViewWorker } from '../worldView'
|
|
21
|
+
import { PlayerStateReactive } from '@/playerState/playerState'
|
|
22
|
+
|
|
23
|
+
function mod(x, n) {
|
|
24
|
+
return ((x % n) + n) % n
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const toMajorVersion = version => {
|
|
28
|
+
const [a, b] = (String(version)).split('.')
|
|
29
|
+
return `${a}.${b}`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const defaultWorldRendererConfig = {
|
|
33
|
+
paused: false,
|
|
34
|
+
// Debug settings
|
|
35
|
+
showChunkBorders: false,
|
|
36
|
+
enableDebugOverlay: false,
|
|
37
|
+
debugModelVariant: undefined as undefined | number[],
|
|
38
|
+
|
|
39
|
+
// Performance settings
|
|
40
|
+
mesherWorkers: 4,
|
|
41
|
+
addChunksBatchWaitTime: 200,
|
|
42
|
+
_experimentalSmoothChunkLoading: true,
|
|
43
|
+
_renderByChunks: false,
|
|
44
|
+
|
|
45
|
+
// Rendering engine settings
|
|
46
|
+
dayCycle: true,
|
|
47
|
+
smoothLighting: true,
|
|
48
|
+
enableLighting: true,
|
|
49
|
+
starfield: true,
|
|
50
|
+
defaultSkybox: true,
|
|
51
|
+
renderEntities: true,
|
|
52
|
+
extraBlockRenderers: true,
|
|
53
|
+
foreground: true,
|
|
54
|
+
fov: 75,
|
|
55
|
+
volume: 1,
|
|
56
|
+
|
|
57
|
+
// Camera visual related settings
|
|
58
|
+
showHand: false,
|
|
59
|
+
viewBobbing: false,
|
|
60
|
+
renderEars: true,
|
|
61
|
+
highlightBlockColor: 'blue',
|
|
62
|
+
|
|
63
|
+
// Player models
|
|
64
|
+
fetchPlayerSkins: true,
|
|
65
|
+
skinTexturesProxy: undefined as string | undefined,
|
|
66
|
+
|
|
67
|
+
// VR settings
|
|
68
|
+
vrSupport: true,
|
|
69
|
+
vrPageGameRendering: true,
|
|
70
|
+
|
|
71
|
+
// World settings
|
|
72
|
+
clipWorldBelowY: undefined as number | undefined,
|
|
73
|
+
isPlayground: false,
|
|
74
|
+
instantCameraUpdate: false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type WorldRendererConfig = typeof defaultWorldRendererConfig
|
|
78
|
+
|
|
79
|
+
export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any> {
|
|
80
|
+
worldReadyResolvers = Promise.withResolvers<void>()
|
|
81
|
+
worldReadyPromise = this.worldReadyResolvers.promise
|
|
82
|
+
timeOfTheDay = 0
|
|
83
|
+
worldSizeParams = { minY: 0, worldHeight: 256 }
|
|
84
|
+
reactiveDebugParams = proxy({
|
|
85
|
+
stopRendering: false,
|
|
86
|
+
chunksRenderAboveOverride: undefined as number | undefined,
|
|
87
|
+
chunksRenderAboveEnabled: false,
|
|
88
|
+
chunksRenderBelowOverride: undefined as number | undefined,
|
|
89
|
+
chunksRenderBelowEnabled: false,
|
|
90
|
+
chunksRenderDistanceOverride: undefined as number | undefined,
|
|
91
|
+
chunksRenderDistanceEnabled: false,
|
|
92
|
+
disableEntities: false,
|
|
93
|
+
// disableParticles: false
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
active = false
|
|
97
|
+
|
|
98
|
+
// #region CHUNK & SECTIONS TRACKING
|
|
99
|
+
loadedChunks = {} as Record<string, boolean> // data is added for these chunks and they might be still processing
|
|
100
|
+
|
|
101
|
+
finishedChunks = {} as Record<string, boolean> // these chunks are fully loaded into the world (scene)
|
|
102
|
+
|
|
103
|
+
finishedSections = {} as Record<string, boolean> // these sections are fully loaded into the world (scene)
|
|
104
|
+
|
|
105
|
+
// loading sections (chunks)
|
|
106
|
+
sectionsWaiting = new Map<string, number>()
|
|
107
|
+
|
|
108
|
+
queuedChunks = new Set<string>()
|
|
109
|
+
queuedFunctions = [] as Array<() => void>
|
|
110
|
+
// #endregion
|
|
111
|
+
|
|
112
|
+
renderUpdateEmitter = new EventEmitter() as unknown as TypedEmitter<{
|
|
113
|
+
dirty(pos: Vec3, value: boolean): void
|
|
114
|
+
update(/* pos: Vec3, value: boolean */): void
|
|
115
|
+
chunkFinished(key: string): void
|
|
116
|
+
heightmap(key: string, heightmap: Uint8Array): void
|
|
117
|
+
}>
|
|
118
|
+
customTexturesDataUrl = undefined as string | undefined
|
|
119
|
+
workers: any[] = []
|
|
120
|
+
viewerChunkPosition?: Vec3
|
|
121
|
+
lastCamUpdate = 0
|
|
122
|
+
droppedFpsPercentage = 0
|
|
123
|
+
initialChunkLoadWasStartedIn: number | undefined
|
|
124
|
+
initialChunksLoad = true
|
|
125
|
+
enableChunksLoadDelay = false
|
|
126
|
+
texturesVersion?: string
|
|
127
|
+
viewDistance = -1
|
|
128
|
+
chunksLength = 0
|
|
129
|
+
allChunksFinished = false
|
|
130
|
+
messageQueue: any[] = []
|
|
131
|
+
isProcessingQueue = false
|
|
132
|
+
ONMESSAGE_TIME_LIMIT = 30 // ms
|
|
133
|
+
|
|
134
|
+
handleResize = () => { }
|
|
135
|
+
highestBlocksByChunks = new Map<string, { [chunkKey: string]: HighestBlockInfo }>()
|
|
136
|
+
blockEntities = {}
|
|
137
|
+
|
|
138
|
+
workersProcessAverageTime = 0
|
|
139
|
+
workersProcessAverageTimeCount = 0
|
|
140
|
+
maxWorkersProcessTime = 0
|
|
141
|
+
geometryReceiveCount = {} as Record<number, number>
|
|
142
|
+
allLoadedIn: undefined | number
|
|
143
|
+
onWorldSwitched = [] as Array<() => void>
|
|
144
|
+
renderTimeMax = 0
|
|
145
|
+
renderTimeAvg = 0
|
|
146
|
+
renderTimeAvgCount = 0
|
|
147
|
+
edgeChunks = {} as Record<string, boolean>
|
|
148
|
+
lastAddChunk = null as null | {
|
|
149
|
+
timeout: any
|
|
150
|
+
x: number
|
|
151
|
+
z: number
|
|
152
|
+
}
|
|
153
|
+
neighborChunkUpdates = true
|
|
154
|
+
lastChunkDistance = 0
|
|
155
|
+
debugStopGeometryUpdate = false
|
|
156
|
+
|
|
157
|
+
protocolCustomBlocks = new Map<string, CustomBlockModels>()
|
|
158
|
+
|
|
159
|
+
blockStateModelInfo = new Map<string, BlockStateModelInfo>()
|
|
160
|
+
|
|
161
|
+
abstract outputFormat: 'threeJs' | 'webgpu'
|
|
162
|
+
worldBlockProvider!: WorldBlockProvider
|
|
163
|
+
soundSystem: SoundSystem | undefined
|
|
164
|
+
|
|
165
|
+
abstract changeBackgroundColor(color: [number, number, number]): void
|
|
166
|
+
|
|
167
|
+
worldRendererConfig: WorldRendererConfig
|
|
168
|
+
playerStateReactive: PlayerStateReactive
|
|
169
|
+
playerStateUtils: PlayerStateUtils
|
|
170
|
+
reactiveState: RendererReactiveState
|
|
171
|
+
mesherLogReader: MesherLogReader | undefined
|
|
172
|
+
forceCallFromMesherReplayer = false
|
|
173
|
+
stopMesherMessagesProcessing = false
|
|
174
|
+
|
|
175
|
+
abortController = new AbortController()
|
|
176
|
+
lastRendered = 0
|
|
177
|
+
renderingActive = true
|
|
178
|
+
geometryReceiveCountPerSec = 0
|
|
179
|
+
mesherLogger = {
|
|
180
|
+
contents: [] as string[],
|
|
181
|
+
active: new URL(location.href).searchParams.get('mesherlog') === 'true'
|
|
182
|
+
}
|
|
183
|
+
currentRenderedFrames = 0
|
|
184
|
+
fpsAverage = 0
|
|
185
|
+
lastFps = 0
|
|
186
|
+
fpsWorst = undefined as number | undefined
|
|
187
|
+
fpsSamples = 0
|
|
188
|
+
backendInfoReport = '-'
|
|
189
|
+
chunksFullInfo = '-'
|
|
190
|
+
workerCustomHandleTime = 0
|
|
191
|
+
|
|
192
|
+
get version() {
|
|
193
|
+
return this.displayOptions.version
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
get displayAdvancedStats() {
|
|
197
|
+
return (this.initOptions.config.statsVisible ?? 0) > 1
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
constructor(public readonly resourcesManager: ResourcesManagerTransferred, public displayOptions: DisplayWorldOptions, public initOptions: GraphicsInitOptions) {
|
|
201
|
+
this.snapshotInitialValues()
|
|
202
|
+
this.worldRendererConfig = displayOptions.inWorldRenderingConfig
|
|
203
|
+
this.playerStateReactive = displayOptions.playerStateReactive!
|
|
204
|
+
this.playerStateUtils = getPlayerStateUtils(this.playerStateReactive)
|
|
205
|
+
this.reactiveState = displayOptions.rendererState!
|
|
206
|
+
// this.mesherLogReader = new MesherLogReader(this)
|
|
207
|
+
this.renderUpdateEmitter.on('update', () => {
|
|
208
|
+
const loadedChunks = Object.keys(this.finishedChunks).length
|
|
209
|
+
updateStatText('loaded-chunks', `${loadedChunks}/${this.chunksLength} chunks (${this.lastChunkDistance}/${this.viewDistance})`)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
addNewStat('downloaded-chunks', 100, 140, 20)
|
|
213
|
+
|
|
214
|
+
this.connect(this.displayOptions.worldView as any)
|
|
215
|
+
|
|
216
|
+
const chunksUpdateInterval = setInterval(() => {
|
|
217
|
+
this.geometryReceiveCountPerSec = Object.values(this.geometryReceiveCount).reduce((acc, curr) => acc + curr, 0)
|
|
218
|
+
this.geometryReceiveCount = {}
|
|
219
|
+
updatePanesVisibility(this.displayAdvancedStats)
|
|
220
|
+
this.updateChunksStats()
|
|
221
|
+
}, 500)
|
|
222
|
+
const fpsUpdateInterval = setInterval(() => {
|
|
223
|
+
this.fpsUpdate()
|
|
224
|
+
}, 1000)
|
|
225
|
+
this.abortController.signal.addEventListener('abort', () => {
|
|
226
|
+
clearInterval(chunksUpdateInterval)
|
|
227
|
+
clearInterval(fpsUpdateInterval)
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
fpsUpdate() {
|
|
232
|
+
this.fpsSamples++
|
|
233
|
+
this.fpsAverage = (this.fpsAverage * (this.fpsSamples - 1) + this.currentRenderedFrames) / this.fpsSamples
|
|
234
|
+
if (this.fpsWorst === undefined) {
|
|
235
|
+
this.fpsWorst = this.currentRenderedFrames
|
|
236
|
+
} else {
|
|
237
|
+
this.fpsWorst = Math.min(this.fpsWorst, this.currentRenderedFrames)
|
|
238
|
+
}
|
|
239
|
+
this.lastFps = this.currentRenderedFrames
|
|
240
|
+
this.displayOptions.nonReactiveState.fps = this.currentRenderedFrames
|
|
241
|
+
this.displayOptions.nonReactiveState.worstRenderTime = this.renderTimeMax
|
|
242
|
+
this.displayOptions.nonReactiveState.avgRenderTime = this.renderTimeAvg
|
|
243
|
+
this.currentRenderedFrames = 0
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
logWorkerWork(message: string | (() => string)) {
|
|
247
|
+
if (!this.mesherLogger.active) return
|
|
248
|
+
this.mesherLogger.contents.push(typeof message === 'function' ? message() : message)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async init() {
|
|
252
|
+
if (this.active) throw new Error('WorldRendererCommon is already initialized')
|
|
253
|
+
|
|
254
|
+
await Promise.all([
|
|
255
|
+
this.resetWorkers(),
|
|
256
|
+
(async () => {
|
|
257
|
+
if (this.resourcesManager.currentResources?.allReady) {
|
|
258
|
+
await this.updateAssetsData()
|
|
259
|
+
}
|
|
260
|
+
})()
|
|
261
|
+
])
|
|
262
|
+
|
|
263
|
+
this.resourcesManager.on('assetsTexturesUpdated', async () => {
|
|
264
|
+
if (!this.active) return
|
|
265
|
+
await this.updateAssetsData()
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
this.watchReactivePlayerState()
|
|
269
|
+
this.watchReactiveConfig()
|
|
270
|
+
this.worldReadyResolvers.resolve()
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
snapshotInitialValues() { }
|
|
274
|
+
|
|
275
|
+
wasChunkSentToWorker(chunkKey: string) {
|
|
276
|
+
return this.loadedChunks[chunkKey]
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async getHighestBlocks(chunkKey: string) {
|
|
280
|
+
return this.highestBlocksByChunks.get(chunkKey)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
updateCustomBlock(chunkKey: string, blockPos: string, model: string) {
|
|
284
|
+
this.protocolCustomBlocks.set(chunkKey, {
|
|
285
|
+
...this.protocolCustomBlocks.get(chunkKey),
|
|
286
|
+
[blockPos]: model
|
|
287
|
+
})
|
|
288
|
+
this.logWorkerWork(() => `-> updateCustomBlock ${chunkKey} ${blockPos} ${model} ${this.wasChunkSentToWorker(chunkKey)}`)
|
|
289
|
+
if (this.wasChunkSentToWorker(chunkKey)) {
|
|
290
|
+
const [x, y, z] = blockPos.split(',').map(Number)
|
|
291
|
+
this.setBlockStateId(new Vec3(x, y, z), undefined)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async getBlockInfo(blockPos: { x: number, y: number, z: number }, stateId: number) {
|
|
296
|
+
const chunkKey = `${Math.floor(blockPos.x / 16) * 16},${Math.floor(blockPos.z / 16) * 16}`
|
|
297
|
+
const customBlockName = this.protocolCustomBlocks.get(chunkKey)?.[`${blockPos.x},${blockPos.y},${blockPos.z}`]
|
|
298
|
+
const cacheKey = getBlockAssetsCacheKey(stateId, customBlockName)
|
|
299
|
+
const modelInfo = this.blockStateModelInfo.get(cacheKey)
|
|
300
|
+
return {
|
|
301
|
+
customBlockName,
|
|
302
|
+
modelInfo
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
initWorkers(numWorkers = this.worldRendererConfig.mesherWorkers) {
|
|
307
|
+
// init workers
|
|
308
|
+
for (let i = 0; i < numWorkers + 1; i++) {
|
|
309
|
+
const worker = initMesherWorker((data) => {
|
|
310
|
+
if (Array.isArray(data)) {
|
|
311
|
+
this.messageQueue.push(...data)
|
|
312
|
+
} else {
|
|
313
|
+
this.messageQueue.push(data)
|
|
314
|
+
}
|
|
315
|
+
void this.processMessageQueue('worker')
|
|
316
|
+
})
|
|
317
|
+
this.workers.push(worker)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
onReactivePlayerStateUpdated<T extends keyof PlayerStateReactive>(key: T, callback: (value: PlayerStateReactive[T]) => void, initial = true) {
|
|
322
|
+
if (initial) {
|
|
323
|
+
callback(this.playerStateReactive[key])
|
|
324
|
+
}
|
|
325
|
+
subscribeKey(this.playerStateReactive, key, callback)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
onReactiveConfigUpdated<T extends keyof typeof this.worldRendererConfig>(key: T, callback: (value: typeof this.worldRendererConfig[T]) => void) {
|
|
329
|
+
callback(this.worldRendererConfig[key])
|
|
330
|
+
subscribeKey(this.worldRendererConfig, key, callback)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
onReactiveDebugUpdated<T extends keyof typeof this.reactiveDebugParams>(key: T, callback: (value: typeof this.reactiveDebugParams[T]) => void) {
|
|
334
|
+
callback(this.reactiveDebugParams[key])
|
|
335
|
+
subscribeKey(this.reactiveDebugParams, key, callback)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
watchReactivePlayerState() {
|
|
339
|
+
this.onReactivePlayerStateUpdated('backgroundColor', (value) => {
|
|
340
|
+
this.changeBackgroundColor(value)
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
watchReactiveConfig() {
|
|
345
|
+
this.onReactiveConfigUpdated('fetchPlayerSkins', (value) => {
|
|
346
|
+
setSkinsConfig({ apiEnabled: value })
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async processMessageQueue(source: string) {
|
|
351
|
+
if (this.isProcessingQueue || this.messageQueue.length === 0) return
|
|
352
|
+
this.logWorkerWork(`# ${source} processing queue`)
|
|
353
|
+
if (this.lastRendered && performance.now() - this.lastRendered > this.ONMESSAGE_TIME_LIMIT && this.worldRendererConfig._experimentalSmoothChunkLoading && this.renderingActive) {
|
|
354
|
+
const start = performance.now()
|
|
355
|
+
await new Promise(resolve => {
|
|
356
|
+
requestAnimationFrame(resolve)
|
|
357
|
+
})
|
|
358
|
+
this.logWorkerWork(`# processing got delayed by ${performance.now() - start}ms`)
|
|
359
|
+
}
|
|
360
|
+
this.isProcessingQueue = true
|
|
361
|
+
|
|
362
|
+
const startTime = performance.now()
|
|
363
|
+
let processedCount = 0
|
|
364
|
+
|
|
365
|
+
while (this.messageQueue.length > 0) {
|
|
366
|
+
const processingStopped = this.stopMesherMessagesProcessing
|
|
367
|
+
if (!processingStopped) {
|
|
368
|
+
const data = this.messageQueue.shift()!
|
|
369
|
+
this.handleMessage(data)
|
|
370
|
+
processedCount++
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Check if we've exceeded the time limit
|
|
374
|
+
if (processingStopped || (performance.now() - startTime > this.ONMESSAGE_TIME_LIMIT && this.renderingActive && this.worldRendererConfig._experimentalSmoothChunkLoading)) {
|
|
375
|
+
// If we have more messages and exceeded time limit, schedule next batch
|
|
376
|
+
if (this.messageQueue.length > 0) {
|
|
377
|
+
requestAnimationFrame(async () => {
|
|
378
|
+
this.isProcessingQueue = false
|
|
379
|
+
void this.processMessageQueue('queue-delay')
|
|
380
|
+
})
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
break
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
this.isProcessingQueue = false
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
handleMessage(rawData: any) {
|
|
391
|
+
const data = rawData as MesherMainEvent
|
|
392
|
+
if (!this.active) return
|
|
393
|
+
this.mesherLogReader?.workerMessageReceived(data.type, data)
|
|
394
|
+
if (data.type !== 'geometry' || !this.debugStopGeometryUpdate) {
|
|
395
|
+
const start = performance.now()
|
|
396
|
+
this.handleWorkerMessage(data as WorkerReceive)
|
|
397
|
+
this.workerCustomHandleTime += performance.now() - start
|
|
398
|
+
}
|
|
399
|
+
if (data.type === 'geometry') {
|
|
400
|
+
this.logWorkerWork(() => `-> ${data.workerIndex} geometry ${data.key} ${JSON.stringify({ dataSize: JSON.stringify(data).length })}`)
|
|
401
|
+
this.geometryReceiveCount[data.workerIndex] ??= 0
|
|
402
|
+
this.geometryReceiveCount[data.workerIndex]++
|
|
403
|
+
const chunkCoords = data.key.split(',').map(Number)
|
|
404
|
+
this.lastChunkDistance = Math.max(...this.getDistance(new Vec3(chunkCoords[0], 0, chunkCoords[2])))
|
|
405
|
+
}
|
|
406
|
+
if (data.type === 'sectionFinished') { // on after load & unload section
|
|
407
|
+
this.logWorkerWork(`<- ${data.workerIndex} sectionFinished ${data.key} ${JSON.stringify({ processTime: data.processTime })}`)
|
|
408
|
+
if (!this.sectionsWaiting.has(data.key)) throw new Error(`sectionFinished event for non-outstanding section ${data.key}`)
|
|
409
|
+
this.sectionsWaiting.set(data.key, this.sectionsWaiting.get(data.key)! - 1)
|
|
410
|
+
if (this.sectionsWaiting.get(data.key) === 0) {
|
|
411
|
+
this.sectionsWaiting.delete(data.key)
|
|
412
|
+
this.finishedSections[data.key] = true
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const chunkCoords = data.key.split(',').map(Number)
|
|
416
|
+
const chunkKey = `${chunkCoords[0]},${chunkCoords[2]}`
|
|
417
|
+
if (this.loadedChunks[chunkKey]) { // ensure chunk data was added, not a neighbor chunk update
|
|
418
|
+
let loaded = true
|
|
419
|
+
for (let y = this.worldMinYRender; y < this.worldSizeParams.worldHeight; y += 16) {
|
|
420
|
+
if (!this.finishedSections[`${chunkCoords[0]},${y},${chunkCoords[2]}`]) {
|
|
421
|
+
loaded = false
|
|
422
|
+
break
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (loaded) {
|
|
426
|
+
// CHUNK FINISHED
|
|
427
|
+
this.finishedChunks[chunkKey] = true
|
|
428
|
+
this.reactiveState.world.chunksLoaded.add(`${Math.floor(chunkCoords[0] / 16)},${Math.floor(chunkCoords[2] / 16)}`)
|
|
429
|
+
this.renderUpdateEmitter.emit(`chunkFinished`, `${chunkCoords[0]},${chunkCoords[2]}`)
|
|
430
|
+
this.checkAllFinished()
|
|
431
|
+
// merge highest blocks by sections into highest blocks by chunks
|
|
432
|
+
// for (let y = this.worldMinYRender; y < this.worldSizeParams.worldHeight; y += 16) {
|
|
433
|
+
// const sectionKey = `${chunkCoords[0]},${y},${chunkCoords[2]}`
|
|
434
|
+
// for (let x = 0; x < 16; x++) {
|
|
435
|
+
// for (let z = 0; z < 16; z++) {
|
|
436
|
+
// const posInsideKey = `${chunkCoords[0] + x},${chunkCoords[2] + z}`
|
|
437
|
+
// let block = null as HighestBlockInfo | null
|
|
438
|
+
// const highestBlock = this.highestBlocksBySections[sectionKey]?.[posInsideKey]
|
|
439
|
+
// if (!highestBlock) continue
|
|
440
|
+
// if (!block || highestBlock.y > block.y) {
|
|
441
|
+
// block = highestBlock
|
|
442
|
+
// }
|
|
443
|
+
// if (block) {
|
|
444
|
+
// this.highestBlocksByChunks[chunkKey] ??= {}
|
|
445
|
+
// this.highestBlocksByChunks[chunkKey][posInsideKey] = block
|
|
446
|
+
// }
|
|
447
|
+
// }
|
|
448
|
+
// }
|
|
449
|
+
// delete this.highestBlocksBySections[sectionKey]
|
|
450
|
+
// }
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
this.renderUpdateEmitter.emit('update')
|
|
455
|
+
if (data.processTime) {
|
|
456
|
+
this.workersProcessAverageTimeCount++
|
|
457
|
+
this.workersProcessAverageTime = ((this.workersProcessAverageTime * (this.workersProcessAverageTimeCount - 1)) + data.processTime) / this.workersProcessAverageTimeCount
|
|
458
|
+
this.maxWorkersProcessTime = Math.max(this.maxWorkersProcessTime, data.processTime)
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (data.type === 'blockStateModelInfo') {
|
|
463
|
+
for (const [cacheKey, info] of Object.entries(data.info)) {
|
|
464
|
+
this.blockStateModelInfo.set(cacheKey, info)
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (data.type === 'heightmap') {
|
|
469
|
+
this.reactiveState.world.heightmaps.set(data.key, new Uint8Array(data.heightmap))
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
downloadMesherLog() {
|
|
474
|
+
const a = document.createElement('a')
|
|
475
|
+
a.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent(this.mesherLogger.contents.join('\n'))
|
|
476
|
+
a.download = 'mesher.log'
|
|
477
|
+
a.click()
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
checkAllFinished() {
|
|
481
|
+
if (this.sectionsWaiting.size === 0) {
|
|
482
|
+
this.reactiveState.world.mesherWork = false
|
|
483
|
+
}
|
|
484
|
+
// todo check exact surrounding chunks
|
|
485
|
+
const allFinished = Object.keys(this.finishedChunks).length >= this.chunksLength
|
|
486
|
+
if (allFinished) {
|
|
487
|
+
this.allChunksLoaded?.()
|
|
488
|
+
this.allChunksFinished = true
|
|
489
|
+
this.allLoadedIn ??= Date.now() - this.initialChunkLoadWasStartedIn!
|
|
490
|
+
}
|
|
491
|
+
this.updateChunksStats()
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
changeHandSwingingState(isAnimationPlaying: boolean, isLeftHand: boolean): void { }
|
|
495
|
+
|
|
496
|
+
abstract handleWorkerMessage(data: WorkerReceive): void
|
|
497
|
+
|
|
498
|
+
abstract updateCamera(pos: Vec3 | null, yaw: number, pitch: number): void
|
|
499
|
+
|
|
500
|
+
abstract render(): void
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Optionally update data that are depedendent on the viewer position
|
|
504
|
+
*/
|
|
505
|
+
updatePosDataChunk?(key: string): void
|
|
506
|
+
|
|
507
|
+
allChunksLoaded?(): void
|
|
508
|
+
|
|
509
|
+
timeUpdated?(newTime: number): void
|
|
510
|
+
|
|
511
|
+
biomeUpdated?(biome: any): void
|
|
512
|
+
|
|
513
|
+
biomeReset?(): void
|
|
514
|
+
|
|
515
|
+
updateViewerPosition(pos: Vec3) {
|
|
516
|
+
this.viewerChunkPosition = pos
|
|
517
|
+
for (const [key, value] of Object.entries(this.loadedChunks)) {
|
|
518
|
+
if (!value) continue
|
|
519
|
+
this.updatePosDataChunk?.(key)
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
sendWorkers(message: WorkerSend) {
|
|
524
|
+
for (const worker of this.workers) {
|
|
525
|
+
worker.postMessage(message)
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
getDistance(posAbsolute: Vec3) {
|
|
530
|
+
const [botX, botZ] = chunkPos(this.viewerChunkPosition!)
|
|
531
|
+
const dx = Math.abs(botX - Math.floor(posAbsolute.x / 16))
|
|
532
|
+
const dz = Math.abs(botZ - Math.floor(posAbsolute.z / 16))
|
|
533
|
+
return [dx, dz] as [number, number]
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
abstract updateShowChunksBorder(value: boolean): void
|
|
537
|
+
|
|
538
|
+
resetWorld() {
|
|
539
|
+
// destroy workers
|
|
540
|
+
for (const worker of this.workers) {
|
|
541
|
+
worker.terminate()
|
|
542
|
+
}
|
|
543
|
+
this.workers = []
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async resetWorkers() {
|
|
547
|
+
this.resetWorld()
|
|
548
|
+
|
|
549
|
+
// for workers in single file build
|
|
550
|
+
if (typeof document !== 'undefined' && document?.readyState === 'loading') {
|
|
551
|
+
await new Promise(resolve => {
|
|
552
|
+
document.addEventListener('DOMContentLoaded', resolve)
|
|
553
|
+
})
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
this.initWorkers()
|
|
557
|
+
this.active = true
|
|
558
|
+
|
|
559
|
+
this.sendMesherMcData()
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
getMesherConfig(): MesherConfig {
|
|
563
|
+
let skyLight = 15
|
|
564
|
+
const timeOfDay = this.timeOfTheDay
|
|
565
|
+
if (timeOfDay < 0 || timeOfDay > 24_000) {
|
|
566
|
+
//
|
|
567
|
+
} else if (timeOfDay <= 6000 || timeOfDay >= 18_000) {
|
|
568
|
+
skyLight = 15
|
|
569
|
+
} else if (timeOfDay > 6000 && timeOfDay < 12_000) {
|
|
570
|
+
skyLight = 15 - ((timeOfDay - 6000) / 6000) * 15
|
|
571
|
+
} else if (timeOfDay >= 12_000 && timeOfDay < 18_000) {
|
|
572
|
+
skyLight = ((timeOfDay - 12_000) / 6000) * 15
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
skyLight = Math.floor(skyLight)
|
|
576
|
+
return {
|
|
577
|
+
version: this.version,
|
|
578
|
+
enableLighting: this.worldRendererConfig.enableLighting,
|
|
579
|
+
skyLight,
|
|
580
|
+
smoothLighting: this.worldRendererConfig.smoothLighting,
|
|
581
|
+
outputFormat: this.outputFormat,
|
|
582
|
+
// textureSize: this.resourcesManager.currentResources!.blocksAtlasParser.atlas.latest.width,
|
|
583
|
+
debugModelVariant: this.worldRendererConfig.debugModelVariant,
|
|
584
|
+
clipWorldBelowY: this.worldRendererConfig.clipWorldBelowY,
|
|
585
|
+
disableBlockEntityTextures: !this.worldRendererConfig.extraBlockRenderers,
|
|
586
|
+
worldMinY: this.worldMinYRender,
|
|
587
|
+
worldMaxY: this.worldMinYRender + this.worldSizeParams.worldHeight,
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
sendMesherMcData() {
|
|
592
|
+
const allMcData = mcDataRaw.pc[this.version] ?? mcDataRaw.pc[toMajorVersion(this.version)]
|
|
593
|
+
const mcData = {
|
|
594
|
+
version: JSON.parse(JSON.stringify(allMcData.version))
|
|
595
|
+
}
|
|
596
|
+
for (const key of dynamicMcDataFiles) {
|
|
597
|
+
mcData[key] = allMcData[key]
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
for (const worker of this.workers) {
|
|
601
|
+
worker.postMessage({ type: 'mcData', mcData, config: this.getMesherConfig() })
|
|
602
|
+
}
|
|
603
|
+
this.logWorkerWork('# mcData sent')
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
async updateAssetsData() {
|
|
607
|
+
const resources = this.resourcesManager.currentResources
|
|
608
|
+
|
|
609
|
+
if (this.workers.length === 0) throw new Error('workers not initialized yet')
|
|
610
|
+
for (const [i, worker] of this.workers.entries()) {
|
|
611
|
+
const { blockstatesModels } = resources
|
|
612
|
+
|
|
613
|
+
worker.postMessage({
|
|
614
|
+
type: 'mesherData',
|
|
615
|
+
workerIndex: i,
|
|
616
|
+
blocksAtlas: {
|
|
617
|
+
latest: resources.blocksAtlasJson
|
|
618
|
+
},
|
|
619
|
+
blockstatesModels,
|
|
620
|
+
config: this.getMesherConfig(),
|
|
621
|
+
})
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
this.logWorkerWork('# mesherData sent')
|
|
625
|
+
console.log('textures loaded')
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
get worldMinYRender() {
|
|
629
|
+
return Math.floor(Math.max(this.worldSizeParams.minY, this.worldRendererConfig.clipWorldBelowY ?? -Infinity) / 16) * 16
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
updateChunksStats() {
|
|
633
|
+
const loadedChunks = Object.keys(this.finishedChunks)
|
|
634
|
+
this.displayOptions.nonReactiveState.world.chunksLoaded = new Set(loadedChunks)
|
|
635
|
+
this.displayOptions.nonReactiveState.world.chunksTotalNumber = this.chunksLength
|
|
636
|
+
this.reactiveState.world.allChunksLoaded = this.allChunksFinished
|
|
637
|
+
|
|
638
|
+
const text = `Q: ${this.messageQueue.length} ${Object.keys(this.loadedChunks).length}/${Object.keys(this.finishedChunks).length}/${this.chunksLength} chunks (${this.workers.length}:${this.workersProcessAverageTime.toFixed(0)}ms/${this.geometryReceiveCountPerSec}ss/${this.allLoadedIn?.toFixed(1) ?? '-'}s)`
|
|
639
|
+
this.chunksFullInfo = text
|
|
640
|
+
updateStatText('downloaded-chunks', text)
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
addColumn(x: number, z: number, chunk: any, isLightUpdate: boolean) {
|
|
644
|
+
if (!this.active) return
|
|
645
|
+
if (this.workers.length === 0) throw new Error('workers not initialized yet')
|
|
646
|
+
this.initialChunksLoad = false
|
|
647
|
+
this.initialChunkLoadWasStartedIn ??= Date.now()
|
|
648
|
+
this.loadedChunks[`${x},${z}`] = true
|
|
649
|
+
this.updateChunksStats()
|
|
650
|
+
|
|
651
|
+
const chunkKey = `${x},${z}`
|
|
652
|
+
const customBlockModels = this.protocolCustomBlocks.get(chunkKey)
|
|
653
|
+
|
|
654
|
+
for (const worker of this.workers) {
|
|
655
|
+
worker.postMessage({
|
|
656
|
+
type: 'chunk',
|
|
657
|
+
x,
|
|
658
|
+
z,
|
|
659
|
+
chunk,
|
|
660
|
+
customBlockModels: customBlockModels || undefined
|
|
661
|
+
})
|
|
662
|
+
}
|
|
663
|
+
// this.workers[0].postMessage({
|
|
664
|
+
// type: 'getHeightmap',
|
|
665
|
+
// x,
|
|
666
|
+
// z,
|
|
667
|
+
// })
|
|
668
|
+
this.logWorkerWork(() => `-> chunk ${JSON.stringify({ x, z, chunkLength: chunk.length, customBlockModelsLength: customBlockModels ? Object.keys(customBlockModels).length : 0 })}`)
|
|
669
|
+
this.mesherLogReader?.chunkReceived(x, z, chunk.length)
|
|
670
|
+
for (let y = this.worldMinYRender; y < this.worldSizeParams.worldHeight; y += 16) {
|
|
671
|
+
const loc = new Vec3(x, y, z)
|
|
672
|
+
this.setSectionDirty(loc)
|
|
673
|
+
if (this.neighborChunkUpdates && (!isLightUpdate || this.worldRendererConfig.smoothLighting)) {
|
|
674
|
+
this.setSectionDirty(loc.offset(-16, 0, 0))
|
|
675
|
+
this.setSectionDirty(loc.offset(16, 0, 0))
|
|
676
|
+
this.setSectionDirty(loc.offset(0, 0, -16))
|
|
677
|
+
this.setSectionDirty(loc.offset(0, 0, 16))
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
markAsLoaded(x, z) {
|
|
683
|
+
this.loadedChunks[`${x},${z}`] = true
|
|
684
|
+
this.finishedChunks[`${x},${z}`] = true
|
|
685
|
+
this.logWorkerWork(`-> markAsLoaded ${JSON.stringify({ x, z })}`)
|
|
686
|
+
this.checkAllFinished()
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
removeColumn(x, z) {
|
|
690
|
+
delete this.loadedChunks[`${x},${z}`]
|
|
691
|
+
for (const worker of this.workers) {
|
|
692
|
+
worker.postMessage({ type: 'unloadChunk', x, z })
|
|
693
|
+
}
|
|
694
|
+
this.logWorkerWork(`-> unloadChunk ${JSON.stringify({ x, z })}`)
|
|
695
|
+
delete this.finishedChunks[`${x},${z}`]
|
|
696
|
+
this.allChunksFinished = Object.keys(this.finishedChunks).length === this.chunksLength
|
|
697
|
+
if (Object.keys(this.finishedChunks).length === 0) {
|
|
698
|
+
this.allLoadedIn = undefined
|
|
699
|
+
this.initialChunkLoadWasStartedIn = undefined
|
|
700
|
+
}
|
|
701
|
+
for (let y = this.worldSizeParams.minY; y < this.worldSizeParams.worldHeight; y += 16) {
|
|
702
|
+
this.setSectionDirty(new Vec3(x, y, z), false)
|
|
703
|
+
delete this.finishedSections[`${x},${y},${z}`]
|
|
704
|
+
}
|
|
705
|
+
this.highestBlocksByChunks.delete(`${x},${z}`)
|
|
706
|
+
|
|
707
|
+
this.updateChunksStats()
|
|
708
|
+
|
|
709
|
+
if (Object.keys(this.loadedChunks).length === 0) {
|
|
710
|
+
this.mesherLogger.contents = []
|
|
711
|
+
this.logWorkerWork('# all chunks unloaded. New log started')
|
|
712
|
+
void this.mesherLogReader?.maybeStartReplay()
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
setBlockStateId(pos: Vec3, stateId: number | undefined, needAoRecalculation = true) {
|
|
717
|
+
const set = async () => {
|
|
718
|
+
const sectionX = Math.floor(pos.x / 16) * 16
|
|
719
|
+
const sectionZ = Math.floor(pos.z / 16) * 16
|
|
720
|
+
if (this.queuedChunks.has(`${sectionX},${sectionZ}`)) {
|
|
721
|
+
await new Promise<void>(resolve => {
|
|
722
|
+
this.queuedFunctions.push(() => {
|
|
723
|
+
resolve()
|
|
724
|
+
})
|
|
725
|
+
})
|
|
726
|
+
}
|
|
727
|
+
if (!this.loadedChunks[`${sectionX},${sectionZ}`]) {
|
|
728
|
+
// console.debug('[should be unreachable] setBlockStateId called for unloaded chunk', pos)
|
|
729
|
+
}
|
|
730
|
+
this.setBlockStateIdInner(pos, stateId, needAoRecalculation)
|
|
731
|
+
}
|
|
732
|
+
void set()
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
updateEntity(e: any, isUpdate = false) { }
|
|
736
|
+
|
|
737
|
+
abstract updatePlayerEntity?(e: any): void
|
|
738
|
+
|
|
739
|
+
lightUpdate(chunkX: number, chunkZ: number) { }
|
|
740
|
+
|
|
741
|
+
connect(worldView: WorldViewWorker) {
|
|
742
|
+
const worldEmitter = worldView
|
|
743
|
+
|
|
744
|
+
worldEmitter.on('entity', (e) => {
|
|
745
|
+
this.updateEntity(e, false)
|
|
746
|
+
})
|
|
747
|
+
worldEmitter.on('entityMoved', (e) => {
|
|
748
|
+
this.updateEntity(e, true)
|
|
749
|
+
})
|
|
750
|
+
worldEmitter.on('playerEntity', (e) => {
|
|
751
|
+
this.updatePlayerEntity?.(e)
|
|
752
|
+
})
|
|
753
|
+
|
|
754
|
+
let currentLoadChunkBatch = null as {
|
|
755
|
+
timeout
|
|
756
|
+
data
|
|
757
|
+
} | null
|
|
758
|
+
worldEmitter.on('loadChunk', ({ x, z, chunk, worldConfig, isLightUpdate }) => {
|
|
759
|
+
this.worldSizeParams = worldConfig
|
|
760
|
+
this.queuedChunks.add(`${x},${z}`)
|
|
761
|
+
const args = [x, z, chunk, isLightUpdate]
|
|
762
|
+
if (!currentLoadChunkBatch) {
|
|
763
|
+
// add a setting to use debounce instead
|
|
764
|
+
currentLoadChunkBatch = {
|
|
765
|
+
data: [],
|
|
766
|
+
timeout: setTimeout(() => {
|
|
767
|
+
for (const args of currentLoadChunkBatch!.data) {
|
|
768
|
+
this.queuedChunks.delete(`${args[0]},${args[1]}`)
|
|
769
|
+
this.addColumn(...args as Parameters<typeof this.addColumn>)
|
|
770
|
+
}
|
|
771
|
+
for (const fn of this.queuedFunctions) {
|
|
772
|
+
fn()
|
|
773
|
+
}
|
|
774
|
+
this.queuedFunctions = []
|
|
775
|
+
currentLoadChunkBatch = null
|
|
776
|
+
}, this.worldRendererConfig.addChunksBatchWaitTime)
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
currentLoadChunkBatch.data.push(args)
|
|
780
|
+
})
|
|
781
|
+
// todo remove and use other architecture instead so data flow is clear
|
|
782
|
+
worldEmitter.on('blockEntities', (blockEntities) => {
|
|
783
|
+
this.blockEntities = blockEntities
|
|
784
|
+
})
|
|
785
|
+
|
|
786
|
+
worldEmitter.on('unloadChunk', ({ x, z }) => {
|
|
787
|
+
this.removeColumn(x, z)
|
|
788
|
+
})
|
|
789
|
+
|
|
790
|
+
worldEmitter.on('blockUpdate', ({ pos, stateId }) => {
|
|
791
|
+
this.setBlockStateId(new Vec3(pos.x, pos.y, pos.z), stateId)
|
|
792
|
+
})
|
|
793
|
+
|
|
794
|
+
worldEmitter.on('chunkPosUpdate', ({ pos }) => {
|
|
795
|
+
this.updateViewerPosition(pos)
|
|
796
|
+
})
|
|
797
|
+
|
|
798
|
+
worldEmitter.on('end', () => {
|
|
799
|
+
this.worldStop?.()
|
|
800
|
+
})
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
worldEmitter.on('renderDistance', (d) => {
|
|
804
|
+
this.viewDistance = d
|
|
805
|
+
this.chunksLength = d === 0 ? 1 : generateSpiralMatrix(d).length
|
|
806
|
+
this.allChunksFinished = Object.keys(this.finishedChunks).length === this.chunksLength
|
|
807
|
+
})
|
|
808
|
+
|
|
809
|
+
worldEmitter.on('markAsLoaded', ({ x, z }) => {
|
|
810
|
+
this.markAsLoaded(x, z)
|
|
811
|
+
})
|
|
812
|
+
|
|
813
|
+
worldEmitter.on('updateLight', ({ pos }) => {
|
|
814
|
+
this.lightUpdate(pos.x, pos.z)
|
|
815
|
+
})
|
|
816
|
+
|
|
817
|
+
worldEmitter.on('onWorldSwitch', () => {
|
|
818
|
+
for (const fn of this.onWorldSwitched) {
|
|
819
|
+
try {
|
|
820
|
+
fn()
|
|
821
|
+
} catch (e) {
|
|
822
|
+
setTimeout(() => {
|
|
823
|
+
console.log('[Renderer Backend] Error in onWorldSwitched:')
|
|
824
|
+
throw e
|
|
825
|
+
}, 0)
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
})
|
|
829
|
+
|
|
830
|
+
worldEmitter.on('time', (timeOfDay) => {
|
|
831
|
+
if (!this.worldRendererConfig.dayCycle) return
|
|
832
|
+
this.timeUpdated?.(timeOfDay)
|
|
833
|
+
|
|
834
|
+
this.timeOfTheDay = timeOfDay
|
|
835
|
+
|
|
836
|
+
// if (this.worldRendererConfig.skyLight === skyLight) return
|
|
837
|
+
// this.worldRendererConfig.skyLight = skyLight
|
|
838
|
+
// if (this instanceof WorldRendererThree) {
|
|
839
|
+
// (this).rerenderAllChunks?.()
|
|
840
|
+
// }
|
|
841
|
+
})
|
|
842
|
+
|
|
843
|
+
worldEmitter.on('biomeUpdate', ({ biome }) => {
|
|
844
|
+
this.biomeUpdated?.(biome)
|
|
845
|
+
})
|
|
846
|
+
|
|
847
|
+
worldEmitter.on('biomeReset', () => {
|
|
848
|
+
this.biomeReset?.()
|
|
849
|
+
})
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
setBlockStateIdInner(pos: Vec3, stateId: number | undefined, needAoRecalculation = true) {
|
|
853
|
+
const chunkKey = `${Math.floor(pos.x / 16) * 16},${Math.floor(pos.z / 16) * 16}`
|
|
854
|
+
const blockPosKey = `${pos.x},${pos.y},${pos.z}`
|
|
855
|
+
const customBlockModels = this.protocolCustomBlocks.get(chunkKey) || {}
|
|
856
|
+
|
|
857
|
+
for (const worker of this.workers) {
|
|
858
|
+
worker.postMessage({
|
|
859
|
+
type: 'blockUpdate',
|
|
860
|
+
pos,
|
|
861
|
+
stateId,
|
|
862
|
+
customBlockModels
|
|
863
|
+
})
|
|
864
|
+
}
|
|
865
|
+
this.logWorkerWork(`-> blockUpdate ${JSON.stringify({ pos, stateId, customBlockModels })}`)
|
|
866
|
+
this.setSectionDirty(pos, true, true)
|
|
867
|
+
if (this.neighborChunkUpdates) {
|
|
868
|
+
if ((pos.x & 15) === 0) this.setSectionDirty(pos.offset(-16, 0, 0), true, true)
|
|
869
|
+
if ((pos.x & 15) === 15) this.setSectionDirty(pos.offset(16, 0, 0), true, true)
|
|
870
|
+
if ((pos.y & 15) === 0) this.setSectionDirty(pos.offset(0, -16, 0), true, true)
|
|
871
|
+
if ((pos.y & 15) === 15) this.setSectionDirty(pos.offset(0, 16, 0), true, true)
|
|
872
|
+
if ((pos.z & 15) === 0) this.setSectionDirty(pos.offset(0, 0, -16), true, true)
|
|
873
|
+
if ((pos.z & 15) === 15) this.setSectionDirty(pos.offset(0, 0, 16), true, true)
|
|
874
|
+
|
|
875
|
+
if (needAoRecalculation) {
|
|
876
|
+
// top view neighbors
|
|
877
|
+
if ((pos.x & 15) === 0 && (pos.z & 15) === 0) this.setSectionDirty(pos.offset(-16, 0, -16), true, true)
|
|
878
|
+
if ((pos.x & 15) === 15 && (pos.z & 15) === 0) this.setSectionDirty(pos.offset(16, 0, -16), true, true)
|
|
879
|
+
if ((pos.x & 15) === 0 && (pos.z & 15) === 15) this.setSectionDirty(pos.offset(-16, 0, 16), true, true)
|
|
880
|
+
if ((pos.x & 15) === 15 && (pos.z & 15) === 15) this.setSectionDirty(pos.offset(16, 0, 16), true, true)
|
|
881
|
+
|
|
882
|
+
// side view neighbors (but ignore updates above)
|
|
883
|
+
// z view neighbors
|
|
884
|
+
if ((pos.x & 15) === 0 && (pos.y & 15) === 0) this.setSectionDirty(pos.offset(-16, -16, 0), true, true)
|
|
885
|
+
if ((pos.x & 15) === 15 && (pos.y & 15) === 0) this.setSectionDirty(pos.offset(16, -16, 0), true, true)
|
|
886
|
+
|
|
887
|
+
// x view neighbors
|
|
888
|
+
if ((pos.z & 15) === 0 && (pos.y & 15) === 0) this.setSectionDirty(pos.offset(0, -16, -16), true, true)
|
|
889
|
+
if ((pos.z & 15) === 15 && (pos.y & 15) === 0) this.setSectionDirty(pos.offset(0, -16, 16), true, true)
|
|
890
|
+
|
|
891
|
+
// x & z neighbors
|
|
892
|
+
if ((pos.y & 15) === 0 && (pos.x & 15) === 0 && (pos.z & 15) === 0) this.setSectionDirty(pos.offset(-16, -16, -16), true, true)
|
|
893
|
+
if ((pos.y & 15) === 0 && (pos.x & 15) === 15 && (pos.z & 15) === 0) this.setSectionDirty(pos.offset(16, -16, -16), true, true)
|
|
894
|
+
if ((pos.y & 15) === 0 && (pos.x & 15) === 0 && (pos.z & 15) === 15) this.setSectionDirty(pos.offset(-16, -16, 16), true, true)
|
|
895
|
+
if ((pos.y & 15) === 0 && (pos.x & 15) === 15 && (pos.z & 15) === 15) this.setSectionDirty(pos.offset(16, -16, 16), true, true)
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
abstract worldStop?()
|
|
901
|
+
|
|
902
|
+
queueAwaited = false
|
|
903
|
+
toWorkerMessagesQueue = {} as { [workerIndex: string]: any[] }
|
|
904
|
+
|
|
905
|
+
getWorkerNumber(pos: Vec3, updateAction = false) {
|
|
906
|
+
if (updateAction) {
|
|
907
|
+
const key = `${Math.floor(pos.x / 16) * 16},${Math.floor(pos.y / 16) * 16},${Math.floor(pos.z / 16) * 16}`
|
|
908
|
+
const cantUseChangeWorker = this.sectionsWaiting.get(key) && !this.finishedSections[key]
|
|
909
|
+
if (!cantUseChangeWorker) return 0
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
const hash = mod(Math.floor(pos.x / 16) + Math.floor(pos.y / 16) + Math.floor(pos.z / 16), this.workers.length - 1)
|
|
913
|
+
return hash + 1
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
async debugGetWorkerCustomBlockModel(pos: Vec3) {
|
|
917
|
+
const data = [] as Array<Promise<string>>
|
|
918
|
+
for (const worker of this.workers) {
|
|
919
|
+
data.push(new Promise((resolve) => {
|
|
920
|
+
worker.addEventListener('message', (e) => {
|
|
921
|
+
if (e.data.type === 'customBlockModel') {
|
|
922
|
+
resolve(e.data.customBlockModel)
|
|
923
|
+
}
|
|
924
|
+
})
|
|
925
|
+
}))
|
|
926
|
+
worker.postMessage({
|
|
927
|
+
type: 'getCustomBlockModel',
|
|
928
|
+
pos
|
|
929
|
+
})
|
|
930
|
+
}
|
|
931
|
+
return Promise.all(data)
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
setSectionDirty(pos: Vec3, value = true, useChangeWorker = false) { // value false is used for unloading chunks
|
|
935
|
+
if (!this.forceCallFromMesherReplayer && this.mesherLogReader) return
|
|
936
|
+
|
|
937
|
+
if (this.viewDistance === -1) throw new Error('viewDistance not set')
|
|
938
|
+
this.reactiveState.world.mesherWork = true
|
|
939
|
+
const distance = this.getDistance(pos)
|
|
940
|
+
// todo shouldnt we check loadedChunks instead?
|
|
941
|
+
if (!this.workers.length || distance[0] > this.viewDistance || distance[1] > this.viewDistance) return
|
|
942
|
+
const key = `${Math.floor(pos.x / 16) * 16},${Math.floor(pos.y / 16) * 16},${Math.floor(pos.z / 16) * 16}`
|
|
943
|
+
// if (this.sectionsOutstanding.has(key)) return
|
|
944
|
+
this.renderUpdateEmitter.emit('dirty', pos, value)
|
|
945
|
+
// Dispatch sections to workers based on position
|
|
946
|
+
// This guarantees uniformity accross workers and that a given section
|
|
947
|
+
// is always dispatched to the same worker
|
|
948
|
+
const hash = this.getWorkerNumber(pos, useChangeWorker && this.mesherLogger.active)
|
|
949
|
+
this.sectionsWaiting.set(key, (this.sectionsWaiting.get(key) ?? 0) + 1)
|
|
950
|
+
if (this.forceCallFromMesherReplayer) {
|
|
951
|
+
this.workers[hash].postMessage({
|
|
952
|
+
type: 'dirty',
|
|
953
|
+
x: pos.x,
|
|
954
|
+
y: pos.y,
|
|
955
|
+
z: pos.z,
|
|
956
|
+
value,
|
|
957
|
+
config: this.getMesherConfig(),
|
|
958
|
+
})
|
|
959
|
+
} else {
|
|
960
|
+
this.toWorkerMessagesQueue[hash] ??= []
|
|
961
|
+
this.toWorkerMessagesQueue[hash].push({
|
|
962
|
+
// this.workers[hash].postMessage({
|
|
963
|
+
type: 'dirty',
|
|
964
|
+
x: pos.x,
|
|
965
|
+
y: pos.y,
|
|
966
|
+
z: pos.z,
|
|
967
|
+
value,
|
|
968
|
+
config: this.getMesherConfig(),
|
|
969
|
+
})
|
|
970
|
+
this.dispatchMessages()
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
dispatchMessages() {
|
|
975
|
+
if (this.queueAwaited) return
|
|
976
|
+
this.queueAwaited = true
|
|
977
|
+
setTimeout(() => {
|
|
978
|
+
// group messages and send as one
|
|
979
|
+
for (const workerIndex in this.toWorkerMessagesQueue) {
|
|
980
|
+
const worker = this.workers[Number(workerIndex)]
|
|
981
|
+
worker.postMessage(this.toWorkerMessagesQueue[workerIndex])
|
|
982
|
+
for (const message of this.toWorkerMessagesQueue[workerIndex]) {
|
|
983
|
+
this.logWorkerWork(`-> ${workerIndex} dispatchMessages ${message.type} ${JSON.stringify({ x: message.x, y: message.y, z: message.z, value: message.value })}`)
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
this.toWorkerMessagesQueue = {}
|
|
987
|
+
this.queueAwaited = false
|
|
988
|
+
})
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// Listen for chunk rendering updates emitted if a worker finished a render and resolve if the number
|
|
992
|
+
// of sections not rendered are 0
|
|
993
|
+
async waitForChunksToRender() {
|
|
994
|
+
return new Promise<void>((resolve, reject) => {
|
|
995
|
+
if ([...this.sectionsWaiting].length === 0) {
|
|
996
|
+
resolve()
|
|
997
|
+
return
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
const updateHandler = () => {
|
|
1001
|
+
if (this.sectionsWaiting.size === 0) {
|
|
1002
|
+
this.renderUpdateEmitter.removeListener('update', updateHandler)
|
|
1003
|
+
resolve()
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
this.renderUpdateEmitter.on('update', updateHandler)
|
|
1007
|
+
})
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
async waitForChunkToLoad(pos: Vec3) {
|
|
1011
|
+
return new Promise<void>((resolve, reject) => {
|
|
1012
|
+
const key = `${Math.floor(pos.x / 16) * 16},${Math.floor(pos.z / 16) * 16}`
|
|
1013
|
+
if (this.loadedChunks[key]) {
|
|
1014
|
+
resolve()
|
|
1015
|
+
return
|
|
1016
|
+
}
|
|
1017
|
+
const updateHandler = () => {
|
|
1018
|
+
if (this.loadedChunks[key]) {
|
|
1019
|
+
this.renderUpdateEmitter.removeListener('update', updateHandler)
|
|
1020
|
+
resolve()
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
this.renderUpdateEmitter.on('update', updateHandler)
|
|
1024
|
+
})
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
destroy() {
|
|
1028
|
+
// Stop all workers
|
|
1029
|
+
for (const worker of this.workers) {
|
|
1030
|
+
worker.terminate()
|
|
1031
|
+
}
|
|
1032
|
+
this.workers = []
|
|
1033
|
+
|
|
1034
|
+
// Stop and destroy sound system
|
|
1035
|
+
if (this.soundSystem) {
|
|
1036
|
+
this.soundSystem.destroy()
|
|
1037
|
+
this.soundSystem = undefined
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
this.active = false
|
|
1041
|
+
|
|
1042
|
+
// Clear chunk and section tracking (previously handled by @worldCleanup decorator)
|
|
1043
|
+
this.loadedChunks = {}
|
|
1044
|
+
this.finishedChunks = {}
|
|
1045
|
+
this.finishedSections = {}
|
|
1046
|
+
this.sectionsWaiting.clear()
|
|
1047
|
+
this.queuedChunks.clear()
|
|
1048
|
+
this.blockStateModelInfo.clear()
|
|
1049
|
+
|
|
1050
|
+
this.renderUpdateEmitter.removeAllListeners()
|
|
1051
|
+
this.abortController.abort()
|
|
1052
|
+
removeAllStats()
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
export const initMesherWorker = (onGotMessage: (data: any) => void) => {
|
|
1057
|
+
// Node environment needs an absolute path, but browser needs the url of the file
|
|
1058
|
+
const workerName = 'mesher.js'
|
|
1059
|
+
|
|
1060
|
+
let worker: any
|
|
1061
|
+
if (process.env.SINGLE_FILE_BUILD) {
|
|
1062
|
+
const workerCode = document.getElementById('mesher-worker-code')!.textContent!
|
|
1063
|
+
const blob = new Blob([workerCode], { type: 'text/javascript' })
|
|
1064
|
+
worker = new Worker(window.URL.createObjectURL(blob))
|
|
1065
|
+
} else {
|
|
1066
|
+
worker = new Worker(workerName)
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
worker.onmessage = ({ data }) => {
|
|
1070
|
+
onGotMessage(data)
|
|
1071
|
+
}
|
|
1072
|
+
if (worker.on) worker.on('message', (data) => { worker.onmessage({ data }) })
|
|
1073
|
+
return worker
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
export const meshersSendMcData = (workers: Worker[], version: string, mcDataKeys: string[] = dynamicMcDataFiles) => {
|
|
1077
|
+
const allMcData = mcDataRaw.pc[version] ?? mcDataRaw.pc[toMajorVersion(version)]
|
|
1078
|
+
const mcData = {
|
|
1079
|
+
version: JSON.parse(JSON.stringify(allMcData.version))
|
|
1080
|
+
}
|
|
1081
|
+
for (const key of mcDataKeys) {
|
|
1082
|
+
mcData[key] = allMcData[key]
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
for (const worker of workers) {
|
|
1086
|
+
worker.postMessage({ type: 'mcData', mcData })
|
|
1087
|
+
}
|
|
1088
|
+
}
|