feat: WebGL shreds chart#373
Conversation
| * "now" is adjusted using an avg diff of server time and real now ts to smooth out server msg delays. | ||
| * We also delay "now" by one data update interval to prevent instability of right-most data. | ||
| */ | ||
| export function getAdjustedNow(serverTimeMs: number, prevTimeDiffs: number[]) { |
There was a problem hiding this comment.
moved from shreds progression plugin
| * Get slots in draw order | ||
| * and max shreds count per slot for scaling | ||
| */ | ||
| export function getDrawInfo( |
There was a problem hiding this comment.
moved from shreds progression plugin
| }; | ||
| }; | ||
|
|
||
| type XRange = { |
There was a problem hiding this comment.
moved to utils.ts for reuse
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/features/Overview/ShredsProgression/WebGl/Chart.tsx
Line: 80-104
Comment:
**Missing WebGL resource cleanup on unmount**
The `THREE.WebGLRenderer` created in `setUpRenderer` (stored in `rendererRef`) is never disposed when the component unmounts. WebGL contexts are a limited browser resource (typically 8–16 per page), and each renderer also holds GPU memory for textures, buffers, and the scene graph (meshes, geometries, materials).
When this component unmounts and remounts (e.g. navigation), a new renderer is created but the old one's context and GPU allocations are leaked. Over time this can exhaust the browser's context limit, causing subsequent `WebGLRenderer` creation to fail silently or throw.
Add a cleanup effect to dispose of the renderer and its resources:
```suggestion
useRafLoop(function drawShredsLoop(time: number) {
if (
lastRedrawRef.current == null ||
time - lastRedrawRef.current >= REDRAW_INTERVAL_MS
) {
lastRedrawRef.current = time;
if (!rendererRef.current) {
const result = setUpRenderer(width, height);
if (!result) return;
rendererRef.current = result;
containerRef.current?.replaceChildren(result.renderer.domElement);
} else {
draw(
chartId,
prevTimeDiffsRef,
rendererRef.current,
visibleTsRangeRef,
labelsRef,
scale,
false,
[0, width],
);
}
}
});
// Dispose WebGL resources on unmount
useLayoutEffect(() => {
return () => {
const obj = rendererRef.current;
if (!obj) return;
obj.renderer.dispose();
for (const slotMesh of obj.meshes.values()) {
slotMesh.mesh.geometry.dispose();
}
for (const slotMesh of obj.availableMeshes) {
slotMesh.mesh.geometry.dispose();
}
rendererRef.current = undefined;
};
}, []);
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/features/Overview/ShredsProgression/WebGl/Chart.tsx
Line: 9
Comment:
**No-op empty type import**
This import doesn't import anything and has no side effects for type-only imports. It should be removed.
```suggestion
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/features/Overview/ShredsProgression/WebGl/chartUtils.ts
Line: 364
Comment:
**Leftover debug code**
This commented-out return statement appears to be leftover from development/debugging and should be removed.
```suggestion
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "feat: WebGL shreds chart" | Re-trigger Greptile |
168ec6a to
f6ddb9f
Compare
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/features/Overview/ShredsProgression/webglUtils.ts
Line: 73-74
Comment:
**Missing `DynamicDrawUsage` on initial buffers**
`ensureCapacity` correctly sets `DynamicDrawUsage` on resized buffers (lines 116-117), but `createSlotMesh` creates the initial attributes with the default `StaticDrawUsage`. Since these buffers are updated every frame via `addRectangleToMesh` + `needsUpdate = true`, they should use `DynamicDrawUsage` from the start so the GPU driver can optimize for frequent updates.
```suggestion
const rectAttr = new THREE.InstancedBufferAttribute(rectArray, 4);
const colorAttr = new THREE.InstancedBufferAttribute(colorArray, 3);
rectAttr.setUsage(THREE.DynamicDrawUsage);
colorAttr.setUsage(THREE.DynamicDrawUsage);
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (2): Last reviewed commit: "feat: WebGL shreds chart" | Re-trigger Greptile |
|
Reviews (3): Last reviewed commit: "chore: fixes" | Re-trigger Greptile |
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/features/Overview/ShredsProgression/atoms.ts
Line: 301-313
Comment:
**In-place Map mutation returns same reference**
`setMinDirtySlotByChartIfSmaller` and the corresponding `store.set` calls in `draw()` mutate the Map in-place and return the same object reference. This works today because all reads go through `store.get()` (imperative reads always see the latest value). However, Jotai v2 uses `Object.is` equality to suppress subscriber notifications — so if any component later subscribes to `minDirtySlotByChartAtom` via `useAtomValue`, it would never see updates since the reference never changes. Consider documenting this constraint or returning a new Map to make the pattern safe for future use.
How can I resolve this? If you propose a fix, please make it concise.Reviews (4): Last reviewed commit: "fix: redraw skipped slots shreds" | Re-trigger Greptile |
|
Reviews (5): Last reviewed commit: "chore: fix package.json, chunk for chart" | Re-trigger Greptile |
|
Reviews (6): Last reviewed commit: "chore: max px ratio, check WebGL support" | Re-trigger Greptile |
cfddbc8 to
80976dc
Compare
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/features/Overview/ShredsProgression/WebGl/chartUtils.ts
Line: 247
Comment:
**`anythingDrawn` is always true after first frame**
After each draw, `minDirtySlotByChartAtom` is reset to `Infinity` (line 234). On the next frame, `minDirtySlot = Infinity`, and `Infinity != null` evaluates to `true`. This means `anythingDrawn` is always true once the chart initializes, so the dirty-slot render skip never triggers.
For a live chart this has no impact because `cameraChanged` is also true every frame (the visible time range shifts with `Date.now()`). But if pause/pan is added later (as the comment on line 84 suggests), this would cause unnecessary `renderer.render()` calls on every frame even when nothing changed.
```suggestion
const anythingDrawn = minDirtySlot != null && isFinite(minDirtySlot);
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/features/Overview/ShredsProgression/webglSupport.ts
Line: 7-14
Comment:
**Probed WebGL2 context not released**
`canvas.getContext("webgl2")` creates a real WebGL2 context that occupies one of the browser's limited context slots (typically 8–16). The canvas is an orphan element so the context will eventually be GC'd, but it may linger until the GC runs. Since this probe runs once at startup it's unlikely to cause issues in practice, but explicitly releasing the context is cheap insurance — especially given the PR already has `forceContextLoss` cleanup on the renderer side.
```suggestion
export function isWebGl2Available(): boolean {
try {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("webgl2");
if (!ctx) return false;
ctx.getExtension("WEBGL_lose_context")?.loseContext();
return true;
} catch {
return false;
}
}
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (7): Last reviewed commit: "chore: max px ratio, check WebGL support" | Re-trigger Greptile |
|
Reviews (8): Last reviewed commit: "fix: anythingDrawn check" | Re-trigger Greptile |
0f03f27
|
Reviews (9): Last reviewed commit: "fix: handle context loss and restore, up..." | Re-trigger Greptile |
0f03f27 to
c92de05
Compare
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/features/Overview/ShredsProgression/WebGl/Chart.tsx
Line: 86-89
Comment:
**WebGL resources leaked on context restore**
`clearTimeout(contextLostTimeoutRef.current)` cancels the timer but does **not** set the ref to `undefined`. When `triggerRemount()` unmounts this component, the cleanup effect (line 105) evaluates `contextLostTimeoutRef.current != null` → `true` (it's still the old timeout ID number), so `isContextLost` is `true` and the entire GPU cleanup block (geometry disposal, `renderer.dispose()`, `forceContextLoss()`) is skipped.
After the context is restored the GL context is live again, so the old renderer holds a valid context that should be released. On repeated context-loss/restore cycles (e.g. repeated tab backgrounding) this will accumulate leaked WebGL contexts until the browser's limit (typically 8–16) is exhausted.
```suggestion
const handleContextRestored = useCallback(() => {
clearTimeout(contextLostTimeoutRef.current);
contextLostTimeoutRef.current = undefined;
triggerRemount();
}, [triggerRemount]);
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (10): Last reviewed commit: "fix: handle context loss and restore, up..." | Re-trigger Greptile |
c92de05 to
8a05898
Compare
|
Reviews (11): Last reviewed commit: "fix: handle context loss and restore, up..." | Re-trigger Greptile |
8a05898 to
b4fd4f4
Compare
|
Reviews (12): Last reviewed commit: "feat: WebGL shreds chart" | Re-trigger Greptile |
No description provided.