-
Notifications
You must be signed in to change notification settings - Fork 139
New FAST renderer on pure WebGPU try: webgpu.mcraft.fun #120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Conversation
Review or Edit in CodeSandboxOpen the branch in Web Editor • VS Code • Insiders |
|
/deploy |
|
Deployed to Vercel Preview: https://prismarine-pho51nlw6-zaro.vercel.app |
|
/deploy |
|
Deployed to Vercel Preview: https://prismarine-6lrmuc5cl-zaro.vercel.app |
|
/deploy |
|
Deployed to Vercel Preview: https://prismarine-bc2ncs3uy-zaro.vercel.app |
|
/deploy |
|
Deployed to Vercel Preview: https://prismarine-1x8yri3lq-zaro.vercel.app |
|
/deploy |
|
Deployed to Vercel Preview: https://prismarine-gpwdotih8-zaro.vercel.app |
|
/deploy |
|
Deployed to Vercel Preview: https://prismarine-90t7s5wjn-zaro.vercel.app |
|
/deploy |
|
Deployed to Vercel Preview: https://prismarine-700h6sw1a-zaro.vercel.app |
|
/deploy |
|
Deployed to Vercel Preview: https://prismarine-rfw8k4jpj-zaro.vercel.app |
|
/deploy |
|
Deployed to Vercel Preview: https://prismarine-n3cdd3dup-zaro.vercel.app |
|
/deploy |
|
Deployed to Vercel Preview: https://prismarine-h8wzgbvk8-zaro.vercel.app |
|
/deploy |
|
Deployed to Vercel Preview: https://prismarine-arqd0oaby-zaro.vercel.app |
|
/deploy |
|
Deployed to Vercel Preview: https://prismarine-qyfyijkk9-zaro.vercel.app |
|
/deploy |
|
Deployed to Vercel Preview: https://prismarine-5aftdhvlw-zaro.vercel.app |
|
/deploy |
|
Deployed to Vercel Preview: https://prismarine-b28fg7kj6-zaro.vercel.app |
|
Deployed to Vercel Preview: https://prismarine-5o52rrley-zaro.vercel.app |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🔭 Outside diff range comments (1)
renderer/viewer/three/world/cursorBlock.ts (1)
93-105:⚠️ Potential issueMethod signature change may cause unintended mutation
The
updateBreakAnimationmethod has been refactored to work with separated position and shape data instead of a complete Block object. While this decoupling is good for the new renderer architecture, there's a potential issue with line 99.The line
position.add(blockPosition)mutates the originalpositionobject frommergedShape. IfmergedShape.positionis used elsewhere after this call, it will contain an incorrect position (with blockPosition already added). Consider using a clone of the position to avoid this side effect:- position.add(blockPosition) - this.blockBreakMesh.position.set(position.x, position.y, position.z) + const worldPosition = position.clone().add(blockPosition) + this.blockBreakMesh.position.set(worldPosition.x, worldPosition.y, worldPosition.z)
🧹 Nitpick comments (5)
renderer/viewer/lib/basePlayerState.ts (3)
17-18: Consider defining a stronger type for block shape fields.
Usinganyforpositionand other fields could lead to runtime surprises. A dedicated interface or a more specific type would improve clarity and maintainability.
45-51: Consolidate coordinate properties into a dedicated type or interface.
Reusing(x, y, z)across multiple structures may lead to duplication. A sharedVec3Liketype can improve consistency.
52-59: Apply the same coordinate-type approach for 'diggingBlock'.
Consider a shared interface or type to avoid repeating coordinate definitions.renderer/viewer/three/worldrendererThree.ts (1)
122-139: Introduce descriptive constants for fog distance values.
Replacing hard-coded values (e.g., 100 vs 20) with well-named constants could improve clarity. Overall, the reactive updates for player state are implemented cleanly.renderer/viewer/three/world/cursorBlock.ts (1)
95-95: Parameter validation inconsistencyThe
mergedShapeparameter is marked as optional in the method signature, but the early return condition treats it as required. This could lead to unexpected behavior if callers assume they can omit it. Consider either:
- Making the parameter required in the signature, or
- Handling the undefined case differently if it's a valid scenario
- updateBreakAnimation (blockPosition: { x: number, y: number, z: number } | undefined, stage: number | null, mergedShape?: BlockShape) { + updateBreakAnimation (blockPosition: { x: number, y: number, z: number } | undefined, stage: number | null, mergedShape: BlockShape) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
renderer/viewer/lib/basePlayerState.ts(3 hunks)renderer/viewer/lib/mesher/mesher.ts(4 hunks)renderer/viewer/lib/mesher/models.ts(10 hunks)renderer/viewer/three/graphicsBackend.ts(1 hunks)renderer/viewer/three/world/cursorBlock.ts(3 hunks)renderer/viewer/three/worldrendererThree.ts(1 hunks)renderer/viewer/webgpu/worldrendererWebgpu.ts(1 hunks)src/controls.ts(3 hunks)src/mineflayer/plugins/mouse.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- renderer/viewer/lib/mesher/mesher.ts
- renderer/viewer/three/graphicsBackend.ts
- renderer/viewer/webgpu/worldrendererWebgpu.ts
- renderer/viewer/lib/mesher/models.ts
🧰 Additional context used
🧬 Code Definitions (1)
src/mineflayer/plugins/mouse.ts (1)
src/mineflayer/playerState.ts (1)
playerState(206-206)
🪛 Biome (1.9.4)
src/controls.ts
[error] 950-950: This is an unexpected use of the debugger statement.
Unsafe fix: Remove debugger statement
(lint/suspicious/noDebugger)
🔇 Additional comments (11)
src/controls.ts (3)
420-422: Improved state check for URL locking.Good enhancement by adding the
fsState.inMemorySavecondition check. This prevents potential errors when attempting to accessinMemorySavePathwhen there is no in-memory save available.
948-951: Avoid leavingdebuggerstatements in production code.According to static analysis, the
debuggerstatement is flagged. Removing or wrapping it behind a dev build check can prevent accidental triggers in production.if (e.code === 'KeyK' && e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey) { - // eslint-disable-next-line no-debugger - debugger + // TODO: Use console.log or dev-tools breakpoints in development only }🧰 Tools
🪛 Biome (1.9.4)
[error] 950-950: This is an unexpected use of the debugger statement.
Unsafe fix: Remove debugger statement
(lint/suspicious/noDebugger)
952-954:Details
❓ Verification inconclusive
New keyboard shortcut to toggle renderer engines.
You've implemented a useful Alt+J keyboard shortcut to toggle between the WebGPU and ThreeJS renderers, which aligns with the main objective of this PR to add WebGPU as a rendering option.
🏁 Script executed:
#!/bin/bash # Check for the renderer options definition in optionsGuiScheme.tsx echo "Checking for renderer options definition..." rg -A 10 "activeRenderer" --include="*.tsx"Length of output: 582
Renderer Toggle Shortcut Functionality – Please Verify Active Renderer Definition
The Alt+J shortcut implementation in
src/controls.ts(lines 952–954) cleanly toggles between the WebGPU and ThreeJS renderers as intended. The logic correctly checks that only Alt (without Shift, Ctrl, or Meta) is pressed and then swaps theactiveRenderervalue.Before finalizing this change, please manually verify that the
activeRendererproperty is properly defined and used in the corresponding UI/options file (for example, inoptionsGuiScheme.tsxor the relevant component), as our initial search usingrgencountered flag issues.
- File:
src/controls.ts(lines 952–954) – toggling logic is correctly implemented.- Action Needed: Confirm that the
activeRendererproperty exists and is managed consistently across the codebase.renderer/viewer/lib/basePlayerState.ts (3)
5-5: Use of 'ref' from 'valtio' is clear and valid.
No issues noted.
40-40: Addition of 'waterBreathing' property looks consistent.
This property aligns well with existing booleans likeinWaterand improves readability.
67-68: Reactive array reference for 'backgroundColor' is appropriate.
Good usage ofrefto optimize reactivity.src/mineflayer/plugins/mouse.ts (3)
14-14: Clearing 'lookingAtBlock' on missing block data is straightforward.
This prevents stale or incorrect references.
19-26: Reactive assignment of 'lookingAtBlock' data.
Storing block details inplayerStateis a clean and maintainable approach.
32-39: Managing 'diggingBlock' reactively is well-structured.
Conditionally clearing data when the break stage is absent ensures correct state transitions.renderer/viewer/three/world/cursorBlock.ts (2)
6-6: Import updated to use new block shape typesThe import statement has been updated to use the new
BlockShapeandBlocksShapestypes from the basePlayerState module. This change aligns with the refactoring of the block rendering system to support the new WebGPU renderer.
121-121: Type signature improved for block shape handlingThe parameter type for
shapePositionshas been updated to use the more specificBlocksShapestype, which improves type safety and makes the expected data structure clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
renderer/viewer/lib/worldrendererCommon.ts (1)
163-167:⚠️ Potential issuePotential memory leak from interval.
A
setIntervalis created but no reference is stored to clear it later. If multiple renderer instances are created or the renderer is destroyed and recreated, this may lead to memory leaks.Consider storing the interval ID as a class property and clearing it in the
destroy()method:+private geometryStatsInterval?: NodeJS.Timeout constructor(public readonly resourcesManager: ResourcesManager, public displayOptions: DisplayWorldOptions, public version: string) { // ... - setInterval(() => { + this.geometryStatsInterval = setInterval(() => { this.geometryReceiveCountPerSec = Object.values(this.geometryReceiveCount).reduce((acc, curr) => acc + curr, 0) this.geometryReceiveCount = {} this.updateChunksStats() }, 500) } destroy() { // ... + if (this.geometryStatsInterval) clearInterval(this.geometryStatsInterval) this.active = false // ... }
🧹 Nitpick comments (1)
src/loadSave.ts (1)
19-20: Well-structured fsState initialization with global exposureThe new initialization approach using
structuredClone(initialFsState)is a good practice as it:
- Creates a deep copy of the initial state to prevent reference issues
- Centralizes the state definition in one place
- Makes the state structure more predictable and maintainable
Making the state available globally with
globalThis.fsState = fsStateimproves accessibility across modules but consider documenting this global access point for other developers.Consider adding a comment explaining the purpose of making fsState global:
export const fsState = proxy(structuredClone(initialFsState)) +// Make fsState globally accessible for debugging and cross-module access globalThis.fsState = fsState
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (9)
renderer/viewer/lib/mesher/mesher.ts(4 hunks)renderer/viewer/lib/worldrendererCommon.ts(4 hunks)renderer/viewer/webgpu/worldrendererWebgpu.ts(1 hunks)src/controls.ts(2 hunks)src/loadSave.ts(3 hunks)src/optionsGuiScheme.tsx(2 hunks)src/optionsStorage.ts(3 hunks)src/utils.ts(1 hunks)src/watchOptions.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- src/watchOptions.ts
- src/optionsGuiScheme.tsx
- src/optionsStorage.ts
- src/utils.ts
- renderer/viewer/webgpu/worldrendererWebgpu.ts
- renderer/viewer/lib/mesher/mesher.ts
🧰 Additional context used
🧬 Code Definitions (2)
renderer/viewer/lib/worldrendererCommon.ts (1)
renderer/viewer/lib/ui/newStats.ts (2)
addNewStat(7-36)updateStatText(82-85)
src/loadSave.ts (1)
src/integratedServer/browserfsShared.ts (1)
initialFsState(31-43)
🔇 Additional comments (9)
src/controls.ts (2)
950-950: Avoid leavingdebuggerstatements in production code.According to static analysis, the
debuggerstatement is flagged. Removing or wrapping it behind a dev build check can prevent accidental triggers in production.if (e.code === 'KeyK' && e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey) { - debugger + // TODO: Use console.log or dev-tools breakpoints in development only }🧰 Tools
🪛 Biome (1.9.4)
[error] 950-950: This is an unexpected use of the debugger statement.
Unsafe fix: Remove debugger statement
(lint/suspicious/noDebugger)
952-954: Good implementation of renderer toggle featureThe new Alt+J keyboard shortcut for toggling between WebGPU and ThreeJS renderers is a useful addition for testing and comparing the renderers. This aligns well with the PR objective of implementing a new WebGPU renderer.
src/loadSave.ts (3)
13-13: Good refactoring of imports from centralized moduleThe import statement has been updated to bring in utility functions and state from the
browserfsSharedmodule, which helps centralize file system related functionality. This is a good practice for maintainability.
107-107: Consistent use of encapsulated state propertiesUsing
fsState.forceCachedDataPathsinstead of a standalone variable improves code organization by properly encapsulating related state within the fsState object.
146-146: Consistent state property access patternUsing
fsState.forceRedirectPathsfollows the same pattern of accessing state properties through the fsState object, maintaining consistency throughout the codebase.renderer/viewer/lib/worldrendererCommon.ts (4)
159-160: Good addition of chunk download statistics UI element.Adding a dedicated stat for downloaded chunks improves visibility of the rendering process, which aligns well with the WebGPU rendering implementation focus of this PR.
167-167: Increased stats refresh rate from 1000ms to 500ms.Doubling the update frequency for geometry reception statistics will provide more responsive feedback during rendering. This is a good change for the WebGPU renderer where performance monitoring is critical.
528-528: Enhanced statistics display with queue information.The addition of message queue length information (
Q: ${this.messageQueue.length}) provides valuable insight into the rendering pipeline's current workload, which is helpful for debugging and performance monitoring.
579-582: Improved condition for resetting chunk loading metrics.The revised condition for resetting metrics when there are no finished chunks is more precise than the previous implementation that checked if all chunks were not finished.
|
|
||
| // viewer.world.chunksReset() // todo | ||
|
|
||
| // TODO! | ||
| // if (localServer) { | ||
| // //@ts-expect-error not sure why it is private... maybe revisit api? | ||
| // localServer.players[0].world.columns = {} | ||
| // } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider implementing the TODO for viewer.world.chunksReset()
The previous code that cleared localServer.players[0].world.columns has been commented out, with a TODO note for implementing viewer.world.chunksReset(). This change appears to be part of the refactoring for the new WebGPU renderer. Make sure to implement this method to properly handle chunk resets in the new rendering system.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (9)
renderer/viewer/webgpu/worldrendererWebgpu.ts (8)
157-159: Fix missing setTimeout delay parameter.The setTimeout call is missing a delay parameter, which may cause inconsistent behavior across browsers.
await new Promise(resolve => { - setTimeout(resolve,) + setTimeout(resolve, 0) })
180-180: Remove or explain commented code.There's commented-out code that might indicate an unresolved issue or incomplete implementation.
Either remove the commented code or add an explanatory comment about why it's commented out and if it needs to be addressed in the future.
- if (/* !this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || */ !this.active) return + if (!this.active) return
230-232: Implement the updateShowChunksBorder method.The method contains only a TODO comment without any implementation, which means this functionality is not working.
updateShowChunksBorder (value: boolean) { - // todo + this.webgpuChannel.updateShowChunksBorder(value) }Would you like me to help implement this method or create an issue to track this task?
429-435: Optimize mainLoop in debug UI to use requestAnimationFrame's callback parameter.The current implementation creates a new variable to track updates, but requestAnimationFrame already provides a timestamp parameter that could be used to calculate FPS more accurately.
- let updates = 0 + let lastTime = performance.now(); + let frames = 0; const mainLoop = () => { - requestAnimationFrame(mainLoop) - updates++ + const currentTime = performance.now(); + frames++; + + // Update FPS counter once per second + if (currentTime - lastTime >= 1000) { + updateText2(`Main Loop: ${frames}`); + frames = 0; + lastTime = currentTime; + } + + requestAnimationFrame(mainLoop); } mainLoop() - setInterval(() => { - updateText2(`Main Loop: ${updates}`) - updates = 0 - }, 1000)
36-36: Add type safety to webgpuChannel placeholder.The current implementation uses
anytype which reduces type safety. Consider using a more specific type to ensure type checking during development.- webgpuChannel: typeof workerProxyType['__workerProxy'] = this.getPlaceholderChannel() + webgpuChannel: typeof workerProxyType['__workerProxy'] = this.getPlaceholderChannel<typeof workerProxyType['__workerProxy']>()Then update the
getPlaceholderChannelmethod:- getPlaceholderChannel () { + getPlaceholderChannel<T extends Record<string, (...args: any[]) => any>> (): T { return new Proxy({}, { get: (target, p) => (...args) => { void this.readyWorkerPromise.then(() => { - this.webgpuChannel[p](...args) + (this.webgpuChannel as any)[p as string](...args) }) } - }) as any // placeholder to avoid crashes + }) as T // placeholder to avoid crashes }
80-88: Improve type safety in getPlaceholderChannel method.The method uses
anytype which reduces type safety. Using generics can provide better type checking while maintaining the flexibility needed for this placeholder implementation.- getPlaceholderChannel () { - return new Proxy({}, { - get: (target, p) => (...args) => { + getPlaceholderChannel<T extends Record<string, (...args: any[]) => any>>(): T { + return new Proxy<Record<string, any>>({}, { + get: (target, p: string | symbol) => (...args: any[]) => { void this.readyWorkerPromise.then(() => { - this.webgpuChannel[p](...args) + (this.webgpuChannel as any)[p](...args) }) } - }) as any // placeholder to avoid crashes + }) as T // placeholder to avoid crashes }
101-113: Improve type safety in sendCameraToWorker method.The method uses
anytype which reduces type safety. Using proper type annotations would improve code quality and prevent potential type-related bugs.+ interface CameraVectors { + rotation: { x: number; y: number; z: number }; + position: { x: number; y: number; z: number }; + } sendCameraToWorker () { - const cameraVectors = ['rotation', 'position'].reduce((acc, key) => { - acc[key] = ['x', 'y', 'z'].reduce((acc2, key2) => { - acc2[key2] = this.camera[key][key2] + const cameraVectors = ['rotation', 'position'].reduce<Partial<CameraVectors>>((acc, key) => { + acc[key as keyof CameraVectors] = ['x', 'y', 'z'].reduce<{ x?: number; y?: number; z?: number }>((acc2, key2) => { + acc2[key2 as 'x' | 'y' | 'z'] = this.camera[key as 'rotation' | 'position'][key2 as 'x' | 'y' | 'z'] return acc2 - }, {}) + }, {}) as { x: number; y: number; z: number } return acc - }, {}) as any + }, {}) as CameraVectors this.webgpuChannel.camera({ ...cameraVectors, fov: this.displayOptions.inWorldRenderingConfig.fov }) }
117-121: Optimize event listener in addWebgpuListener method.The current implementation adds a new listener for every type, which is inefficient if multiple listeners for different types are added. Instead, a single listener could be used with type filtering.
export class WorldRendererWebgpu extends WorldRendererCommon { + private typeListeners: Map<string, Array<(data: any) => void>> = new Map(); + private messageHandler: ((e: MessageEvent) => void) | null = null; // ... addWebgpuListener (type: string, listener: (data: any) => void) { void this.readyWorkerPromise.then(() => { - this.worker!.addEventListener('message', (e: any) => { - if (e.data.type === type) { - listener(e.data) - } - }) + if (!this.typeListeners.has(type)) { + this.typeListeners.set(type, []); + } + this.typeListeners.get(type)!.push(listener); + + // Add the global handler only once + if (!this.messageHandler) { + this.messageHandler = (e: MessageEvent) => { + const messageType = e.data?.type; + if (messageType && this.typeListeners.has(messageType)) { + for (const typeListener of this.typeListeners.get(messageType)!) { + typeListener(e.data); + } + } + }; + this.worker!.addEventListener('message', this.messageHandler); + } }) } // Don't forget to clean up in destroy method override destroy () { // ... + if (this.worker && this.messageHandler) { + this.worker.removeEventListener('message', this.messageHandler); + this.messageHandler = null; + this.typeListeners.clear(); + } // ... } }src/index.ts (1)
347-367: Large block of commented-out code should be removedThis extensive block of commented-out code suggests incomplete refactoring. If this functionality has been replaced, the commented code should be removed to improve readability.
If the functionality has been completely replaced by the new implementation, consider removing this commented-out code. If not, add a comment explaining why it's being preserved.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
renderer/viewer/webgpu/graphicsBackendWebgpu.ts(1 hunks)renderer/viewer/webgpu/worldrendererWebgpu.ts(1 hunks)src/appViewer.ts(4 hunks)src/appViewerLoad.ts(3 hunks)src/index.ts(10 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- renderer/viewer/webgpu/graphicsBackendWebgpu.ts
🧰 Additional context used
🧬 Code Definitions (3)
src/index.ts (4)
src/integratedServer/main.ts (2)
destroyLocalServerMain(115-137)startLocalServerMain(55-97)src/globalState.ts (1)
miscUiState(109-129)src/optionsStorage.ts (1)
options(218-223)src/appViewer.ts (1)
appViewer(256-256)
renderer/viewer/webgpu/worldrendererWebgpu.ts (5)
renderer/playground/webgpuRendererShared.ts (2)
defaultWebgpuRendererParams(4-13)rendererParamsGui(15-25)renderer/playground/webgpuRendererWorker.ts (2)
workerProxyType(95-281)canvas(97-113)renderer/viewer/webgpu/graphicsBackendWebgpu.ts (1)
WebgpuInitOptions(5-7)src/appViewer.ts (1)
DisplayWorldOptions(61-68)renderer/playground/webgpuBlockModels.ts (1)
prepareCreateWebgpuBlocksModelsData(9-157)
src/appViewerLoad.ts (2)
src/appViewer.ts (2)
appViewer(256-256)loadBackend(117-156)src/optionsStorage.ts (1)
options(218-223)
🔇 Additional comments (15)
renderer/viewer/webgpu/worldrendererWebgpu.ts (5)
58-63: Add MessagePort cleanup in destroy method.The destroy method terminates the worker if it's a Worker instance but doesn't close the port if it's a MessagePort, which could lead to resource leaks.
Apply this diff to fix the issue:
if (this.worker instanceof Worker) { this.worker.terminate() +} else if (this.worker instanceof MessagePort) { + this.worker.close() }
273-274: Add error handling for fetch operation.The fetch operation could fail for various reasons (network issues, invalid URL, etc.), but there's no error handling.
- const imageBlob = await fetch(image).then(async (res) => res.blob()) + let imageBlob: Blob; + try { + const response = await fetch(image); + if (!response.ok) { + throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`); + } + imageBlob = await response.blob(); + } catch (error) { + console.error('Failed to fetch texture image:', error); + this.issueReporter.reportProblem(false, `Failed to load textures: ${error.message}`); + throw error; + }
402-404: Implement UI cleanup in addWebgpuDebugUi.The TODO comment indicates that cleanup for the debug UI is not implemented, which could lead to memory leaks.
The debug UI should be properly cleaned up when no longer needed. Consider implementing a cleanup function that:
- Removes event listeners
- Clears timers
- Removes DOM elements
This function should be called when the renderer is destroyed.
407-419: Add cleanup for event listener in addWebgpuDebugUi.The event listener is added but never removed, which could lead to memory leaks if the component is destroyed and recreated.
+ const messageListener = (e: any) => { + if (e.data.type === 'fps') { + updateText(`FPS: ${e.data.fps}`) + if (prevTimeout) clearTimeout(prevTimeout) + prevTimeout = setTimeout(() => { + updateText('<hanging>') + }, 1002) + } + if (e.data.type === 'stats') { + updateTextGpuStats(e.data.stats) + // worldRenderer.rendererDevice = `${e.data.device} WebGL data: ${WorldRendererThree.getRendererInfo(new THREE.WebGLRenderer())}` + } + } + worker.addEventListener('message', messageListener) + + // Clean up on abort + worldRenderer.abortController.signal.addEventListener('abort', () => { + worker.removeEventListener('message', messageListener) + if (prevTimeout) clearTimeout(prevTimeout) + }) - worker.addEventListener('message', (e: any) => { - if (e.data.type === 'fps') { - updateText(`FPS: ${e.data.fps}`) - if (prevTimeout) clearTimeout(prevTimeout) - prevTimeout = setTimeout(() => { - updateText('<hanging>') - }, 1002) - } - if (e.data.type === 'stats') { - updateTextGpuStats(e.data.stats) - // worldRenderer.rendererDevice = `${e.data.device} WebGL data: ${WorldRendererThree.getRendererInfo(new THREE.WebGLRenderer())}` - } - })
454-458: Fix URL search parameter update.The code sets the search parameter to the old value
valueinstead of the new valuenewVal.if (rendererParamsGui[key]?.qsReload) { const searchParams = new URLSearchParams(window.location.search) - searchParams.set(key, String(value)) + searchParams.set(key, String(newVal)) window.location.search = searchParams.toString() }src/appViewerLoad.ts (3)
2-2: Appropriate import for new WebGPU rendererThe import of the WebGPU backend matches the PR objective to implement a new rendering engine using WebGPU.
11-11: Correctly enables WebGPU backend loadingPreviously commented code is now active, properly loading the WebGPU renderer when selected in options.
37-45:Details
❓ Verification inconclusive
Potential undefined variable in renderer change handler
The subscription handler references a global
botvariable that isn't defined or imported in this file. This could lead to runtime errors if the variable isn't available when the handler executes.
🏁 Script executed:
#!/bin/bash # Check if 'bot' is defined as a global variable elsewhere rg -A 2 -B 2 "window.bot\s*=|global.bot\s*=" --glob="*.{js,ts,jsx,tsx}"Length of output: 794
Attention: Global 'bot' Initialization Clarification Needed
The subscription handler in
src/appViewerLoad.tsreferencesbotwithout a local declaration. Our search shows thatbotis defined as a global variable in bothsrc/index.tsandsrc/globals.js(via assignments towindow.bot). Although this confirms that a globalbotis present, relying on an implicit global can be fragile if the initialization order isn’t strictly guaranteed.To strengthen the code:
- Explicit Global Reference: Consider referencing
window.botinstead ofbotdirectly to make the dependency clearer.- Documentation/Declaration: Add a global declaration or an inline comment that documents the source and expected initialization order of
bot.- Initialization Order Review: Verify that the code initializing
botalways runs before the subscription handler insrc/appViewerLoad.tsfires.src/index.ts (4)
94-94: Appropriate imports for server managementThe imports correctly include the new server management functions that replace the previous implementation.
200-200: Improved server termination with proper cleanupUsing
destroyLocalServerMain(false)is more robust than simply setting variables to undefined as it properly terminates the worker and cleans up resources.
346-346: Improved server initialization with better encapsulationObtaining the CustomClient from
startLocalServerMainpromotes better separation of concerns compared to the previous approach.
706-706: Improved encapsulation with new startWithBot methodUsing the new
appViewer.startWithBot()method encapsulates the renderer initialization logic, which is a good practice for maintainability.src/appViewer.ts (3)
194-194: Improved code organization in resetBackendThe change simplifies the method by delegating the cleanup logic to
disconnectBackend, which is good for code maintainability.
221-226: Good consolidation of cleanup logicUpdating the
disconnectBackendmethod to handle the state cleanup based on thecleanStateparameter consolidates related functionality in one place, which is a good practice.
235-235: Simplified renderer state initializationThe direct assignment using
proxy(getDefaultRendererState())is cleaner than the previous approach and ensures a fresh renderer state.
| const mainLoop = () => { | ||
| requestAnimationFrame(mainLoop) | ||
| updates++ | ||
| } | ||
| mainLoop() | ||
| setInterval(() => { | ||
| updateText2(`Main Loop: ${updates}`) | ||
| updates = 0 | ||
| }, 1000) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Clean up interval timer in addWebgpuDebugUi.
The setInterval is created but never cleared, which could lead to memory leaks if the component is destroyed and recreated.
- setInterval(() => {
+ const statsInterval = setInterval(() => {
updateText2(`Main Loop: ${updates}`)
updates = 0
}, 1000)
+
+ // Clean up on abort
+ worldRenderer.abortController.signal.addEventListener('abort', () => {
+ clearInterval(statsInterval);
+ })📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const mainLoop = () => { | |
| requestAnimationFrame(mainLoop) | |
| updates++ | |
| } | |
| mainLoop() | |
| setInterval(() => { | |
| updateText2(`Main Loop: ${updates}`) | |
| updates = 0 | |
| }, 1000) | |
| const mainLoop = () => { | |
| requestAnimationFrame(mainLoop) | |
| updates++ | |
| } | |
| mainLoop() | |
| const statsInterval = setInterval(() => { | |
| updateText2(`Main Loop: ${updates}`) | |
| updates = 0 | |
| }, 1000) | |
| // Clean up on abort | |
| worldRenderer.abortController.signal.addEventListener('abort', () => { | |
| clearInterval(statsInterval); | |
| }) |
| addWebgpuListener (type: string, listener: (data: any) => void) { | ||
| void this.readyWorkerPromise.then(() => { | ||
| this.worker!.addEventListener('message', (e: any) => { | ||
| if (e.data.type === type) { | ||
| listener(e.data) | ||
| } | ||
| }) | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve event listener management to prevent memory leaks.
The addWebgpuListener method adds event listeners without providing a way to remove them when the renderer is destroyed, which could lead to memory leaks if many listeners are added.
Create a storage for the listeners and clean them up in the destroy method:
export class WorldRendererWebgpu extends WorldRendererCommon {
// Add a new property to store listeners
+ private messageListeners: Array<{ type: string, handler: (e: any) => void }> = [];
// Modify the addWebgpuListener method
addWebgpuListener (type: string, listener: (data: any) => void) {
void this.readyWorkerPromise.then(() => {
- this.worker!.addEventListener('message', (e: any) => {
+ const handler = (e: any) => {
if (e.data.type === type) {
listener(e.data)
}
- })
+ };
+ this.worker!.addEventListener('message', handler);
+ this.messageListeners.push({ type, handler });
})
}
// Modify the destroy method to clean up listeners
override destroy () {
// Existing code...
+ // Clean up message listeners
+ if (this.worker) {
+ for (const { handler } of this.messageListeners) {
+ this.worker.removeEventListener('message', handler);
+ }
+ this.messageListeners = [];
+ }
// Rest of destroy method...
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| addWebgpuListener (type: string, listener: (data: any) => void) { | |
| void this.readyWorkerPromise.then(() => { | |
| this.worker!.addEventListener('message', (e: any) => { | |
| if (e.data.type === type) { | |
| listener(e.data) | |
| } | |
| }) | |
| }) | |
| } | |
| export class WorldRendererWebgpu extends WorldRendererCommon { | |
| // Existing properties... | |
| // Add a new property to store listeners | |
| private messageListeners: Array<{ type: string, handler: (e: any) => void }> = []; | |
| // Other class members... | |
| addWebgpuListener(type: string, listener: (data: any) => void) { | |
| void this.readyWorkerPromise.then(() => { | |
| const handler = (e: any) => { | |
| if (e.data.type === type) { | |
| listener(e.data); | |
| } | |
| }; | |
| this.worker!.addEventListener('message', handler); | |
| this.messageListeners.push({ type, handler }); | |
| }); | |
| } | |
| override destroy() { | |
| // Existing cleanup code... | |
| // Clean up message listeners | |
| if (this.worker) { | |
| for (const { handler } of this.messageListeners) { | |
| this.worker.removeEventListener('message', handler); | |
| } | |
| this.messageListeners = []; | |
| } | |
| // Rest of destroy method... | |
| } | |
| // Other class methods... | |
| } |
| const blob = await fetch(this.resourcesManager.currentResources!.blocksAtlasParser.latestImage).then(async (res) => res.blob()) | ||
| this.webgpuChannel.updateTexture(blob) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for texture update fetch operation.
Similar to the issue at line 273, this fetch operation lacks error handling which could lead to unhandled promise rejections.
- const blob = await fetch(this.resourcesManager.currentResources!.blocksAtlasParser.latestImage).then(async (res) => res.blob())
- this.webgpuChannel.updateTexture(blob)
+ try {
+ const response = await fetch(this.resourcesManager.currentResources!.blocksAtlasParser.latestImage);
+ if (!response.ok) {
+ throw new Error(`Failed to fetch texture: ${response.status} ${response.statusText}`);
+ }
+ const blob = await response.blob();
+ this.webgpuChannel.updateTexture(blob);
+ } catch (error) {
+ console.error('Failed to update texture:', error);
+ this.issueReporter.reportProblem(false, `Failed to update textures: ${error.message}`);
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const blob = await fetch(this.resourcesManager.currentResources!.blocksAtlasParser.latestImage).then(async (res) => res.blob()) | |
| this.webgpuChannel.updateTexture(blob) | |
| } | |
| try { | |
| const response = await fetch(this.resourcesManager.currentResources!.blocksAtlasParser.latestImage); | |
| if (!response.ok) { | |
| throw new Error(`Failed to fetch texture: ${response.status} ${response.statusText}`); | |
| } | |
| const blob = await response.blob(); | |
| this.webgpuChannel.updateTexture(blob); | |
| } catch (error) { | |
| console.error('Failed to update texture:', error); | |
| this.issueReporter.reportProblem(false, `Failed to update textures: ${error.message}`); | |
| } | |
| } |
| subscribeKey(options, 'activeRenderer', () => { | ||
| if (appViewer.currentDisplay === 'world' && bot) { | ||
| appViewer.resetBackend(true) | ||
| loadBackend() | ||
| void appViewer.startWithBot() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for asynchronous operation
The code calls appViewer.startWithBot() with void which swallows any errors that might occur during this asynchronous operation.
Consider adding proper error handling:
- void appViewer.startWithBot()
+ try {
+ void appViewer.startWithBot()
+ } catch (error) {
+ console.error('Error restarting with new renderer:', error)
+ }Committable suggestion skipped: line range outside the PR's diff.
| if (singleplayer && !fsState.inMemorySave) { | ||
| possiblyCleanHandle(() => { | ||
| // todo: this is not enough, we need to wait for all async operations to finish | ||
| }) | ||
| // possiblyCleanHandle(() => { | ||
| // // todo: this is not enough, we need to wait for all async operations to finish | ||
| // }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Commented out cleanup code may lead to resource leaks
The previous cleanup logic using possiblyCleanHandle has been commented out without a clear replacement. This could potentially lead to resource leaks.
🏁 Script executed:
#!/bin/bash
# Check what possiblyCleanHandle was doing and if its functionality is replaced
rg -A 10 "possiblyCleanHandle\s*=" --glob="*.{js,ts,jsx,tsx}"Length of output: 788
Critical: Address the removal of cleanup logic in src/index.ts
The previously active cleanup via possiblyCleanHandle is now commented out (lines 217–220 in src/index.ts), and there’s no clear replacement. Since possiblyCleanHandle (defined in src/integratedServer/browserfsShared.ts) handles asynchronous cleanup via browserfs.configure, eliminating its invocation risks leaving async operations unfinished and may create resource leaks.
- Action items:
- Reinstate and adjust cleanup: Re-enable
possiblyCleanHandlewith proper synchronization to ensure all async operations complete. - Or remove safely: If the cleanup logic is deemed unnecessary, remove the commented-out code entirely and update documentation accordingly to reflect the change.
- Reinstate and adjust cleanup: Re-enable
| async startWithBot () { | ||
| const renderDistance = miscUiState.singleplayer ? options.renderDistance : options.multiplayerRenderDistance | ||
| await this.startWorld(bot.world, renderDistance) | ||
| this.worldView!.listenToBot(bot) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
New method lacks error handling and uses undeclared global variable
The new startWithBot method references a global bot variable that isn't imported in this file. Additionally, it lacks error handling for potential issues during world initialization.
Consider adding proper variable declaration and error handling:
async startWithBot () {
+ if (!window.bot) {
+ throw new Error('Bot is not initialized')
+ }
const renderDistance = miscUiState.singleplayer ? options.renderDistance : options.multiplayerRenderDistance
- await this.startWorld(bot.world, renderDistance)
- this.worldView!.listenToBot(bot)
+ try {
+ await this.startWorld(window.bot.world, renderDistance)
+ this.worldView!.listenToBot(window.bot)
+ } catch (error) {
+ console.error('Error starting world with bot:', error)
+ throw error
+ }
}🏁 Script executed:
#!/bin/bash
# Check how bot is defined and accessed elsewhere in the codebase
rg -A 2 -B 2 "window.bot\s*=|global.bot\s*=" --glob="*.{js,ts,jsx,tsx}"Length of output: 794
Attention: Global Variable Reference and Error Handling in startWithBot
After verifying the codebase, it’s clear that the bot instance is defined on the global scope as window.bot (see references in src/index.ts and src/globals.js), so direct usage of bot in this method is incorrect. Additionally, the lack of error handling around the asynchronous call to startWorld may lead to unhandled exceptions if issues occur during initialization.
Please update the startWithBot method to:
- Explicitly reference
window.botinstead of the undeclaredbot. - Add an initialization check (e.g., verify that
window.botexists). - Wrap the asynchronous calls in a try/catch block to capture and log any errors.
Below is a suggested diff:
async startWithBot () {
+ if (!window.bot) {
+ throw new Error('Bot is not initialized.');
+ }
const renderDistance = miscUiState.singleplayer ? options.renderDistance : options.multiplayerRenderDistance
- await this.startWorld(bot.world, renderDistance)
- this.worldView!.listenToBot(bot)
+ try {
+ await this.startWorld(window.bot.world, renderDistance)
+ this.worldView!.listenToBot(window.bot)
+ } catch (error) {
+ console.error('Error starting world with bot:', error)
+ throw error
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async startWithBot () { | |
| const renderDistance = miscUiState.singleplayer ? options.renderDistance : options.multiplayerRenderDistance | |
| await this.startWorld(bot.world, renderDistance) | |
| this.worldView!.listenToBot(bot) | |
| } | |
| async startWithBot () { | |
| if (!window.bot) { | |
| throw new Error('Bot is not initialized.'); | |
| } | |
| const renderDistance = miscUiState.singleplayer ? options.renderDistance : options.multiplayerRenderDistance | |
| try { | |
| await this.startWorld(window.bot.world, renderDistance) | |
| this.worldView!.listenToBot(window.bot) | |
| } catch (error) { | |
| console.error('Error starting world with bot:', error) | |
| throw error | |
| } | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
renderer/viewer/lib/worldrendererCommon.ts (3)
469-485: Unify out-of-range time checks with error throwing logic.
Here, the code gracefully handlestimeOfDay < 0 || timeOfDay > 24000, while line 720 explicitly throws an error for the same condition. Consider consolidating into a single approach (error or fallback) to prevent confusion.
542-542: Minor formatting nitpick in stat text.
String formatting includes both “ms” and “ss” units, which might be slightly unclear. Consider clarifying or unifying the display for better readability.
593-593: Consolidate repeated logic for settingallChunksFinished.
This line reassignsthis.allChunksFinished, whilecheckAllFinished()and other lines (e.g. line 703) also manage it. Centralizing the logic incheckAllFinished()(or a single function) can reduce confusion and maintain consistency.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
renderer/viewer/lib/mesher/mesher.ts(5 hunks)renderer/viewer/lib/worldrendererCommon.ts(7 hunks)renderer/viewer/webgpu/graphicsBackendWebgpu.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- renderer/viewer/lib/mesher/mesher.ts
- renderer/viewer/webgpu/graphicsBackendWebgpu.ts
🧰 Additional context used
🧬 Code Definitions (1)
renderer/viewer/lib/worldrendererCommon.ts (1)
renderer/viewer/lib/ui/newStats.ts (2)
addNewStat(7-36)updateStatText(82-85)
🔇 Additional comments (5)
renderer/viewer/lib/worldrendererCommon.ts (5)
19-19: Adopting new UI stat utilities looks good.
No issues found with the expanded import ofaddNewStat,removeAllStats,removeStat, andupdateStatText. This aligns with the overall stats-tracking approach in the codebase.
56-56: Initialization of day time indicator.
DefiningtimeOfTheDay = 0is a straightforward and clear default.
160-161: Stat creation for downloaded chunks.
UsingaddNewStatto visualize the "downloaded-chunks" metric is consistent with your existing stat-based UI.
164-168: Potential memory leak from setInterval.
AsetIntervalis created but never cleared, which may cause a memory leak if multiple renderer instances are constructed. This issue was previously flagged in an older review. Consider storing the interval ID as a class property and callingclearIntervalindestroy()to avoid leaks.Example fix:
+ private geometryStatsInterval?: number constructor(...) { ... - setInterval(() => { + this.geometryStatsInterval = setInterval(() => { this.geometryReceiveCountPerSec = Object.values(this.geometryReceiveCount).reduce((acc, curr) => acc + curr, 0) this.geometryReceiveCount = {} this.updateChunksStats() }, 500) } destroy() { ... + if (this.geometryStatsInterval) clearInterval(this.geometryStatsInterval) }
725-725: Verify redundant out-of-range checks.
Because line 720 already throws an error iftimeOfDayis out of range, ensure you truly need this additional assignment logic for negative or beyond 24000 values.Would you like to confirm whether any code path sets
timeOfTheDayto an invalid range, or should we remove the no-op block ingetMesherConfig()?
|
@coderabbitai pause |
✅ Actions performedReviews paused. |
|
Deployed to Vercel Preview: https://prismarine-axi4apbgu-zaro.vercel.app |
|
Deployed to Vercel Preview: https://prismarine-p1admyy31-zaro.vercel.app |
|
Is their a download fo the modified minecraft web client of this site: https://prismarine-pho51nlw6-zaro.vercel.app/ |
You can do it yourself by checking out the branch and running build-single-file npm script. I might add it deploy workflow as well so you can just download a single html file |
|
Deployed to Vercel Preview: https://prismarine-7s5ojgyk2-zaro.vercel.app |
|
/deploy |
|
Deployed to Vercel Preview: https://prismarine-mq41ttsbv-zaro.vercel.app |
WORKING PLAYGROUND DEMO - PERF CHUNKS
LATEST MAP DEMO (Future City)
tasks (branch is archived now):
@zardoy
Summary by CodeRabbit
New Features
Controlscomponent for better touch interaction.TextureAnimationclass for managing texture animations.MessageChannelReplacementclass for improved message handling.Enhancements
Bug Fixes
These updates deliver improved graphics, responsiveness, and a modernized user interface for a superior viewing experience.