Skip to content

Conversation

sa2urami
Copy link

@sa2urami sa2urami commented May 15, 2024

WORKING PLAYGROUND DEMO - PERF CHUNKS
LATEST MAP DEMO (Future City)

tasks (branch is archived now):

  • Fog, chunk opacity, cube map stars &meter
  • true cull face (like normal dot product)
  • investigate reusing shaders pipeline
  • > 2gb buffers for 4090
  • static lighting with cache
  • ssao
  • slabs & stairs, custom models like torches
  • god rays vol lighting
  • linearize Z (make clip Z far)
  • pass vert shader - fix lag on old ipads 5 with slow rest
  • display current rendering tiles
  • check transparent no text
  • frustum culling chunks
  • always draw 8 block radius

@zardoy

  • redesign mobile control like ptg
  • fix ui rot bug
  • investigate alt orientation ui
  • optifine zoom button
  • print more info on loading chunks, debug minecraft-like ui
  • always fix conn rel
  • load chunks in spiral, no mesher recompute, all sides on edges
  • Singleplayer world worker
  • mem crash ui
  • mem buff limit

Summary by CodeRabbit

  • New Features

    • Introduced a new WebGPU rendering mode for enhanced visuals and performance.
    • Added improved touch controls for mobile devices.
    • Implemented a new Controls component for better touch interaction.
    • Introduced new scene variants for enriched 3D experiences.
    • Added a new TextureAnimation class for managing texture animations.
    • Introduced a MessageChannelReplacement class for improved message handling.
  • Enhancements

    • Updated the maps provider URL for better integration.
    • Refined camera controls and texture animations for a more immersive experience.
    • Enhanced state management for player attributes and interactions.
    • Improved error handling and state management in various components.
  • Bug Fixes

    • Resolved issues related to block visibility and rendering.
    • Fixed issues with touch event handling and button responsiveness.

These updates deliver improved graphics, responsiveness, and a modernized user interface for a superior viewing experience.

Copy link

codesandbox bot commented May 15, 2024

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@zardoy
Copy link
Owner

zardoy commented May 21, 2024

/deploy

Copy link

Deployed to Vercel Preview: https://prismarine-pho51nlw6-zaro.vercel.app
Playground
Storybook

@zardoy
Copy link
Owner

zardoy commented May 22, 2024

/deploy

Copy link

Deployed to Vercel Preview: https://prismarine-6lrmuc5cl-zaro.vercel.app
Playground
Storybook

@zardoy zardoy changed the title New realisation of renderer on WebGPU New realisation of renderer on WebGPU (webgpu.mcraft.fun) May 24, 2024
@zardoy
Copy link
Owner

zardoy commented May 25, 2024

/deploy

Copy link

Deployed to Vercel Preview: https://prismarine-bc2ncs3uy-zaro.vercel.app
Playground
Storybook

@zardoy
Copy link
Owner

zardoy commented Jun 25, 2024

/deploy

Copy link

Deployed to Vercel Preview: https://prismarine-1x8yri3lq-zaro.vercel.app
Playground
Storybook

@zardoy
Copy link
Owner

zardoy commented Jul 10, 2024

/deploy

Copy link

Deployed to Vercel Preview: https://prismarine-gpwdotih8-zaro.vercel.app
Playground
Storybook

@zardoy
Copy link
Owner

zardoy commented Jul 10, 2024

/deploy

Copy link

Deployed to Vercel Preview: https://prismarine-90t7s5wjn-zaro.vercel.app
Playground
Storybook

@zardoy
Copy link
Owner

zardoy commented Jul 18, 2024

/deploy

Copy link

Deployed to Vercel Preview: https://prismarine-700h6sw1a-zaro.vercel.app
Playground
Storybook

@zardoy
Copy link
Owner

zardoy commented Jul 31, 2024

/deploy

Copy link

Deployed to Vercel Preview: https://prismarine-rfw8k4jpj-zaro.vercel.app
Playground
Storybook

@zardoy
Copy link
Owner

zardoy commented Aug 2, 2024

/deploy

Copy link

github-actions bot commented Aug 2, 2024

Deployed to Vercel Preview: https://prismarine-n3cdd3dup-zaro.vercel.app
Playground
Storybook

@zardoy
Copy link
Owner

zardoy commented Aug 2, 2024

/deploy

Copy link

github-actions bot commented Aug 2, 2024

Deployed to Vercel Preview: https://prismarine-h8wzgbvk8-zaro.vercel.app
Playground
Storybook

@zardoy
Copy link
Owner

zardoy commented Aug 4, 2024

/deploy

Copy link

github-actions bot commented Aug 4, 2024

Deployed to Vercel Preview: https://prismarine-arqd0oaby-zaro.vercel.app
Playground
Storybook

@zardoy
Copy link
Owner

zardoy commented Aug 4, 2024

/deploy

Copy link

github-actions bot commented Aug 4, 2024

Deployed to Vercel Preview: https://prismarine-qyfyijkk9-zaro.vercel.app
Playground
Storybook

@zardoy
Copy link
Owner

zardoy commented Aug 4, 2024

/deploy

Copy link

github-actions bot commented Aug 4, 2024

Deployed to Vercel Preview: https://prismarine-5aftdhvlw-zaro.vercel.app
Playground
Storybook

@zardoy
Copy link
Owner

zardoy commented Aug 5, 2024

/deploy

Copy link

github-actions bot commented Aug 5, 2024

Deployed to Vercel Preview: https://prismarine-b28fg7kj6-zaro.vercel.app
Playground
Storybook

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
src/integratedServer/browserfsShared.ts (5)

18-18: Replace delete operator with undefined assignment for better performance.

The delete operator can impact performance as indicated by the static analysis. Consider using undefined assignment instead.

-  delete defaultMountablePoints['/world']
+  defaultMountablePoints['/world'] = undefined
🧰 Tools
🪛 Biome (1.9.4)

[error] 18-18: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


171-182: Improve error handling in mkdirRecursive function.

The function catches errors but doesn't handle them appropriately. Some errors, like permission issues, should be propagated rather than silently ignored.

export const mkdirRecursive = async (path: string) => {
  const parts = path.split('/')
  let current = ''
  for (const part of parts) {
    current += part + '/'
    try {
      // eslint-disable-next-line no-await-in-loop
      await fs.promises.mkdir(current)
    } catch (err) {
+     // Ignore if directory already exists (EEXIST error)
+     if (err.code !== 'EEXIST') {
+       throw err
+     }
    }
  }
}

322-322: Implement the TODO for handle cleanup.

The comment indicates that handle cleanup needs to be implemented, which is important for resource management.

The TODO comment suggests that handle cleanup functionality is needed. Would you like me to help implement this feature? Having a clear cleanup strategy for handles is important to prevent resource leaks.


372-376: Improve error handling in fileExistsAsyncOptimized.

The function has a commented-out error throw and a misleading ESLint disable comment. The error handling should be clearer and more consistent.

  try {
    await fs.promises.readdir(path)
  } catch (err) {
    if (err.code === 'ENOTDIR') return true
-   // eslint-disable-next-line sonarjs/prefer-single-boolean-return
    if (err.code === 'ENOENT') return false
-   // throw err
-   return false
+   // Handle other potential errors
+   console.warn(`Unexpected error checking file existence: ${err.message || String(err)}`)
+   return false
  }
  return true

399-407: Improve error handling in the file copy operation.

Consider adding more specific error handling for common file system errors like permission issues or disk space limitations.

      // Copy file
      try {
        await fs.promises.writeFile(curPathDest, await fs.promises.readFile(curPathSrc) as any)
        console.debug('copied file', curPathSrc, curPathDest)
      } catch (err) {
-       console.error('Error copying file', curPathSrc, curPathDest, err)
-       throw err
+       console.error(`Error copying file from ${curPathSrc} to ${curPathDest}:`, err)
+       if (err.code === 'ENOSPC') {
+         throw new Error(`Not enough disk space to copy file: ${curPathSrc}`)
+       } else if (err.code === 'EACCES') {
+         throw new Error(`Permission denied while copying file: ${curPathSrc}`)
+       } else {
+         throw err
+       }
      }
      fileCopied?.(curPathDest)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 51b818e and 792a195.

📒 Files selected for processing (1)
  • src/integratedServer/browserfsShared.ts (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/integratedServer/browserfsShared.ts

[error] 18-18: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

🔇 Additional comments (2)
src/integratedServer/browserfsShared.ts (2)

186-203: Good job on addressing the async Promise executor issue.

The Promise executor is correctly implemented without using async, which prevents potential unhandled rejections. This addresses the feedback from previous reviews.


104-108:

❓ Verification inconclusive

Address the TODO comment about world corruption.

The comment indicates a critical issue with write operations causing world corruption on interactions like block placements. This needs to be addressed to ensure data integrity.

This appears to be a potential source of data corruption when blocks are placed while in read-only mode. Could you provide more information about this issue or how it manifests?


Action Required: Clarify and Resolve the World Corruption Risk

The code at src/integratedServer/browserfsShared.ts lines 104–108 currently shortcuts write operations in read-only mode by returning an empty buffer. However, the inline TODO note warns that this handling may lead to world corruption (e.g., during block placements). To proceed, please clarify the following:

  • How exactly do these write operations lead to world corruption when the filesystem is in read-only mode?
  • What is the expected behavior for write attempts in this state—should we throw an explicit error instead of silently returning an empty result?
  • Do you have test cases or scenarios where this behavior has been observed, so we can better understand the risk?

Once we have more details on the manifestation of this issue and a clear strategy to prevent any data integrity problems, we can consider adjusting the implementation accordingly.

Comment on lines +119 to +121
// todo that's not correct
currentInternalFsState.openWriteOperations--
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix inaccurate tracking of write operations.

The comment "todo that's not correct" indicates that the tracking of write operations is inaccurate, which could lead to incorrect state management.

Consider implementing proper operation tracking. Without more context on why this is incorrect, a thorough review of the operation counting logic is needed to ensure operations are properly tracked and closed.

Comment on lines +287 to +316
export async function removeFileRecursiveAsync (path) {
const errors = [] as Array<[string, Error]>
try {
const files = await fs.promises.readdir(path)

// Use Promise.all to parallelize file/directory removal
await Promise.all(files.map(async (file) => {
const curPath = join(path, file)
const stats = await fs.promises.stat(curPath)
if (stats.isDirectory()) {
// Recurse
await removeFileRecursiveAsync(curPath)
} else {
// Delete file
await fs.promises.unlink(curPath)
}
}))

// After removing all files/directories, remove the current directory
await fs.promises.rmdir(path)
} catch (error) {
errors.push([path, error])
}

if (errors.length) {
setTimeout(() => {
console.error(errors)
throw new Error(`Error removing directories/files: ${errors.map(([path, err]) => `${path}: ${err.message}`).join(', ')}`)
})
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor error handling in removeFileRecursiveAsync.

Using setTimeout to throw errors after the function has returned can lead to unhandled promise rejections and makes error handling unpredictable.

export async function removeFileRecursiveAsync (path) {
  const errors = [] as Array<[string, Error]>
  try {
    const files = await fs.promises.readdir(path)

    // Use Promise.all to parallelize file/directory removal
    await Promise.all(files.map(async (file) => {
      const curPath = join(path, file)
      const stats = await fs.promises.stat(curPath)
      if (stats.isDirectory()) {
        // Recurse
        await removeFileRecursiveAsync(curPath)
      } else {
        // Delete file
        await fs.promises.unlink(curPath)
      }
    }))

    // After removing all files/directories, remove the current directory
    await fs.promises.rmdir(path)
  } catch (error) {
    errors.push([path, error])
  }

  if (errors.length) {
-   setTimeout(() => {
-     console.error(errors)
-     throw new Error(`Error removing directories/files: ${errors.map(([path, err]) => `${path}: ${err.message}`).join(', ')}`)
-   })
+   console.error(errors)
+   throw new Error(`Error removing directories/files: ${errors.map(([path, err]) => `${path}: ${err?.message || String(err)}`).join(', ')}`)
  }
}
📝 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.

Suggested change
export async function removeFileRecursiveAsync (path) {
const errors = [] as Array<[string, Error]>
try {
const files = await fs.promises.readdir(path)
// Use Promise.all to parallelize file/directory removal
await Promise.all(files.map(async (file) => {
const curPath = join(path, file)
const stats = await fs.promises.stat(curPath)
if (stats.isDirectory()) {
// Recurse
await removeFileRecursiveAsync(curPath)
} else {
// Delete file
await fs.promises.unlink(curPath)
}
}))
// After removing all files/directories, remove the current directory
await fs.promises.rmdir(path)
} catch (error) {
errors.push([path, error])
}
if (errors.length) {
setTimeout(() => {
console.error(errors)
throw new Error(`Error removing directories/files: ${errors.map(([path, err]) => `${path}: ${err.message}`).join(', ')}`)
})
}
export async function removeFileRecursiveAsync (path) {
const errors = [] as Array<[string, Error]>
try {
const files = await fs.promises.readdir(path)
// Use Promise.all to parallelize file/directory removal
await Promise.all(files.map(async (file) => {
const curPath = join(path, file)
const stats = await fs.promises.stat(curPath)
if (stats.isDirectory()) {
// Recurse
await removeFileRecursiveAsync(curPath)
} else {
// Delete file
await fs.promises.unlink(curPath)
}
}))
// After removing all files/directories, remove the current directory
await fs.promises.rmdir(path)
} catch (error) {
errors.push([path, error])
}
if (errors.length) {
console.error(errors)
throw new Error(`Error removing directories/files: ${errors.map(([path, err]) => `${path}: ${err?.message || String(err)}`).join(', ')}`)
}
}

Copy link

github-actions bot commented Apr 5, 2025

Deployed to Vercel Preview: https://prismarine-7cretdayd-zaro.vercel.app
Playground
Storybook

Copy link

@coderabbitai coderabbitai bot left a 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 (25)
src/globals.js (1)

11-11: Consider the implications of setting such a high listener limit.

Setting the maximum listeners to 10,000 prevents warnings but may indicate an architectural concern. A high number of listeners could:

  1. Lead to memory leaks if not properly cleaned up
  2. Impact performance when events are emitted
  3. Suggest a design where event listeners might be better managed differently

Consider these alternatives:

  • Ensure listeners are properly removed when no longer needed
  • Add comments explaining why this high limit is necessary
  • Monitor actual listener count during development to determine a more appropriate limit
  • Implement a more structured event management system if the WebGPU renderer truly requires thousands of listeners
src/core/timers.ts (2)

127-134: Consider adding explicit return type annotation

The function now returns a Promise, but lacks an explicit TypeScript return type annotation. For better type safety and code clarity:

-return async () => {
+return async (): Promise<void> => {

127-138: Inconsistent error handling approach

There's inconsistency in error handling - the outer try-catch still uses console.error while the inner one now uses console.warn. Consider using a consistent approach to error logging based on severity.

  try {
    // ...existing code...
    return async (): Promise<void> => {
      try {
        oscillator.stop()
        await audioContext.close()
      } catch (err) {
        console.warn('Error stopping silent audio:', err)
      }
    }
  } catch (err) {
-   console.error('Error creating silent audio:', err)
+   console.warn('Error creating silent audio:', err)
    return () => {}
  }
src/index.ts (2)

335-348: Avoid using any type for CustomClient.

The any type bypasses TypeScript's type checking and could lead to runtime errors. Consider defining a proper interface or type for CustomClient.

-let CustomClient: any
+// Define a proper interface for the client
+interface CustomClientInterface {
+  // Add relevant properties and methods
+}
+let CustomClient: CustomClientInterface

349-368: Clean up commented-out server initialization code.

There's a significant block of commented-out code related to server initialization and loading status updates. If this functionality has been moved elsewhere or is no longer needed, consider removing it to improve code readability.

src/integratedServer/worker.ts (3)

8-8: Prefer defining a distinct interface for options.

Currently, server is declared as FullServer & { options } without specifying the shape of options. Consider defining a concrete interface or type for options to improve readability and type safety.

- let server: import('flying-squid/dist/index').FullServer & { options }
+ interface ServerOptions {
+   // add explicit fields here
+ }
+ let server: import('flying-squid/dist/index').FullServer & { options: ServerOptions }

41-41: Add a type for processDataGlobal.

Storing a critical function or object in an untyped variable can introduce maintainability issues.

-let processDataGlobal
+let processDataGlobal: (data: unknown) => void

63-63: Type the serverOptions parameter for clarity.

Consider defining an interface for serverOptions to ensure a consistent usage across the codebase.

- const startServer = async (serverOptions) => {
+ interface ServerStartOptions {
+   // define relevant fields
+ }
+ const startServer = async (serverOptions: ServerStartOptions) => {
src/integratedServer/main.ts (1)

36-48: Avoid duplicating restorePatchedDataDeep logic.

A similar function exists in worker.ts. Extract it to a shared utility module to promote reusability and reduce code duplication.

renderer/viewer/webgpu/worldrendererWebgpu.ts (9)

66-74: Replace any type with a proper type definition.

Using any bypasses TypeScript's type checking, which can lead to runtime errors. Consider defining a proper interface for the proxy.

 getPlaceholderChannel () {
   return new Proxy({}, {
     get: (target, p) => (...args) => {
       void this.readyWorkerPromise.then(() => {
         this.webgpuChannel[p](...args)
       })
     }
-  }) as any // placeholder to avoid crashes
+  }) as typeof workerProxyType['__workerProxy'] // placeholder to avoid crashes
 }

87-94: Simplify camera vector preparation.

The nested reduce functions make the code harder to read. Consider using a more straightforward approach with object literals.

 sendCameraToWorker () {
-  const cameraVectors = ['rotation', 'position'].reduce((acc, key) => {
-    acc[key] = ['x', 'y', 'z'].reduce((acc2, key2) => {
-      acc2[key2] = this.camera[key][key2]
-      return acc2
-    }, {})
-    return acc
-  }, {}) as any
+  const cameraVectors = {
+    position: {
+      x: this.camera.position.x,
+      y: this.camera.position.y,
+      z: this.camera.position.z
+    },
+    rotation: {
+      x: this.camera.rotation.x,
+      y: this.camera.rotation.y,
+      z: this.camera.rotation.z
+    }
+  }
   this.webgpuChannel.camera({
     ...cameraVectors,
     fov: this.camera.fov
   })
 }

181-182: Implement the empty updatePosDataChunk method.

The method is empty but seems to be part of the renderer API. Either implement the required functionality or add a TODO comment explaining why it's empty.

 updatePosDataChunk (key: string) {
+  // TODO: Implement position data chunk updates for WebGPU renderer
 }

200-202: Consider implementing updateShowChunksBorder.

The method contains a TODO comment but doesn't provide any implementation. This affects feature completeness.

Would you like me to propose an implementation for this method based on how it might be implemented in other renderers?


240-242: Clarify playground setting.

Line 240 is commented out, and line 241 sets playground to false directly. This suggests a temporary change that may need revisiting.

-  // const playground = this.displayOptions.isPlayground
-  const playground = false
+  // Always false for now, but may need to be configurable in the future
+  const playground = false // this.displayOptions.isPlayground

245-247: Verify canvas element existence before removal.

The code removes the existing canvas without checking if it exists, which could lead to errors if the element isn't found.

 const existingCanvas = document.getElementById('viewer-canvas')
-existingCanvas?.remove()
+if (existingCanvas) {
+  existingCanvas.remove()
+}

255-267: Consider using a more dynamic approach for worker path.

The worker path is hardcoded, which might cause issues if the application structure changes. Consider using a configurable or relative path.

 if (USE_WORKER) {
-  this.worker = new Worker('./webgpuRendererWorker.js')
+  // Use a configurable path or import.meta.url for relative paths
+  const workerPath = new URL('./webgpuRendererWorker.js', import.meta.url).href
+  this.worker = new Worker(workerPath)
   console.log('starting offscreen')
 } else if (globalThis.webgpuRendererChannel) {

280-285: Replace setTimeout(0) with a more explicit approach.

Using setTimeout with a 0ms delay is a hacky way to force an event loop tick. Consider using a more explicit approach.

 if (!USE_WORKER) {
 // wait for the .canvas() message to be processed (it's async since we still use message channel)
-  await new Promise(resolve => {
-    setTimeout(resolve, 0)
-  })
+  // Use queueMicrotask or Promise.resolve() to defer execution to the next event loop tick
+  await Promise.resolve()
 }

398-398: Consider extracting build info to a configuration.

Hardcoding build information directly in the UI code makes it harder to maintain. Consider extracting it to a configuration file.

-  updateTextBuild(`WebGPU Renderer Demo by @SA2URAMI. Build: ${process.env.NODE_ENV === 'development' ? 'dev' : process.env.RELEASE_TAG}`)
+  const buildInfo = {
+    author: '@SA2URAMI',
+    version: process.env.NODE_ENV === 'development' ? 'dev' : process.env.RELEASE_TAG
+  }
+  updateTextBuild(`WebGPU Renderer Demo by ${buildInfo.author}. Build: ${buildInfo.version}`)
src/browserfs.ts (7)

7-7: Consider renaming installResourcepackPack for clarity.
The function name can be confusing. A more concise name (e.g., installResourcePack) might be clearer.

Suggested rename:

- import { installResourcepackPack, updateTexturePackInstalledState } from './resourcePack'
+ import { installResourcePack, updateTexturePackInstalledState } from './resourcePack'

Also update its usage at line 226 accordingly:

- void installResourcepackPack(file, createFullScreenProgressReporter()).catch((err) => {
+ void installResourcePack(file, createFullScreenProgressReporter()).catch((err) => {

134-134: Consider handling potential failures from mountRemoteFsBackend.
If an error is thrown here, it won't be caught or communicated. A simple try/catch block and user-facing message could improve resilience.

-  await mountRemoteFsBackend(fsState)
+  try {
+    await mountRemoteFsBackend(fsState)
+  } catch (e) {
+    showNotification('Error mounting remote FS', e.message, true)
+    throw e
+  }

141-158: Remove or revise large commented block.
This entire ZipFS mounting logic is disabled; if no longer needed, removing it keeps the file clean. If you intend to re-enable soon, add a clarifying comment.


198-198: Revisit commented-out cleanup logic.
If possiblyCleanHandle() is obsolete, consider removing it. If necessary, re-enable it or provide an alternative resource cleanup mechanism.


251-273: Most of the resource-pack detection code is commented out.
Refactor or remove the code if the final approach is to let users install texture packs via the options menu. Keeping it commented might cause confusion.

Would you like help investigating the mentioned “browserfs read errors”?


275-275: Unused constant.
const SUPPORT_WRITE = true is unused now that the code referencing it is commented out.

Remove it or reintroduce it together with the relevant logic.


277-321: Large commented-out block for directory-based world loading.
If you no longer plan to support direct directory handles, consider removing this block. Unused code can hinder readability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 792a195 and 6b642d0.

📒 Files selected for processing (9)
  • renderer/playground/webgpuRendererShared.ts (1 hunks)
  • renderer/viewer/webgpu/worldrendererWebgpu.ts (1 hunks)
  • src/browserfs.ts (5 hunks)
  • src/core/timers.ts (1 hunks)
  • src/globals.js (1 hunks)
  • src/index.ts (10 hunks)
  • src/integratedServer/main.ts (1 hunks)
  • src/integratedServer/worker.ts (1 hunks)
  • src/optionsStorage.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/optionsStorage.ts
  • renderer/playground/webgpuRendererShared.ts
🧰 Additional context used
🧬 Code Definitions (2)
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/integratedServer/worker.ts (5)
src/integratedServer/customServer.ts (1)
  • createCustomServerImpl (5-23)
src/createLocalServer.ts (1)
  • startLocalServer (3-13)
src/integratedServer/browserfsShared.ts (1)
  • localFsState (44-46)
src/loadSave.ts (1)
  • fsState (19-19)
src/integratedServer/browserfsServer.ts (1)
  • mountFsBackend (42-50)
🪛 Biome (1.9.4)
src/integratedServer/worker.ts

[error] 37-37: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)


[error] 16-16: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 17-17: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

🔇 Additional comments (16)
src/core/timers.ts (2)

130-130: Properly awaiting AudioContext.close()

Good improvement! Adding await ensures the audio context is fully closed before continuing. AudioContext.close() returns a Promise that should be properly awaited.


132-132: Changed error severity level from error to warning

The error handling has been changed from console.error to console.warn, indicating this is considered a less critical issue. This is reasonable since it's occurring during cleanup, but ensure this aligns with your error handling policy.

src/index.ts (5)

94-95: Import refactoring reflects the new WebGPU renderer architecture.

The imports have been updated to use the new integrated server main module, which aligns with the WebGPU renderer implementation mentioned in the PR objectives.


198-202: Improved server shutdown approach.

The server shutdown logic has been refactored to use a dedicated method destroyLocalServerMain(false) instead of manual reference clearing. This is a cleaner approach that ensures proper cleanup of resources.


708-708: Good use of async/await for world initialization.

Adding the await keyword to the appViewer.startWorld call ensures proper sequencing of world initialization, which prevents potential race conditions that could occur if the world isn't fully loaded before subsequent operations.


220-223:

✅ Verification successful

Verify if file system cleanup is still needed.

The commented-out possiblyCleanHandle code suggests file system cleanup might no longer be implemented. Ensure this doesn't cause resource leaks.


🏁 Script executed:

#!/bin/bash
# Check if possiblyCleanHandle is used elsewhere or has been replaced
rg "possiblyCleanHandle" --type ts
# Check for other file cleanup mechanisms that might have replaced it
rg "cleanHandle|cleanFs|fileHandle" --type ts

Length of output: 347


File System Cleanup Verified – Relying on cleanFs()

After verifying the shell script results, it's clear that file system cleanup is still being handled in the code. The commented-out possiblyCleanHandle block in src/index.ts has been replaced by an active implementation using the cleanFs() function. The search also confirms that while possiblyCleanHandle is defined in other parts of the codebase (in src/integratedServer/browserfsShared.ts), it isn’t used in the cleanup process in src/index.ts.

Actionable Points:

  • Confirm that cleanFs() in src/index.ts meets all file system cleanup requirements.
  • Consider removing the commented-out possiblyCleanHandle code from src/index.ts to reduce confusion and maintain code clarity.

456-461:

❓ Verification inconclusive

Ensure proper client initialization for all connection types.

The commented-out CustomChannelClient on line 460 suggests a change in client initialization for replay sessions. Verify that replay functionality still works correctly with the new WebGPU renderer.


🏁 Script executed:

#!/bin/bash
# Look for tests or usages of replay functionality
rg "localReplaySession|startLocalReplayServer" --type ts -A 5 -B 5

Length of output: 4922


Review Update: Verify Replay Session Client Initialization with WebGPU Renderer

  • The grep results confirm that replay functionality is exercised (see usage in src/packetsReplay/replayPackets.ts and the invocation of startLocalReplayServer).
  • In src/index.ts (lines 456–461), when localReplaySession is active the configuration only defines an empty connect() function, and the intended client assignment (CustomChannelClient as any) remains commented out.
  • Please ensure that replay sessions are properly initialized for the new WebGPU renderer. If a custom client is still required for replay, update the initialization accordingly; if not, remove the commented code to avoid confusion.
  • Additionally, verify that all replay functionality tests pass with the current renderer integration.
src/integratedServer/worker.ts (1)

16-17: Avoid using '{}' as a type
This usage was flagged in a previous review. Leaving it unresolved can lead to confusion, as '{}' matches any non-nullable value.

 export interface BackEvents {
-  ready: {}
-  quit: {}
+  ready: Record<string, never>
+  quit: Record<string, never>
   packet: any
   ...
 }
🧰 Tools
🪛 Biome (1.9.4)

[error] 16-16: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 17-17: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

src/integratedServer/main.ts (2)

17-34: lastOptions is never assigned.

This was raised in a prior review. Returning a never-assigned variable can cause unexpected results. Consider setting lastOptions after initializing or starting the server.

+ lastOptions = serverOptions

56-56:

❓ Verification inconclusive

Verify the availability of './integratedServer.js'.

Ensure your build process or deployment correctly generates and serves integratedServer.js. Otherwise, instantiating the Worker will fail.


🏁 Script executed:

#!/bin/bash
# Search for 'integratedServer.js' generation in build scripts:
rg "integratedServer.js"

Length of output: 410


Attention Required: Confirm Worker Script Accessibility and Correct Path

In src/integratedServer/main.ts (line 56), the Worker is instantiated with:

worker = new Worker('./integratedServer.js')

Our search shows that your build process generates integratedServer.js in the dist directory (see references in renderer/rsbuildSharedConfig.ts and buildWorkers.mjs). Please verify that at runtime the worker file is accessible by this relative path. If it isn’t—because the file is actually served from the dist folder—you might need to adjust the Worker instantiation (e.g., use new Worker('./dist/integratedServer.js')) or ensure that your deployment properly maps the generated file to the expected location.

renderer/viewer/webgpu/worldrendererWebgpu.ts (1)

58-64: Add MessagePort cleanup in destroy method.

When the worker is a MessagePort, it should be closed to avoid resource leaks. This was previously identified but not implemented.

 override destroy () {
   this.abortController.abort()
   this.webgpuChannel.destroy() // still needed in case if running in the same thread
   if (this.worker instanceof Worker) {
     this.worker.terminate()
+  } else if (this.worker instanceof MessagePort) {
+    this.worker.close()
   }
 }
src/browserfs.ts (5)

4-4: No issues with the added import.
This import for resetOptions is straightforward and matches its usage below.


14-14: Looks good.
Importing functions from browserfsShared ensures a clearer separation of responsibilities.


131-133: State assignment appears correct.
Storing indexFileUrl and baseUrl in fsState should simplify tracking the active remote backend.


95-121:

✅ Verification successful

Validate new indexFileUrl usage.
The logic for discovering and storing indexFileUrl appears sound. However, ensure that no additional calls rely on the old approach.


🏁 Script executed:

#!/bin/bash
# Verify usage of the newly introduced indexFileUrl property.
rg -A 3 'indexFileUrl'

Length of output: 600


IndexFileUrl Integration Validated

The updated implementation in src/browserfs.ts correctly sets and routes indexFileUrl into the file system state. The grep output confirms that:

  • indexFileUrl is assigned from the fetched URL.
  • It is subsequently stored in fsState.usingIndexFileUrl.
  • No other parts of the codebase reference an older approach.

It appears that the new usage is isolated and fully integrated. Please double-check that any downstream dependencies (e.g., within mountRemoteFsBackend) consistently reference fsState.usingIndexFileUrl.


1-1:

❓ Verification inconclusive

Double-check Node’s fs usage in a browser environment.
If this code is intended for the browser, relying on Node’s fs could lead to compatibility issues unless properly polyfilled or only used when running in Node-like environments.

Please verify all references to fs with the following script to confirm whether they’re guarded by environment checks or replaced in production builds:


🏁 Script executed:

#!/bin/bash
# Search for usage of `fs` to ensure calls are safe for the browser environment.
rg -A 5 $'import.*fs.*?from'

Length of output: 21004


Action: Verify Conditional Usage of Node’s fs in Browser Context

In the file src/browserfs.ts, the import of Node’s fs module remains unguarded:

import * as fs from 'fs'

Since this module also imports BrowserFS (which provides a browser-compatible filesystem polyfill), please ensure that any direct calls to Node’s native fs are either conditionally executed (e.g., wrapped by environment checks) or properly replaced/aliased in the browser production build. This verification is especially important given the widespread usage of fs across the codebase, as shown by the search results.

  • Confirm that the bundler or runtime environment correctly handles the unguarded fs import when running in a browser.
  • If the file is exclusively for Node environments, consider clarifying this by renaming the file or adding explicit comments to avoid confusion.

oscillator.start()

return () => {
return async () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Return type is now asynchronous - confirm consumer code compatibility

The function now returns an async function, which means it returns a Promise. This is a breaking change that might require updates to any code that calls this function.


🏁 Script executed:

#!/bin/bash
# Find all usages of preventThrottlingWithSound to verify if consumer code handles the async return value
rg "preventThrottlingWithSound" --type ts -A 3 -B 3

Length of output: 975


Update consumer code to properly await the async function

The function now returns an asynchronous function (i.e. a Promise resolving to a cleanup callback). In its current usage (in src/mineflayer/timers.ts), the return value is directly assigned and passed to bot.on('end') without being awaited, which could lead to unexpected behavior. Please update the call site to handle the promise correctly, for example:

  • Change from:
    const unsub = preventThrottlingWithSound();
    bot.on('end', unsub);
  • To something like:
    (async () => {
      const unsub = await preventThrottlingWithSound();
      bot.on('end', unsub);
    })();

Review all consumer code paths for preventThrottlingWithSound to ensure they correctly await its asynchronous return, thereby maintaining compatibility and preventing runtime issues.

globalThis.postMessage({ type, data }, ...args)
} catch (err) {
// eslint-disable-next-line no-debugger
debugger
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove the leftover debugger statement.

Having debugger in production code can unintentionally pause execution in debugging tools.

- debugger
📝 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.

Suggested change
debugger
🧰 Tools
🪛 Biome (1.9.4)

[error] 37-37: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)

Comment on lines +372 to +374
const addWebgpuDebugUi = (worker, isPlayground, worldRenderer: WorldRendererWebgpu) => {
// todo destroy
const mobile = isMobile()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

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:

  1. Removes event listeners
  2. Clears timers
  3. Removes DOM elements

This function should be called when the renderer is destroyed.

Comment on lines +243 to +244
const imageBlob = await fetch(image).then(async (res) => res.blob())

Copy link

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 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;
+  }
📝 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.

Suggested change
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;
}

Comment on lines +426 to +427
searchParams.set(key, String(value))
window.location.search = searchParams.toString()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix URL search parameter update.

The code sets the search parameter to the old value value instead of the new value newVal.

 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()
 }
📝 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.

Suggested change
searchParams.set(key, String(value))
window.location.search = searchParams.toString()
if (rendererParamsGui[key]?.qsReload) {
const searchParams = new URLSearchParams(window.location.search)
searchParams.set(key, String(newVal))
window.location.search = searchParams.toString()
}

Comment on lines +377 to +389
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())}`
}
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

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())}`
-    }
-  })
📝 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.

Suggested change
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())}`
}
})
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)
})

Copy link

github-actions bot commented Apr 5, 2025

Deployed to Vercel Preview: https://prismarine-5o52rrley-zaro.vercel.app
Playground
Storybook

Copy link

@coderabbitai coderabbitai bot left a 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 issue

Method signature change may cause unintended mutation

The updateBreakAnimation method 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 original position object from mergedShape. If mergedShape.position is 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.
Using any for position and 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 shared Vec3Like type 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 inconsistency

The mergedShape parameter 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:

  1. Making the parameter required in the signature, or
  2. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6b642d0 and 746f90d.

📒 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.inMemorySave condition check. This prevents potential errors when attempting to access inMemorySavePath when there is no in-memory save available.


948-951: Avoid leaving debugger statements in production code.

According to static analysis, the debugger statement 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:

❓ 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 the activeRenderer value.

Before finalizing this change, please manually verify that the activeRenderer property is properly defined and used in the corresponding UI/options file (for example, in optionsGuiScheme.tsx or the relevant component), as our initial search using rg encountered flag issues.

  • File: src/controls.ts (lines 952–954) – toggling logic is correctly implemented.
  • Action Needed: Confirm that the activeRenderer property 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 like inWater and improves readability.


67-68: Reactive array reference for 'backgroundColor' is appropriate.
Good usage of ref to 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 in playerState is 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 types

The import statement has been updated to use the new BlockShape and BlocksShapes types 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 handling

The parameter type for shapePositions has been updated to use the more specific BlocksShapes type, which improves type safety and makes the expected data structure clearer.

Copy link

@coderabbitai coderabbitai bot left a 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 issue

Potential memory leak from interval.

A setInterval is 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 exposure

The new initialization approach using structuredClone(initialFsState) is a good practice as it:

  1. Creates a deep copy of the initial state to prevent reference issues
  2. Centralizes the state definition in one place
  3. Makes the state structure more predictable and maintainable

Making the state available globally with globalThis.fsState = fsState improves 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

📥 Commits

Reviewing files that changed from the base of the PR and between 746f90d and 96b1a81.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is 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 leaving debugger statements in production code.

According to static analysis, the debugger statement 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 feature

The 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 module

The import statement has been updated to bring in utility functions and state from the browserfsShared module, which helps centralize file system related functionality. This is a good practice for maintainability.


107-107: Consistent use of encapsulated state properties

Using fsState.forceCachedDataPaths instead of a standalone variable improves code organization by properly encapsulating related state within the fsState object.


146-146: Consistent state property access pattern

Using fsState.forceRedirectPaths follows 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.

Comment on lines +601 to +608

// viewer.world.chunksReset() // todo

// TODO!
// if (localServer) {
// //@ts-expect-error not sure why it is private... maybe revisit api?
// localServer.players[0].world.columns = {}
// }
Copy link

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.

Copy link

@coderabbitai coderabbitai bot left a 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 any type 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 getPlaceholderChannel method:

-  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 any type 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 any type 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 removed

This 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

📥 Commits

Reviewing files that changed from the base of the PR and between 96b1a81 and fe590bf.

📒 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:

  1. Removes event listeners
  2. Clears timers
  3. 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 value instead of the new value newVal.

 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 renderer

The import of the WebGPU backend matches the PR objective to implement a new rendering engine using WebGPU.


11-11: Correctly enables WebGPU backend loading

Previously commented code is now active, properly loading the WebGPU renderer when selected in options.


37-45:

❓ Verification inconclusive

Potential undefined variable in renderer change handler

The subscription handler references a global bot variable 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.ts references bot without a local declaration. Our search shows that bot is defined as a global variable in both src/index.ts and src/globals.js (via assignments to window.bot). Although this confirms that a global bot is 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.bot instead of bot directly 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 bot always runs before the subscription handler in src/appViewerLoad.ts fires.
src/index.ts (4)

94-94: Appropriate imports for server management

The imports correctly include the new server management functions that replace the previous implementation.


200-200: Improved server termination with proper cleanup

Using 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 encapsulation

Obtaining the CustomClient from startLocalServerMain promotes better separation of concerns compared to the previous approach.


706-706: Improved encapsulation with new startWithBot method

Using 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 resetBackend

The change simplifies the method by delegating the cleanup logic to disconnectBackend, which is good for code maintainability.


221-226: Good consolidation of cleanup logic

Updating the disconnectBackend method to handle the state cleanup based on the cleanState parameter consolidates related functionality in one place, which is a good practice.


235-235: Simplified renderer state initialization

The direct assignment using proxy(getDefaultRendererState()) is cleaner than the previous approach and ensures a fresh renderer state.

Comment on lines +430 to +438
const mainLoop = () => {
requestAnimationFrame(mainLoop)
updates++
}
mainLoop()
setInterval(() => {
updateText2(`Main Loop: ${updates}`)
updates = 0
}, 1000)
Copy link

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.

Suggested change
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);
})

Comment on lines +115 to +123
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)
}
})
})
}
Copy link

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.

Suggested change
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...
}

Comment on lines +221 to +223
const blob = await fetch(this.resourcesManager.currentResources!.blocksAtlasParser.latestImage).then(async (res) => res.blob())
this.webgpuChannel.updateTexture(blob)
}
Copy link

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.

Suggested change
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}`);
}
}

Comment on lines +39 to +44
subscribeKey(options, 'activeRenderer', () => {
if (appViewer.currentDisplay === 'world' && bot) {
appViewer.resetBackend(true)
loadBackend()
void appViewer.startWithBot()
}
Copy link

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.

Comment on lines 217 to +220
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
// })
Copy link

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 possiblyCleanHandle with 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.

Comment on lines +158 to +162
async startWithBot () {
const renderDistance = miscUiState.singleplayer ? options.renderDistance : options.multiplayerRenderDistance
await this.startWorld(bot.world, renderDistance)
this.worldView!.listenToBot(bot)
}
Copy link

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.bot instead of the undeclared bot.
  • Add an initialization check (e.g., verify that window.bot exists).
  • 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.

Suggested change
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
}
}

Copy link

@coderabbitai coderabbitai bot left a 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 handles timeOfDay < 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 setting allChunksFinished.
This line reassigns this.allChunksFinished, while checkAllFinished() and other lines (e.g. line 703) also manage it. Centralizing the logic in checkAllFinished() (or a single function) can reduce confusion and maintain consistency.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe590bf and 809da7c.

📒 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 of addNewStat, removeAllStats, removeStat, and updateStatText. This aligns with the overall stats-tracking approach in the codebase.


56-56: Initialization of day time indicator.
Defining timeOfTheDay = 0 is a straightforward and clear default.


160-161: Stat creation for downloaded chunks.
Using addNewStat to visualize the "downloaded-chunks" metric is consistent with your existing stat-based UI.


164-168: Potential memory leak from setInterval.
A setInterval is 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 calling clearInterval in destroy() 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 if timeOfDay is 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 timeOfTheDay to an invalid range, or should we remove the no-op block in getMesherConfig()?

@zardoy zardoy changed the title New realisation of renderer on WebGPU (webgpu.mcraft.fun) New FAST renderer on clean WebGPU try: -> webgpu.mcraft.fun <- Apr 6, 2025
@zardoy
Copy link
Owner

zardoy commented Apr 6, 2025

@coderabbitai pause

Copy link

coderabbitai bot commented Apr 6, 2025

✅ Actions performed

Reviews paused.

Copy link

github-actions bot commented Apr 7, 2025

Deployed to Vercel Preview: https://prismarine-axi4apbgu-zaro.vercel.app
Playground
Storybook

Copy link

github-actions bot commented Apr 7, 2025

Deployed to Vercel Preview: https://prismarine-p1admyy31-zaro.vercel.app
Playground
Storybook

@Alexander-Peterson
Copy link

Is their a download fo the modified minecraft web client of this site: https://prismarine-pho51nlw6-zaro.vercel.app/

@zardoy
Copy link
Owner

zardoy commented Apr 15, 2025

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

Copy link

Deployed to Vercel Preview: https://prismarine-7s5ojgyk2-zaro.vercel.app
Playground
Storybook

@zardoy zardoy changed the title New FAST renderer on clean WebGPU try: -> webgpu.mcraft.fun <- New FAST renderer on clean WebGPU try: (webgpu.mcraft.fun) Jun 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants