Skip to content

Conversation

@ibgreen
Copy link
Collaborator

@ibgreen ibgreen commented Nov 27, 2024

Changes

  • Replace regl with luma.gl/core and luma.gl/engine
  • Replace regl.Framebuffer2D, Texture2D, Buffer with luma.gl Framebuffer, Texture, Buffer
  • Replace regl.DrawCommend with luma.gl Model

Of course nothing builds at this point, additional passes will be needed.

Summary by CodeRabbit

  • New Features

    • Optional device‑backed GPU rendering, HTML sanitization utility, and release bumped to 2.7.0‑beta.2
  • Improvements

    • pixelRatio uses devicePixelRatio for sharper high‑DPI output
    • Shaders modernized for WebGL2/GLSL with optional uniform‑buffer support
    • Many modules migrated to a unified GPU pipeline with clearer resource lifecycles
  • Bug Fixes

    • WebGL error dialogs now include reported error details
  • Removed

    • Legacy quadtree many‑body module and related configuration flags

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 27, 2024

📝 Walkthrough

Walkthrough

Device-backed rendering replaces Regl across the codebase; shaders modernized to GLSL ES 3.00 with optional UBOs; ForceManyBodyQuadtree removed; many modules adopt luma.gl Model/Texture/Framebuffer/UniformStore APIs and explicit resource lifecycles.

Changes

Cohort / File(s) Summary
Build / deps
package.json, vite.config.ts, tsconfig.json
Version bump to 2.7.0-beta.2; add @luma.gl/*; remove regl and vite-plugin-glsl; add prepublishOnly script; include vite.config.ts in TS config; adjust Rollup externals/globals.
Core Graph & helpers
src/index.ts, src/config.ts, src/helper.ts, src/graph/utils/error-message.ts, src/modules/core-module.ts, src/variables.ts, src/modules/Store/index.ts
Introduce Device lifecycle (createDevice/ensureDevice/validateDevice), optional external devicePromise, device ownership flags, new device-backed readPixels API, sanitizeHtml util, createWebGLErrorMessage now accepts error string, removed quadtree config flags, pixelRatio default uses window.devicePixelRatio, added transformationMatrix4x4 and MIN_MOUSE_MOVEMENT_THRESHOLD.
Points (major)
src/modules/Points/**, src/modules/Shared/texture-utils.ts, src/modules/Shared/uniform-utils.ts
Full migration from Regl to luma.gl Models/Textures/Framebuffers/Buffers/UniformStores; many shaders upgraded (in/out, texture, fragColor) and UBO branches added; new uniform-store-backed flows, vertex buffers, GPU textures exposed; helpers getBytesPerRow, ensureVec2/ensureVec4 added; destroy lifecycle added.
Clusters
src/modules/Clusters/**
Converted to Model/UniformStore pipeline; new UniformStores, Model commands, dynamic texture/FBO recreation, previous-size tracking; added initPrograms, calculateCentermass, run(renderPass?) and destroy lifecycle; types switched to luma.gl Framebuffer/Model/Buffer/Texture.
Lines
src/modules/Lines/**
Regl → Model migration; new GPU buffers/textures/UniformStores/Models; shaders modernized and UBO-enabled; picking/hover path moved to texture+Model; draw(renderPass) updated; new destroy flow and bytes-per-row/readback considerations.
Force modules
src/modules/Force*/**
Per-module migrations to Model/UniformStore/RenderPass flows; GLSL upgraded to #version 300 es, texture() and in/out usage, optional uniform blocks; Models and destroy() methods added; ForceManyBodyQuadtree and its shader-generator removed.
ForceManyBody quadtree removal
src/modules/ForceManyBodyQuadtree/*, src/modules/ForceManyBody/quadtree-frag-shader.ts
Entire quadtree module and shader-generator deleted; exported shader factory forceFrag(startLevel,maxLevels) removed — update callers.
Shared utils & shaders
src/modules/Shared/*, many src/modules/**/**/*.frag/.vert
Removed several Regl helpers and clear.frag; added texture-utils.getBytesPerRow and uniform-utils; widespread shader modernization to GLSL300 (in/out, texture, fragColor) and optional std140 UBO macros—review std140 layout and mat3/mat4 conversion sites.
Stories / examples
src/stories/**
Stories updated to return optional destroy hooks; Storybook render wrapper now returns container and wires destroy; some stories create/provide devicePromise to Graph; beforeEach cleanup switched to returned destroy.
Other module migrations & API changes
many src/modules/*/index.ts
Multiple modules (ForceCenter, ForceGravity, ForceLink, ForceManyBody, ForceMouse, Points, Lines, Clusters, etc.) changed public resource types from regl types to luma.gl types, added Model/UniformStore fields, and added destroy()/lifecycle updates — check public signatures where modules are consumed.

Sequence Diagram(s)

sequenceDiagram
  participant App as App / Story
  participant Graph as Graph
  participant Device as Device (`@luma.gl`)
  participant GPU as GPU/Driver

  rect rgba(70,130,180,0.5)
  App->>Graph: new Graph(div, config, devicePromise?)
  alt external devicePromise provided
    App->>Device: provide devicePromise
    Device-->>Graph: device resolved
  else Graph creates device
    Graph->>Device: createDevice(canvas, pixelRatio)
    Device-->>Graph: device ready
  end
  end

  rect rgba(34,139,34,0.5)
  Graph->>Device: ensureDevice()
  Graph->>Device: create Textures / Framebuffers / UniformStores / Models
  Device-->>GPU: allocate GPU resources
  end

  rect rgba(255,165,0,0.5)
  loop per-frame
    Graph->>Device: beginRenderPass(target)
    Graph->>Model: setUniforms / setBindings / draw()
    Model->>GPU: draw()
    alt readback needed
      Graph->>Device: readPixelsToArrayWebGL(...)
      Device-->>Graph: Float32Array
    end
    Graph->>Device: endRenderPass()
  end
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • rokotyan

Poem

"I’m a rabbit in the render loop, hopping light and spry,
I nudged old regl burrows out and let new devices fly.
I stitched shaders with fresh in/outs and shelved an old quadtree,
I tidy GPU crumbs and dance where frames now roam free.
Hop, hop — pixels bloom beneath a tiny carrot sky. 🐇"

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the main objective: migrating the codebase from Regl to luma.gl with WebGL 2 support.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

rokotyan and others added 20 commits December 31, 2025 08:20
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
… dynamic sizing

Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
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

🤖 Fix all issues with AI agents
In `@src/index.ts`:
- Around line 675-682: The clear render pass in both the render() and destroy()
methods currently calls device.beginRenderPass(...) and clearPass.end() but
never flushes commands; after the clearPass.end() call in each method (same
pattern as renderFrame()), call this.device.submit() (guarded by the this.device
existence check) so the GPU receives the commands; update the code paths around
device.beginRenderPass / clearPass.end in render() and destroy() to mirror
renderFrame() by invoking this.device.submit() immediately after ending the
pass.
♻️ Duplicate comments (1)
src/index.ts (1)

1415-1423: Prevent ensureDevice callbacks after destroy.

ensureDevice still invokes queued callbacks without checking _isDestroyed, so callbacks can fire after teardown. Please guard before invoking the callback (and optionally before logging) to avoid post-destroy work.

🔧 Suggested fix
 private ensureDevice (callback: () => void): boolean {
   if (!this.device) {
     this.deviceInitPromise
       .then(() => {
+        if (this._isDestroyed) return
         callback()
       })
       .catch(error => {
+        if (this._isDestroyed) return
         console.error('Device initialization failed', error)
       })
     return true
   }
   return false
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05f6885 and 0eb1153.

📒 Files selected for processing (1)
  • src/index.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/index.ts (3)
src/config.ts (1)
  • GraphConfigInterface (20-612)
src/helper.ts (2)
  • getRgbaColor (16-32)
  • readPixels (75-84)
src/variables.ts (1)
  • defaultConfigValues (12-62)

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 675 to 682
if (this.device) {
const clearPass = this.device.beginRenderPass({
clearColor: this.store.backgroundColor,
clearDepth: 1,
clearStencil: 0,
})
clearPass.end()
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check the file exists and see the size
wc -l src/index.ts

Repository: cosmosgl/graph

Length of output: 75


🏁 Script executed:

# Look at the render() method around lines 675-682
sed -n '650,700p' src/index.ts | head -60

Repository: cosmosgl/graph

Length of output: 2230


🏁 Script executed:

# Look at the destroy() method around lines 1315-1324
sed -n '1300,1340p' src/index.ts

Repository: cosmosgl/graph

Length of output: 1204


🏁 Script executed:

# Search for renderFrame() method to see if it calls device.submit()
rg -A 30 'renderFrame\s*\(' src/index.ts | head -80

Repository: cosmosgl/graph

Length of output: 1339


🏁 Script executed:

# Search for all device.submit() calls to understand the pattern
rg 'device\.submit\(\)' src/index.ts -B 3 -A 1

Repository: cosmosgl/graph

Length of output: 126


🏁 Script executed:

# See all uses of device.submit() and surrounding context
rg 'device\.submit\(\)' -B 5 -A 2 src/

Repository: cosmosgl/graph

Length of output: 347


🏁 Script executed:

# Check if there are multiple render passes in renderFrame to understand the pattern
sed -n '1000,1100p' src/index.ts | grep -A 50 'renderFrame'

Repository: cosmosgl/graph

Length of output: 40


🏁 Script executed:

# Look for any imports or type definitions for device
rg 'import.*device|from.*luma' src/index.ts | head -20

Repository: cosmosgl/graph

Length of output: 215


🏁 Script executed:

# Search for WebGLDevice or device type hints
rg 'WebGLDevice|device.*:' src/index.ts | head -15

Repository: cosmosgl/graph

Length of output: 532


🌐 Web query:

luma.gl WebGLDevice submit() method requirement render pass

💡 Result:

Yes — you must end the active RenderPass before calling device.submit(). The luma.gl docs show the pattern:

const renderPass = device.beginRenderPass();
model.draw();
renderPass.end();
device.submit();

RenderPass.end() must be called after all draw calls to guarantee rendering; submit() then submits that work to the GPU. (luma.gl)

Citations:


Add device.submit() calls after render passes in render() and destroy() methods.

The luma.gl command pipeline requires calling device.submit() after calling renderPass.end() to flush the commands to the GPU. The renderFrame() method correctly follows this pattern with device.submit(), but the render() and destroy() methods omit the submit call after their clear passes. Without it, the GPU rendering commands may not execute.

Required changes
       if (this.device) {
         const clearPass = this.device.beginRenderPass({
           clearColor: this.store.backgroundColor,
           clearDepth: 1,
           clearStencil: 0,
         })
         clearPass.end()
+        this.device.submit()
       }
     if (this.device) {
       // Clears the canvas after particle system is destroyed
       const clearPass = this.device.beginRenderPass({
         clearColor: this.store.backgroundColor,
         clearDepth: 1,
         clearStencil: 0,
       })
       clearPass.end()
+      this.device.submit()
       this.device.destroy()
     }
🤖 Prompt for AI Agents
In `@src/index.ts` around lines 675 - 682, The clear render pass in both the
render() and destroy() methods currently calls device.beginRenderPass(...) and
clearPass.end() but never flushes commands; after the clearPass.end() call in
each method (same pattern as renderFrame()), call this.device.submit() (guarded
by the this.device existence check) so the GPU receives the commands; update the
code paths around device.beginRenderPass / clearPass.end in render() and
destroy() to mirror renderFrame() by invoking this.device.submit() immediately
after ending the pass.

Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
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

🤖 Fix all issues with AI agents
In `@src/index.ts`:
- Around line 1446-1454: validateDevice currently only checks for non-null
HTMLCanvasElement context but doesn't ensure the device is WebGL-capable, which
causes runtime failures when later code calls device.readPixelsToArrayWebGL() or
accesses (device as WebGLDevice).gl; update validateDevice to assert the device
is a WebGLDevice by checking for the WebGL-specific surface/GL properties (e.g.,
presence of readPixelsToArrayWebGL method and a gl property or
canvasContext.type indicating 'html-canvas' with WebGL support) and throw a
clear Error if not; reference validateDevice, WebGLDevice,
readPixelsToArrayWebGL, and any uses of (device as WebGLDevice).gl to locate and
change the guard so external devices are validated at runtime.
♻️ Duplicate comments (6)
src/modules/ForceManyBody/index.ts (2)

64-68: Clamp levels to a non‑negative integer.
Math.log2 can yield fractional or non‑finite values (e.g., 0/negative space sizes), which breaks the loop bounds and the deepest‑level centermass fallback. Please floor and guard it.

Suggested fix
-    this.levels = Math.log2(store.adjustedSpaceSize)
+    const rawLevels = Math.log2(store.adjustedSpaceSize ?? 0)
+    this.levels = Number.isFinite(rawLevels) && rawLevels > 0
+      ? Math.floor(rawLevels)
+      : 0

87-90: Destroy the framebuffer before its attached texture.
Destroying the texture first can leave the framebuffer referencing a freed resource.

Suggested fix
-      if (existingTarget) {
-        if (!existingTarget.texture.destroyed) existingTarget.texture.destroy()
-        if (!existingTarget.fbo.destroyed) existingTarget.fbo.destroy()
-      }
+      if (existingTarget) {
+        if (!existingTarget.fbo.destroyed) existingTarget.fbo.destroy()
+        if (!existingTarget.texture.destroyed) existingTarget.texture.destroy()
+      }
src/index.ts (1)

684-691: Submit after clear passes so commands flush.
Both clear passes end without a device.submit(), so the clear may never be flushed.

Suggested fix
       if (this.device) {
         const clearPass = this.device.beginRenderPass({
           clearColor: this.store.backgroundColor,
           clearDepth: 1,
           clearStencil: 0,
         })
         clearPass.end()
+        this.device.submit()
       }
     if (this.device) {
       const clearPass = this.device.beginRenderPass({
         clearColor: this.store.backgroundColor,
         clearDepth: 1,
         clearStencil: 0,
       })
       clearPass.end()
+      this.device.submit()
       this.device.destroy()
     }

Also applies to: 1324-1332

src/modules/Lines/index.ts (2)

377-391: Guard against zero pointsTextureSize before modulo/division.
If pointsTextureSize is 0/undefined, the index math yields NaN/Infinity and corrupts GPU data.

Suggested fix
   public updatePointsBuffer (): void {
     const { device, data, store } = this
     if (data.linksNumber === undefined || data.links === undefined) return
+    if (!store.pointsTextureSize) return

451-507: Validate link color/width/arrow array lengths before GPU writes.
linkColors, linkWidths, and linkArrows can be mismatched with linksNumber, which can lead to partial writes or out‑of‑bounds GPU uploads. Consider validating and falling back to zero‑filled arrays.

Example fix (apply similarly to width/arrow)
   public updateColor (): void {
     const { device, data } = this
     const linksNumber = data.linksNumber ?? 0
-    const colorData = data.linkColors ?? new Float32Array(linksNumber * 4).fill(0)
+    const expected = linksNumber * 4
+    const colorData =
+      data.linkColors && data.linkColors.length === expected
+        ? data.linkColors
+        : new Float32Array(expected).fill(0)

Also applies to: 515-542

src/modules/Clusters/index.ts (1)

396-401: Gate velocityFbo check on !renderPass for consistency.

Line 401 returns early when velocityFbo is missing/destroyed, but this resource is only used in the fallback branch (lines 427-433) when no external renderPass is provided. This unnecessarily blocks rendering when a valid renderPass is supplied.

🔧 Suggested fix
     if (!this.clusterTexture || this.clusterTexture.destroyed) return
     if (!this.centermassTexture || this.centermassTexture.destroyed) return
     if (!this.clusterPositionsTexture || this.clusterPositionsTexture.destroyed) return
     if (!this.clusterForceCoefficientTexture || this.clusterForceCoefficientTexture.destroyed) return
     if (!this.points?.previousPositionTexture || this.points.previousPositionTexture.destroyed) return
-    if (!this.points?.velocityFbo || this.points.velocityFbo.destroyed) return
+    if (!renderPass && (!this.points?.velocityFbo || this.points.velocityFbo.destroyed)) return
🧹 Nitpick comments (1)
src/modules/Clusters/index.ts (1)

93-184: Consider extracting repetitive texture creation/update logic.

The texture handling pattern (check existence → destroy old → create new → copy data OR just copy data) is repeated for clusterTexture, clusterPositionsTexture, and clusterForceCoefficientTexture. This adds ~90 lines of similar code.

♻️ Example helper to reduce duplication
// Could be added to texture-utils.ts
function updateOrRecreateTexture(
  device: Device,
  texture: Texture | undefined,
  options: { width: number; height: number; format: TextureFormat },
  data: Float32Array,
  shouldRecreate: boolean
): Texture {
  if (!texture || shouldRecreate) {
    if (texture && !texture.destroyed) {
      texture.destroy()
    }
    texture = device.createTexture({
      ...options,
      usage: Texture.SAMPLE | Texture.RENDER | Texture.COPY_DST,
    })
  }
  texture.copyImageData({
    data,
    bytesPerRow: getBytesPerRow(options.format, options.width),
    mipLevel: 0,
    x: 0,
    y: 0,
  })
  return texture
}

Comment on lines +1446 to +1454
private validateDevice (device: Device): NonNullable<Device['canvasContext']> {
const deviceCanvasContext = device.canvasContext
// Cosmos requires an HTMLCanvasElement canvas context.
// OffscreenCanvas and compute-only devices are not supported.
if (deviceCanvasContext === null || deviceCanvasContext.type === 'offscreen-canvas') {
throw new Error('Device must have an HTMLCanvasElement canvas context. OffscreenCanvas and compute-only devices are not supported.')
}
return deviceCanvasContext
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the file structure and read the relevant section
cd /tmp/repo 2>/dev/null || cd - > /dev/null 2>&1
wc -l src/index.ts

Repository: cosmosgl/graph

Length of output: 75


🏁 Script executed:

# Read the validateDevice method and surrounding context
sed -n '1440,1470p' src/index.ts

Repository: cosmosgl/graph

Length of output: 1428


🏁 Script executed:

# Search for WebGLDevice references
rg "WebGLDevice" src/index.ts | head -20

Repository: cosmosgl/graph

Length of output: 360


🏁 Script executed:

# Search for WebGL-specific APIs mentioned in the comment
rg "readPixelsToArrayWebGL|\.gl\." src/index.ts | head -20

Repository: cosmosgl/graph

Length of output: 300


🏁 Script executed:

# Check Device type definition
rg "type Device" src/index.ts -A 5 | head -30

Repository: cosmosgl/graph

Length of output: 40


🏁 Script executed:

# Search for devicePromise usage and where it's created/resolved
rg "devicePromise" src/index.ts -B 2 -A 2 | head -40

Repository: cosmosgl/graph

Length of output: 338


🏁 Script executed:

# Let's look for where validateDevice is called
rg "validateDevice" src/index.ts -B 2 -A 2

Repository: cosmosgl/graph

Length of output: 527


🏁 Script executed:

# Check if Device type is a union and what it could be
rg "from '@luma.gl" src/index.ts | head -20

Repository: cosmosgl/graph

Length of output: 215


🏁 Script executed:

# Search for WebGL API usage more broadly - look for .gl access and WebGL calls
rg "\.gl\." src/index.ts -B 1 -A 1

Repository: cosmosgl/graph

Length of output: 556


🏁 Script executed:

# Check what device types luma.gl supports - look for device creation patterns
rg "createDevice|Device" src/index.ts -B 1 | head -40

Repository: cosmosgl/graph

Length of output: 1564


🏁 Script executed:

# Check if there's any type checking on device parameter
rg "typeof device|instanceof|device as WebGLDevice" src/index.ts

Repository: cosmosgl/graph

Length of output: 300


🏁 Script executed:

# Let's check if there are other WebGL-specific usages throughout the file
rg "WebGLDevice|\.gl\." src/index.ts | wc -l

Repository: cosmosgl/graph

Length of output: 59


🏁 Script executed:

# See full context of how createDevice works
sed -n '1455,1485p' src/index.ts

Repository: cosmosgl/graph

Length of output: 1064


🏁 Script executed:

# Check the type of device passed and if there's a type constraint elsewhere
rg "Device\>" src/index.ts | grep -E "type.*Device|Device.*=" | head -10

Repository: cosmosgl/graph

Length of output: 138


🏁 Script executed:

# Let's verify if WebGLDevice is available for instanceof check at runtime
rg "WebGLDevice" src/index.ts

Repository: cosmosgl/graph

Length of output: 360


🏁 Script executed:

# Look for any render or initialization code that might assume WebGL
rg "render|submit" src/index.ts -B 1 | head -30

Repository: cosmosgl/graph

Length of output: 1136


🏁 Script executed:

# Let's look at the render method to see if there are more WebGL dependencies
sed -n '1750,1850p' src/index.ts

Repository: cosmosgl/graph

Length of output: 3562


🏁 Script executed:

# Check all usages of 'device' variable to see if WebGL operations are common
rg "device\." src/index.ts | grep -v "device\.canvasContext" | head -20

Repository: cosmosgl/graph

Length of output: 550


🏁 Script executed:

# Let's see if there's any documentation or type definition about Device
rg "interface Device|type Device" src/index.ts -A 5

Repository: cosmosgl/graph

Length of output: 40


🏁 Script executed:

# Check if there's any handling of different device types
rg "webgpu|webgl|device" src/index.ts -i | grep -i type | head -20

Repository: cosmosgl/graph

Length of output: 169


🏁 Script executed:

# Let's search for render method implementation
sed -n '1650,1750p' src/index.ts

Repository: cosmosgl/graph

Length of output: 3220


🏁 Script executed:

# Let's check the readPixels function
rg "readPixels" src/index.ts -B 2 -A 2

Repository: cosmosgl/graph

Length of output: 2681


🏁 Script executed:

# Let's check imports for readPixels
rg "import.*readPixels" src/index.ts

Repository: cosmosgl/graph

Length of output: 129


🏁 Script executed:

# Check what type Device actually is and if it's a union or interface
rg "Device.*from" src/index.ts

Repository: cosmosgl/graph

Length of output: 175


🏁 Script executed:

# Let's look at the full initialization code to understand the devicePromise flow
sed -n '1350,1420p' src/index.ts

Repository: cosmosgl/graph

Length of output: 2945


🏁 Script executed:

# Let's check the readPixels helper function to see if it has WebGL dependencies
rg -A 10 "export.*readPixels|function readPixels" src/graph/helper*

Repository: cosmosgl/graph

Length of output: 115


🏁 Script executed:

# Check if there are any type guards or instanceof checks elsewhere
rg "instanceof.*Device|typeof.*device.*webgl" src/index.ts

Repository: cosmosgl/graph

Length of output: 40


🏁 Script executed:

# Let's see what luma.gl Device actually exports and if there's a base Device class
rg "class Device|export.*Device" src/index.ts

Repository: cosmosgl/graph

Length of output: 40


🏁 Script executed:

# Check the complete flow: who can pass devicePromise and what guarantees exist
sed -n '1320,1360p' src/index.ts

Repository: cosmosgl/graph

Length of output: 1320


🏁 Script executed:

# Find the helper file location
fd "helper" src/

Repository: cosmosgl/graph

Length of output: 71


🏁 Script executed:

# Now check readPixels implementation
find src -name "helper*" -type f

Repository: cosmosgl/graph

Length of output: 71


🏁 Script executed:

# List files in src/graph directory
ls -la src/graph/

Repository: cosmosgl/graph

Length of output: 220


🏁 Script executed:

# Check readPixels implementation in helper.ts
rg -A 15 "export.*readPixels|function readPixels" src/helper.ts

Repository: cosmosgl/graph

Length of output: 649


🏁 Script executed:

# Check if readPixels or any WebGL operations are generic or device-specific
cat src/helper.ts | grep -A 20 "readPixels"

Repository: cosmosgl/graph

Length of output: 1897


🏁 Script executed:

# Let's look at all imports in helper.ts to understand dependencies
head -30 src/helper.ts

Repository: cosmosgl/graph

Length of output: 1239


Add runtime check to validate devices are WebGL-capable.

The readPixels helper calls device.readPixelsToArrayWebGL(), and the code accesses (device as WebGLDevice).gl for point size queries. If an external caller passes a non-WebGL device via devicePromise, this will fail at runtime. The internal createDevice() always creates a WebGL device, but external devices bypass this guarantee.

Add a runtime check in validateDevice:

Suggested fix
   private validateDevice (device: Device): NonNullable<Device['canvasContext']> {
     const deviceCanvasContext = device.canvasContext
+    if (!(device instanceof WebGLDevice)) {
+      throw new Error('Device must be a WebGLDevice.')
+    }
     // Cosmos requires an HTMLCanvasElement canvas context.
     // OffscreenCanvas and compute-only devices are not supported.
     if (deviceCanvasContext === null || deviceCanvasContext.type === 'offscreen-canvas') {
       throw new Error('Device must have an HTMLCanvasElement canvas context. OffscreenCanvas and compute-only devices are not supported.')
     }
     return deviceCanvasContext
   }
🤖 Prompt for AI Agents
In `@src/index.ts` around lines 1446 - 1454, validateDevice currently only checks
for non-null HTMLCanvasElement context but doesn't ensure the device is
WebGL-capable, which causes runtime failures when later code calls
device.readPixelsToArrayWebGL() or accesses (device as WebGLDevice).gl; update
validateDevice to assert the device is a WebGLDevice by checking for the
WebGL-specific surface/GL properties (e.g., presence of readPixelsToArrayWebGL
method and a gl property or canvasContext.type indicating 'html-canvas' with
WebGL support) and throw a clear Error if not; reference validateDevice,
WebGLDevice, readPixelsToArrayWebGL, and any uses of (device as WebGLDevice).gl
to locate and change the guard so external devices are validated at runtime.

Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/stories/2. configuration.mdx (1)

82-85: Missing trailing pipe character breaks table formatting.

Line 82 is missing the closing | at the end of the row, which will break the markdown table rendering.

📝 Proposed fix
-| simulationRepulsionFromMouse | Repulsion from the mouse pointer force coefficient. The repulsion force is activated by pressing the right mouse button. | 0.0 – 5.0 | `2.0`
+| simulationRepulsionFromMouse | Repulsion from the mouse pointer force coefficient. The repulsion force is activated by pressing the right mouse button. | 0.0 – 5.0 | `2.0` |
src/index.ts (1)

1266-1276: Add device.submit() after running the simulation step

When step() is called manually outside the render loop, GPU commands from runSimulationStep(true) are queued but never submitted. Module run() methods and points.updatePosition() create and end render passes without calling submit(). Only the frame() method calls device.submit() at the end. For manual step() calls to execute GPU work, add submission:

Fix
  public step (): void {
    if (this._isDestroyed) return
    if (this.ensureDevice(() => this.step())) return

    if (!this.config.enableSimulation) return
    if (!this.store.pointsTextureSize) return

    // Run one simulation step, forcing execution regardless of isSimulationRunning
    this.runSimulationStep(true)
+   this.device?.submit()
  }
🤖 Fix all issues with AI agents
In `@src/index.ts`:
- Around line 33-35: The constructor currently accepts an optional devicePromise
but the class always destroys the resolved Device; add an ownership flag (e.g.,
this.ownsDevice) set true only when Graph creates the device (the path that
constructs/awaits a new Device) and false when a devicePromise is supplied, then
change the initialization callback that currently destroys the Device when
this._isDestroyed is true (referencing device, deviceInitPromise,
this._isDestroyed) to only call device.destroy() if this.ownsDevice is true, and
similarly update the destroy method (the class destroy function) to
conditionally call device.destroy() only when this.ownsDevice is true so
externally supplied/shared devices are not torn down.
♻️ Duplicate comments (2)
src/index.ts (2)

690-697: Clear passes are never submitted (render/destroy)

Line 690-697 and Line 1331-1338 end render passes without device.submit(). luma.gl requires a submit to flush commands; otherwise the clear may not execute. This was raised earlier and still looks unresolved.

✅ Add submit after clear passes
       if (this.device) {
         const clearPass = this.device.beginRenderPass({
           clearColor: this.store.backgroundColor,
           clearDepth: 1,
           clearStencil: 0,
         })
         clearPass.end()
+        this.device.submit()
       }
     if (this.device) {
       const clearPass = this.device.beginRenderPass({
         clearColor: this.store.backgroundColor,
         clearDepth: 1,
         clearStencil: 0,
       })
       clearPass.end()
+      this.device.submit()
       this.device.destroy()
     }

Also applies to: 1331-1339


1452-1459: Guard against non-WebGL devices

Line 1452-1457 only validates the canvas context, but the class later uses WebGL-only APIs (e.g., readPixelsToArrayWebGL, (device as WebGLDevice).gl). If an external non-WebGL Device is passed, this will crash at runtime. This was raised earlier and still looks unresolved.

🛡️ Suggested runtime guard
   private validateDevice (device: Device): NonNullable<Device['canvasContext']> {
     const deviceCanvasContext = device.canvasContext
+    if (!(device instanceof WebGLDevice)) {
+      throw new Error('Device must be a WebGLDevice.')
+    }
     if (deviceCanvasContext === null || deviceCanvasContext.type === 'offscreen-canvas') {
       throw new Error('Device must have an HTMLCanvasElement canvas context. OffscreenCanvas and compute-only devices are not supported.')
     }
     return deviceCanvasContext
   }

Signed-off-by: Stukova Olya <stukova.o@gmail.com>
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/modules/Clusters/index.ts (1)

48-68: Guard pointsTextureSize before allocating arrays/textures.
Line 49 uses pointsTextureSize for array sizes and texture dimensions without checking it’s defined; this can throw (NaN sizes) if create() runs before points are initialized.

🛠️ Proposed fix
  public create (): void {
    const { device, store, data } = this
    const { pointsTextureSize } = store
+   if (!pointsTextureSize) return
    if (data.pointsNumber === undefined || (!data.pointClusters && !data.clusterPositions)) return
🤖 Fix all issues with AI agents
In `@src/index.ts`:
- Line 729: The code calls readPixels(this.device,
this.points.currentPositionFbo) which uses the WebGL-only API
device.readPixelsToArrayWebGL(); update validateDevice() to explicitly assert
the device is a WebGL-capable device (e.g., check for the presence of
readPixelsToArrayWebGL or a WebGL-specific type flag) and throw a clear error if
not, so functions like readPixels and operations referencing
points.currentPositionFbo fail fast with a descriptive message rather than at
runtime.
♻️ Duplicate comments (7)
src/modules/ForceManyBody/index.ts (1)

87-91: Fix destruction order: destroy framebuffer before texture.

Lines 89-90 destroy the texture before the framebuffer, but luma.gl requires framebuffers to be destroyed before their attached textures to avoid use-after-free. The same issue exists at lines 117-118. The destroy() method correctly documents this order.

Proposed fix
       // Destroy old resources if size changed
       if (existingTarget) {
-        if (!existingTarget.texture.destroyed) existingTarget.texture.destroy()
-        if (!existingTarget.fbo.destroyed) existingTarget.fbo.destroy()
+        if (!existingTarget.fbo.destroyed) existingTarget.fbo.destroy()
+        if (!existingTarget.texture.destroyed) existingTarget.texture.destroy()
       }

Also fix lines 117-118:

     for (const [level, target] of Array.from(this.levelTargets.entries())) {
       if (level >= this.levels) {
-        if (!target.texture.destroyed) target.texture.destroy()
-        if (!target.fbo.destroyed) target.fbo.destroy()
+        if (!target.fbo.destroyed) target.fbo.destroy()
+        if (!target.texture.destroyed) target.texture.destroy()
         this.levelTargets.delete(level)
       }
     }
src/index.ts (5)

1452-1460: Insufficient device type validation.

As noted earlier, this validation only checks canvas context type but doesn't verify the device is a WebGLDevice, which is required for WebGL-specific operations used elsewhere (e.g., .gl.getParameter(), readPixelsToArrayWebGL()).


690-697: Missing device.submit() after clear pass.

The luma.gl pipeline requires calling device.submit() after renderPass.end() to flush GPU commands. The renderFrame() method correctly follows this pattern (line 1654), but this clear pass does not, so the clear may not execute.

🔧 Proposed fix
       if (this.device) {
         const clearPass = this.device.beginRenderPass({
           clearColor: this.store.backgroundColor,
           clearDepth: 1,
           clearStencil: 0,
         })
         clearPass.end()
+        this.device.submit()
       }

1331-1340: Missing device.submit() before device.destroy().

Similar to the render() method, the clear pass here ends without calling device.submit(). While the subsequent device.destroy() might implicitly flush pending work, explicitly calling submit() ensures the clear operation completes before destruction.

🔧 Proposed fix
     if (this.device) {
       // Clears the canvas after particle system is destroyed
       const clearPass = this.device.beginRenderPass({
         clearColor: this.store.backgroundColor,
         clearDepth: 1,
         clearStencil: 0,
       })
       clearPass.end()
+      this.device.submit()
       this.device.destroy()
     }

197-198: WebGLDevice cast without runtime validation.

The code casts device to WebGLDevice to access .gl.getParameter(), but validateDevice() only checks for HTMLCanvasElement context, not whether the device is actually a WebGLDevice. If an external caller provides a WebGPU device via devicePromise, this cast will fail at runtime.

🛠️ Add WebGLDevice check in validateDevice
 private validateDevice (device: Device): NonNullable<Device['canvasContext']> {
   const deviceCanvasContext = device.canvasContext
+  if (!(device instanceof WebGLDevice)) {
+    throw new Error('Cosmos requires a WebGLDevice. WebGPU devices are not supported.')
+  }
   // Cosmos requires an HTMLCanvasElement canvas context.
   // OffscreenCanvas and compute-only devices are not supported.
   if (deviceCanvasContext === null || deviceCanvasContext.type === 'offscreen-canvas') {
     throw new Error('Device must have an HTMLCanvasElement canvas context. OffscreenCanvas and compute-only devices are not supported.')
   }
   return deviceCanvasContext
 }

86-104: External device ownership not tracked — may destroy shared devices.

The constructor accepts an optional devicePromise (line 89), but there's no tracking of whether the device was externally supplied. Both the initialization callback (line 102) and destroy() method (line 1339) unconditionally call device.destroy(), which can tear down a shared device the caller still needs.

🛠️ Suggested ownership guard
 class Graph {
   private device: Device | undefined
   private deviceInitPromise: Promise<Device>
+  private ownsDevice = true

   public constructor (
     div: HTMLDivElement,
     config?: GraphConfigInterface,
     devicePromise?: Promise<Device>
   ) {
     if (config) this.config.init(config)

     if (devicePromise) {
       this.deviceInitPromise = devicePromise
+      this.ownsDevice = false
     } else {
       const canvas = document.createElement('canvas')
       this.deviceInitPromise = this.createDevice(canvas)
     }

     this.deviceInitPromise.then(device => {
       if (this._isDestroyed) {
-        device.destroy()
+        if (this.ownsDevice) device.destroy()
         return device
       }
       // ...
     })
   }

Then in destroy():

     if (this.device) {
       const clearPass = this.device.beginRenderPass({ ... })
       clearPass.end()
-      this.device.destroy()
+      if (this.ownsDevice) this.device.destroy()
     }
src/modules/Clusters/index.ts (1)

384-402: Verify size-stability in run().
If pointsTextureSize, clustersTextureSize, or clusterCount can change between create() and run(), consider an early-return guard to avoid using stale textures.

🧹 Nitpick comments (2)
src/modules/ForceMouse/index.ts (1)

92-111: Consider consistent destroyed-state checks in destroy().

The vertexCoordBuffer destruction includes a !this.vertexCoordBuffer.destroyed guard (line 108), but this pattern isn't applied to runCommand or uniformStore. For defensive consistency against double-destroy scenarios, consider applying the same pattern to all resources:

♻️ Suggested consistent pattern
   public destroy (): void {
     // 1. Destroy Models FIRST (they destroy _gpuGeometry if exists, and _uniformStore)
-    this.runCommand?.destroy()
+    if (this.runCommand && !this.runCommand.destroyed) {
+      this.runCommand.destroy()
+    }
     this.runCommand = undefined

     // ...

     // 4. Destroy UniformStores (Models already destroyed their managed uniform buffers)
-    this.uniformStore?.destroy()
+    if (this.uniformStore && !this.uniformStore.destroyed) {
+      this.uniformStore.destroy()
+    }
     this.uniformStore = undefined

     // 5. Destroy Buffers (passed via attributes - NOT owned by Models, must destroy manually)
     if (this.vertexCoordBuffer && !this.vertexCoordBuffer.destroyed) {
       this.vertexCoordBuffer.destroy()
     }
     this.vertexCoordBuffer = undefined
   }
src/modules/ForceManyBody/index.ts (1)

252-254: Consider adding explicit usage flags for consistency.

The forceVertexCoordBuffer is created without explicit usage flags, unlike pointIndices at line 162-165 which specifies usage: Buffer.VERTEX | Buffer.COPY_DST. While luma.gl may use sensible defaults, adding explicit flags improves consistency and clarity.

Suggested change
     this.forceVertexCoordBuffer ||= device.createBuffer({
       data: new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
+      usage: Buffer.VERTEX,
     })

if (!this.device || !this.points || !this.canvasD3Selection) return
const { store: { screenSize } } = this
const positionPixels = readPixels(this.reglInstance, this.points.currentPositionFbo as regl.Framebuffer2D)
const positionPixels = readPixels(this.device, this.points.currentPositionFbo as Framebuffer)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat src/helper.ts | grep -A 10 "export function readPixels"

Repository: cosmosgl/graph

Length of output: 447


🏁 Script executed:

# Check if readPixelsToArrayWebGL is device-specific or universal
rg "readPixelsToArrayWebGL" --type ts

Repository: cosmosgl/graph

Length of output: 318


🏁 Script executed:

# Search for Device type definition and validateDevice function
rg "validateDevice|interface Device|type Device" --type ts -A 3

Repository: cosmosgl/graph

Length of output: 606


🏁 Script executed:

# Get full validateDevice implementation
sed -n '729,850p' src/index.ts | head -50

Repository: cosmosgl/graph

Length of output: 1906


🏁 Script executed:

# Check imports to understand Device type source
head -30 src/index.ts | grep -E "import.*Device|from.*luma"

Repository: cosmosgl/graph

Length of output: 215


🏁 Script executed:

# Search for where device is created or assigned
rg "this\.device\s*=" --type ts -B 2 -A 2

Repository: cosmosgl/graph

Length of output: 479


🏁 Script executed:

# Check if Device type has readPixelsToArrayWebGL method defined
rg "readPixelsToArrayWebGL" --type ts -B 5

Repository: cosmosgl/graph

Length of output: 975


🏁 Script executed:

# Search for device creation and initialization in the codebase
rg "new WebGLDevice|new Device" --type ts -B 2 -A 2

Repository: cosmosgl/graph

Length of output: 40


🏁 Script executed:

# Check if Device interface or type is extended/checked elsewhere
rg "instanceof.*Device|as WebGLDevice|WebGLDevice" --type ts

Repository: cosmosgl/graph

Length of output: 399


🏁 Script executed:

# Get full validateDevice implementation to understand scope of validation
rg "private validateDevice" --type ts -A 20

Repository: cosmosgl/graph

Length of output: 1187


🏁 Script executed:

# Check what the review comment context might be referring to by looking at surrounding code
sed -n '700,750p' src/index.ts

Repository: cosmosgl/graph

Length of output: 2599


readPixels uses WebGL-specific API that should be protected by early device validation.

The readPixels helper calls device.readPixelsToArrayWebGL() (a WebGL-specific method, now deprecated in luma.gl v9). The method is not type-safe on the generic Device type, yet validateDevice() only checks canvasContext and doesn't validate the device is WebGL-capable. Add an explicit WebGL device type check in validateDevice() to ensure this and other WebGL-dependent operations fail fast with a clear error rather than at runtime.

🤖 Prompt for AI Agents
In `@src/index.ts` at line 729, The code calls readPixels(this.device,
this.points.currentPositionFbo) which uses the WebGL-only API
device.readPixelsToArrayWebGL(); update validateDevice() to explicitly assert
the device is a WebGL-capable device (e.g., check for the presence of
readPixelsToArrayWebGL or a WebGL-specific type flag) and throw a clear error if
not, so functions like readPixels and operations referencing
points.currentPositionFbo fail fast with a descriptive message rather than at
runtime.

Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
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

🤖 Fix all issues with AI agents
In `@package.json`:
- Around line 83-85: Update the three luma.gl dependency versions in
package.json to the published 9.1.9 release: replace "@luma.gl/core": "^9.2.5",
"@luma.gl/engine": "^9.2.5", and "@luma.gl/webgl": "^9.2.5" with
"@luma.gl/core": "^9.1.9", "@luma.gl/engine": "^9.1.9", and "@luma.gl/webgl":
"^9.1.9" respectively so npm can resolve the packages; ensure the changes are
applied to the dependency entries that reference these package names.

Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/index.ts (1)

1292-1354: Device cleanup logic is correct, but missing device.submit() before destroy.

The ownership check with shouldDestroyDevice is properly implemented. However, the clear pass at lines 1346-1351 is missing the device.submit() call before device.destroy().

🔧 Proposed fix
       if (this.shouldDestroyDevice) {
         // Clears the canvas after particle system is destroyed
         const clearPass = this.device.beginRenderPass({
           clearColor: this.store.backgroundColor,
           clearDepth: 1,
           clearStencil: 0,
         })
         clearPass.end()
+        this.device.submit()
         this.device.destroy()
       }
🤖 Fix all issues with AI agents
In `@src/stories/beginners/pinned-points/index.ts`:
- Around line 71-76: The destroy function currently destroys the device directly
via devicePromise.then(device => device.destroy()), but must first call
graph.destroy() to let the Graph release GPU resources; update the destroy
function to wait for devicePromise, call graph.destroy() (or graph?.destroy())
before calling device.destroy(), and ensure any promise ordering handles errors
so Graph cleanup runs prior to device.destroy().
♻️ Duplicate comments (2)
src/index.ts (2)

701-708: Missing device.submit() after the clear render pass.

The luma.gl command pipeline requires calling device.submit() after renderPass.end() to flush commands to the GPU. The renderFrame() method at line 1669 correctly follows this pattern, but this clear pass in render() is missing the submit call.

🔧 Proposed fix
       if (this.device) {
         const clearPass = this.device.beginRenderPass({
           clearColor: this.store.backgroundColor,
           clearDepth: 1,
           clearStencil: 0,
         })
         clearPass.end()
+        this.device.submit()
       }

1467-1475: Add runtime check to validate devices are WebGL-capable.

The readPixels helper (in src/helper.ts) calls device.readPixelsToArrayWebGL(), and line 208 casts to WebGLDevice to access .gl. If an external caller passes a non-WebGL device via devicePromise, these will fail at runtime. The internal createDevice() always creates a WebGL device, but external devices bypass this guarantee.

🔧 Suggested fix to add WebGL validation
   private validateDevice (device: Device): NonNullable<Device['canvasContext']> {
     const deviceCanvasContext = device.canvasContext
+    // Cosmos requires a WebGL device for readPixels and GL parameter queries
+    if (!(device instanceof WebGLDevice)) {
+      throw new Error('Device must be a WebGLDevice. WebGPU devices are not currently supported.')
+    }
     // Cosmos requires an HTMLCanvasElement canvas context.
     // OffscreenCanvas and compute-only devices are not supported.
     if (deviceCanvasContext === null || deviceCanvasContext.type === 'offscreen-canvas') {
       throw new Error('Device must have an HTMLCanvasElement canvas context. OffscreenCanvas and compute-only devices are not supported.')
     }
     return deviceCanvasContext
   }

Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
package.json (1)

83-98: Add missing peer dependency @luma.gl/shadertools.

@luma.gl/engine@9.2.6 declares @luma.gl/shadertools@~9.2.0 as a peer dependency, but it is not listed in package.json. Add "@luma.gl/shadertools": "^9.2.6" to dependencies to satisfy the peer requirement and avoid runtime issues.

Signed-off-by: Stukova Olya <stukova.o@gmail.com>
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

🤖 Fix all issues with AI agents
In `@src/index.ts`:
- Around line 1836-1859: findHoveredItem currently skips detection when the
mouse hasn’t moved, which lets hover state stick while the scene animates;
update findHoveredItem to also detect scene movement and bypass the
MIN_MOUSE_MOVEMENT_THRESHOLD in that case by: add a sceneMoved check (e.g.
compare current scene/viewport transform/scale/translation — introduce/compare
this._viewportTransform or this._sceneTransform with a
this._lastCheckedViewportTransform/_lastCheckedSceneTransform), treat sceneMoved
as equivalent to mouseMoved in the early return condition, and after detection
update the last-checked scene transform fields and reset
this._shouldForceHoverDetection as you already do; ensure
hoveredPoint/hoveredLinkIndex get recomputed when sceneMoved so
mouseover/mouseout callbacks fire correctly.

In `@src/modules/Store/index.ts`:
- Around line 201-217: The adjustSpaceSize method uses webglMaxTextureSize
without validating it, which can produce NaN for this.adjustedSpaceSize; add a
guard in adjustSpaceSize to ensure webglMaxTextureSize is a finite number > 0
(e.g., Number.isFinite(webglMaxTextureSize) && webglMaxTextureSize > 0) and if
not, set a safe fallback (use defaultConfigValues.spaceSize or minSpaceSize)
before applying the WebGL-limits branch; update the branch that computes
this.adjustedSpaceSize (and its warning message) to use the validated/fallback
value so scale calculations downstream cannot become NaN.

Comment on lines 1836 to +1859
private findHoveredItem (): void {
if (this._isDestroyed || !this._isMouseOnCanvas || !this.reglInstance) return
if (this._isDestroyed || !this._isMouseOnCanvas) return
if (this._findHoveredItemExecutionCount < MAX_HOVER_DETECTION_DELAY) {
this._findHoveredItemExecutionCount += 1
return
}

// Check if mouse has moved significantly since last hover detection
const deltaX = Math.abs(this._lastMouseX - this._lastCheckedMouseX)
const deltaY = Math.abs(this._lastMouseY - this._lastCheckedMouseY)
const mouseMoved = deltaX > MIN_MOUSE_MOVEMENT_THRESHOLD || deltaY > MIN_MOUSE_MOVEMENT_THRESHOLD

// Skip if mouse hasn't moved AND not forced
if (!mouseMoved && !this._shouldForceHoverDetection) {
return
}

// Update last checked position
this._lastCheckedMouseX = this._lastMouseX
this._lastCheckedMouseY = this._lastMouseY

// Reset force flag after use
this._shouldForceHoverDetection = false

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Hover state can become stale while the scene moves.

With the movement threshold, hover detection is skipped whenever the mouse is stationary, even if points are moving (simulation, zoom animation, right‑click repulsion). This can leave hoveredPoint/hoveredLinkIndex “stuck” and prevent mouseout/mouseover callbacks from firing correctly.

Consider bypassing the threshold when the scene is moving, or forcing hover detection on those frames.

💡 Suggested tweak
-    // Skip if mouse hasn't moved AND not forced
-    if (!mouseMoved && !this._shouldForceHoverDetection) {
+    const isSceneMoving =
+      this.store.isSimulationRunning ||
+      this.zoomInstance.isRunning ||
+      this.isRightClickMouse
+
+    // Skip if mouse hasn't moved AND not forced AND scene is stable
+    if (!mouseMoved && !this._shouldForceHoverDetection && !isSceneMoving) {
       return
     }
🤖 Prompt for AI Agents
In `@src/index.ts` around lines 1836 - 1859, findHoveredItem currently skips
detection when the mouse hasn’t moved, which lets hover state stick while the
scene animates; update findHoveredItem to also detect scene movement and bypass
the MIN_MOUSE_MOVEMENT_THRESHOLD in that case by: add a sceneMoved check (e.g.
compare current scene/viewport transform/scale/translation — introduce/compare
this._viewportTransform or this._sceneTransform with a
this._lastCheckedViewportTransform/_lastCheckedSceneTransform), treat sceneMoved
as equivalent to mouseMoved in the early return condition, and after detection
update the last-checked scene transform fields and reset
this._shouldForceHoverDetection as you already do; ensure
hoveredPoint/hoveredLinkIndex get recomputed when sceneMoved so
mouseover/mouseout callbacks fire correctly.

Signed-off-by: Stukova Olya <stukova.o@gmail.com>
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

🤖 Fix all issues with AI agents
In `@src/modules/Store/index.ts`:
- Around line 201-224: The webglMaxTextureSize handling in adjustSpaceSize can
be smaller than minSpaceSize (2) causing adjustedSpaceSize to exceed the WebGL
limit; update the validation to treat webglMaxTextureSize < minSpaceSize as
invalid and skip the WebGL-limit branch. In adjustSpaceSize, either change the
existing check for webglMaxTextureSize (currently using Number.isFinite(...) ||
<= 0) to require webglMaxTextureSize >= minSpaceSize, or add an explicit branch
that warns when webglMaxTextureSize < minSpaceSize and sets
this.adjustedSpaceSize = configSpaceSize (and returns) so the later clamp using
webglMaxTextureSize never produces a value larger than the WebGL limit.
- Around line 27-32: The Mat4Array type alias is used as the return type of the
public getter transformationMatrix4x4 but is not exported, causing "private name
in public API" errors; fix this by exporting the type alias (make Mat4Array
exported) so the public API references an exported type (ensure the declaration
export applies to the Mat4Array type alias).

…ace size check

Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
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.

4 participants