Skip to content

Comments

Allow for multiple Lighter instances#6555

Closed
benjaminpkane wants to merge 5 commits intodevelopfrom
cleaning/lighter
Closed

Allow for multiple Lighter instances#6555
benjaminpkane wants to merge 5 commits intodevelopfrom
cleaning/lighter

Conversation

@benjaminpkane
Copy link
Contributor

@benjaminpkane benjaminpkane commented Nov 10, 2025

What changes are proposed in this pull request?

  • Remove ghost looker logic for 2D lighter
  • Make lighter scene atom configurable

TODO

  • Decide on changes/behavior for degraded modal's "Selection" and "Tagging" actions

How is this patch tested? If it is not, please explain why.

Locally

What areas of FiftyOne does this PR affect?

  • App: FiftyOne application changes
  • Build: Build and test infrastructure changes
  • Core: Core fiftyone Python library changes
  • Documentation: FiftyOne documentation changes
  • Other

Summary by CodeRabbit

  • New Features

    • Added render-callback registration to extend lighter rendering.
    • Introduced a reusable canvas for more stable media rendering.
  • Improvements

    • Modal can render without certain viewer references for more reliable UI.
    • Annotation mode hides unrelated actions for a cleaner toolbar.
    • Scene/state handling made atom-configurable and simplified (reduced sceneId/ghost coupling).
  • Bug Fixes

    • Guarded viewer accesses to prevent runtime errors.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 10, 2025

Walkthrough

This pull request decouples Lighter scene state from a hard singleton and relaxes looker assumptions in modal code. Changes: lookerRef props/params made optional and call sites use optional chaining; Modal rendering no longer requires lookerRef.current; lighterSceneAtom renamed to defaultLighterSceneAtom and SceneAtom type added; Lighter hooks (useLighter, useLighterSetupWithPixi) accept an optional atom (defaulting to defaultLighterSceneAtom) and useLighter exposes registerRenderCallback; Scene2D no longer stores/exposes sceneId; LighterSampleRenderer accepts an atom prop (default provided); SharedCanvas singleton removed and replaced by ReusabledCanvas/defaultCanvas; ghost-related selective-render logic removed; modal action rendering hides specific actions during annotate mode.

Sequence Diagram(s)

sequenceDiagram
  actor Component
  participant Hook as useLighter(atom)
  participant Atom as defaultLighterSceneAtom
  participant Scene as Scene2D (scene)
  participant Render as render callbacks

  Component->>Hook: call useLighter(atom?)
  Hook->>Atom: read atom (useAtom)
  Atom-->>Hook: scene (nullable)
  Hook->>Scene: expose scene APIs (addOverlay, undo, redo, ...)
  Component->>Hook: registerRenderCallback(cb)
  alt scene present
    Hook->>Scene: scene.registerRenderCallback(cb)
    Scene->>Render: invoke callbacks on render
    Render-->>Component: callback runs
  else scene missing
    Hook-->>Component: registerRenderCallback no-op (warn)
  end
Loading
sequenceDiagram
  participant Index as ModalEntry (index.tsx)
  participant Modal as Modal component
  participant Looker as lookerRef?.current

  Note over Index,Modal: Old flow required Looker to exist to render Modal
  Index->>Index: if modal && lookerRef.current
  Index-->>Modal: render Modal

  Note over Index,Modal: New flow removes lookerRef guard
  Index->>Index: if modal
  Index-->>Modal: render Modal
  Modal->>Looker: access via lookerRef?.current (optional chaining)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • useLighter / useLighterSetupWithPixi
    • verify registerRenderCallback lifecycle, cleanup, and scene-change behavior
    • confirm optional-atom handling and default atom interactions
  • Scene2D and SceneConfig
    • ensure removal of sceneId does not break identity-dependent logic or ordering
  • Canvas changes
    • review ReusabledCanvas attach/detach semantics vs removed SharedCanvas and WebGL context reuse
  • Modal & lookerRef changes
    • confirm optional chaining covers all prior usages and safe mounting of Modal without lookerRef
  • Modal selective rendering / ghost removal
    • verify effect dependencies and render decisions remain correct
  • Cross-package atom rename
    • ensure all imports updated from lighterSceneAtomdefaultLighterSceneAtom and types aligned

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning PR description is incomplete; missing testing details and release notes sections from the template. Add comprehensive testing methodology, specify release note details, and clarify the TODO completion status for Selection/Tagging actions.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main change—making lighter scene atom configurable to support multiple instances—and is concise and specific.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cleaning/lighter

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

@benjaminpkane benjaminpkane marked this pull request as ready for review November 13, 2025 16:05
@benjaminpkane benjaminpkane requested a review from a team as a code owner November 13, 2025 16:05
Copy link
Contributor

@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

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b4b22a and d18f814.

📒 Files selected for processing (14)
  • app/packages/core/src/components/Actions/Selected/Modal.tsx (3 hunks)
  • app/packages/core/src/components/Actions/Selected/hooks.ts (1 hunks)
  • app/packages/core/src/components/Actions/Selected/index.tsx (1 hunks)
  • app/packages/core/src/components/Modal/Actions/index.tsx (3 hunks)
  • app/packages/core/src/components/Modal/Lighter/LighterSampleRenderer.tsx (1 hunks)
  • app/packages/core/src/components/Modal/ModalLooker.tsx (2 hunks)
  • app/packages/core/src/components/Modal/Sidebar/Annotate/Annotate.tsx (2 hunks)
  • app/packages/core/src/components/Modal/use-modal-selective-rendering.ts (3 hunks)
  • app/packages/lighter/src/core/Scene2D.ts (1 hunks)
  • app/packages/lighter/src/core/SceneConfig.ts (0 hunks)
  • app/packages/lighter/src/react/useLighter.ts (5 hunks)
  • app/packages/lighter/src/react/useLighterSetup.ts (3 hunks)
  • app/packages/lighter/src/state.ts (1 hunks)
  • app/packages/looker-3d/src/fo3d/MediaTypeFo3d.tsx (2 hunks)
💤 Files with no reviewable changes (1)
  • app/packages/lighter/src/core/SceneConfig.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

Files:

  • app/packages/core/src/components/Modal/Sidebar/Annotate/Annotate.tsx
  • app/packages/core/src/components/Actions/Selected/index.tsx
  • app/packages/lighter/src/state.ts
  • app/packages/core/src/components/Modal/Actions/index.tsx
  • app/packages/core/src/components/Modal/use-modal-selective-rendering.ts
  • app/packages/core/src/components/Actions/Selected/hooks.ts
  • app/packages/lighter/src/core/Scene2D.ts
  • app/packages/core/src/components/Modal/ModalLooker.tsx
  • app/packages/looker-3d/src/fo3d/MediaTypeFo3d.tsx
  • app/packages/lighter/src/react/useLighter.ts
  • app/packages/lighter/src/react/useLighterSetup.ts
  • app/packages/core/src/components/Actions/Selected/Modal.tsx
  • app/packages/core/src/components/Modal/Lighter/LighterSampleRenderer.tsx
🧠 Learnings (3)
📚 Learning: 2024-10-22T16:25:08.017Z
Learnt from: minhtuev
Repo: voxel51/fiftyone PR: 4957
File: app/packages/components/src/components/Toast/Toast.tsx:29-36
Timestamp: 2024-10-22T16:25:08.017Z
Learning: When reviewing React components in `app/packages/components/src/components/*`, verify component imports carefully before suggesting missing imports, especially when components are imported via destructuring from packages like `mui/material`.

Applied to files:

  • app/packages/core/src/components/Modal/Sidebar/Annotate/Annotate.tsx
📚 Learning: 2024-07-27T04:14:08.421Z
Learnt from: sashankaryal
Repo: voxel51/fiftyone PR: 4310
File: app/packages/looker-3d/src/fo3d/Gizmos.tsx:110-0
Timestamp: 2024-07-27T04:14:08.421Z
Learning: The grid rendering logic in `Gizmos.tsx` is part of an external component and not directly modifiable within the project's codebase.

Applied to files:

  • app/packages/core/src/components/Actions/Selected/index.tsx
📚 Learning: 2025-04-23T15:22:03.452Z
Learnt from: benjaminpkane
Repo: voxel51/fiftyone PR: 5732
File: app/packages/core/src/components/Filters/use-query-performance-icon.tsx:26-38
Timestamp: 2025-04-23T15:22:03.452Z
Learning: React hooks (including useRecoilValue and other state management hooks) must be called unconditionally at the top level of a component or custom hook. They cannot be placed inside conditional statements, loops, or nested functions, as this violates React's Rules of Hooks and leads to unpredictable behavior.

Applied to files:

  • app/packages/core/src/components/Modal/Actions/index.tsx
🧬 Code graph analysis (9)
app/packages/core/src/components/Modal/Sidebar/Annotate/Annotate.tsx (1)
app/packages/lighter/src/state.ts (1)
  • defaultLighterSceneAtom (11-11)
app/packages/core/src/components/Actions/Selected/index.tsx (2)
app/packages/state/src/recoil/atoms.ts (1)
  • modal (29-55)
app/packages/core/src/components/Modal/Actions/GroupVisibility/GroupVisibility.tsx (1)
  • modal (11-110)
app/packages/core/src/components/Actions/Selected/hooks.ts (2)
app/packages/state/src/recoil/groups.ts (1)
  • isGroup (135-140)
app/packages/state/src/recoil/schema.ts (1)
  • activeLabels (418-467)
app/packages/core/src/components/Modal/ModalLooker.tsx (1)
app/packages/core/src/components/Modal/Lighter/LighterSampleRenderer.tsx (1)
  • LighterSampleRenderer (29-94)
app/packages/looker-3d/src/fo3d/MediaTypeFo3d.tsx (1)
app/packages/lighter/src/state.ts (1)
  • defaultLighterSceneAtom (11-11)
app/packages/lighter/src/react/useLighter.ts (3)
app/packages/lighter/src/state.ts (1)
  • defaultLighterSceneAtom (11-11)
app/packages/lighter/src/core/Scene2D.ts (6)
  • canUndo (1255-1257)
  • canRedo (1263-1265)
  • registerRenderCallback (889-904)
  • RenderCallback (118-122)
  • addOverlay (943-981)
  • redo (1241-1249)
app/packages/lighter/src/commands/UndoRedoManager.ts (3)
  • canUndo (82-84)
  • canRedo (90-92)
  • redo (68-76)
app/packages/lighter/src/react/useLighterSetup.ts (2)
app/packages/lighter/src/state.ts (1)
  • defaultLighterSceneAtom (11-11)
app/packages/lighter/src/event/EventBus.ts (1)
  • EventBus (364-406)
app/packages/core/src/components/Actions/Selected/Modal.tsx (1)
app/packages/looker/src/lookers/video.ts (1)
  • VideoLooker (29-447)
app/packages/core/src/components/Modal/Lighter/LighterSampleRenderer.tsx (3)
app/packages/core/src/components/Modal/Lighter/SharedCanvas.ts (1)
  • singletonCanvas (91-91)
app/packages/lighter/src/react/useLighterSetup.ts (1)
  • useLighterSetupWithPixi (32-96)
app/packages/lighter/src/react/index.ts (1)
  • useLighterSetupWithPixi (7-7)
🔇 Additional comments (10)
app/packages/core/src/components/Actions/Selected/Modal.tsx (1)

28-28: LGTM: Optional lookerRef is properly handled.

The changes correctly make lookerRef optional and apply optional chaining consistently throughout. The existing guard on lines 42-45 already ensures safe access to pause().

Also applies to: 37-37, 48-48

app/packages/core/src/components/Modal/Sidebar/Annotate/Annotate.tsx (1)

2-2: LGTM: Consistent atom rename.

The import and usage correctly reference defaultLighterSceneAtom. The explicit React import (line 8) may be unnecessary depending on your React version but doesn't cause issues.

Also applies to: 8-8, 97-97

app/packages/lighter/src/react/useLighterSetup.ts (2)

30-37: LGTM: Configurable atom enables multiple instances.

The addition of the atom parameter with a default value cleanly enables support for multiple Lighter instances while maintaining backward compatibility. This aligns perfectly with the PR objectives.


43-68: Scene2D sceneId removal verified and correctly implemented.

The verification confirms that Scene2D.ts has been successfully updated: the constructor accepts only config: Scene2DConfig and contains no references to sceneId. The useLighterSetup.ts callsite correctly omits sceneId from the Scene2D initialization.

app/packages/looker-3d/src/fo3d/MediaTypeFo3d.tsx (1)

10-10: LGTM: Consistent atom usage.

The import and usage correctly reference the renamed defaultLighterSceneAtom.

Also applies to: 120-120

app/packages/core/src/components/Actions/Selected/index.tsx (1)

83-91: LGTM: Simplified rendering logic.

Removing the lookerRef.current check aligns with making lookerRef optional in Modal. The Modal component now handles undefined lookerRef safely, so this external check is unnecessary.

app/packages/core/src/components/Modal/ModalLooker.tsx (2)

46-47: LGTM: Ghost-dependent styling removed.

The unconditional 100% width and height simplifies the component. This aligns with the PR objective of removing ghost looker logic.


88-92: LGTM: Cleaner rendering logic without ghost.

The ternary operator clearly separates annotate mode (LighterSampleRenderer) from normal mode (ModalLookerNoTimeline), removing the ghost-based conditional rendering complexity.

app/packages/core/src/components/Modal/use-modal-selective-rendering.ts (1)

14-29: LGTM: Ghost logic removed.

The removal of the ghost parameter and related conditional logic simplifies this hook and aligns with the PR objective of removing ghost looker logic. The function now only depends on looker presence for its early return.

Also applies to: 63-63

app/packages/lighter/src/state.ts (1)

6-11: All references to the old atom name have been successfully updated.

The search found no remaining instances of lighterSceneAtom across TypeScript and TSX files. The rename to defaultLighterSceneAtom is complete, and the type-only import is correct.

Copy link
Contributor

@sashankaryal sashankaryal left a comment

Choose a reason for hiding this comment

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

This still doesn't make it easy to spawn multiple Lighter instances... for instance, I cannot have multiple <LighterSampleRenderer /> in the same view (like I want in, say, 3D annotation view).

Could we instead do atomFamily and we key by (now removed) "sceneId" ${sample?.sample?._id}-${sample?.sample?.last_modified_at?.datetime}?

==
Requesting changes for reason above + ambiguity of "defaultLighterSceneAtom`.

Copy link
Contributor

@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

Caution

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

⚠️ Outside diff range comments (1)
app/packages/core/src/components/Modal/Lighter/LighterSampleRenderer.tsx (1)

20-36: Make atom prop optional in LighterSampleRendererProps interface

The interface declares atom: SceneAtom as required, but ModalLooker.tsx:89 calls <LighterSampleRenderer sample={sample} /> without passing the atom prop. The component implementation provides a default value (atom = defaultLighterSceneAtom), so this should be allowed.

Update the interface to:

export interface LighterSampleRendererProps {
  /** Scene atom; defaults to defaultLighterSceneAtom when omitted */
  atom?: SceneAtom;
  className?: string;
  sample: Sample;
}

This resolves the type-check failure in ModalLooker.tsx and aligns the interface with the actual default behavior.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d18f814 and ef2ea66.

⛔ Files ignored due to path filters (2)
  • app/packages/core/package.json is excluded by !**/*.json
  • app/yarn.lock is excluded by !**/yarn.lock, !**/*.lock, !**/*.lock
📒 Files selected for processing (4)
  • app/packages/core/src/components/Modal/Lighter/LighterSampleRenderer.tsx (4 hunks)
  • app/packages/core/src/components/Modal/Lighter/ReusableCanvas.ts (1 hunks)
  • app/packages/core/src/components/Modal/Lighter/SharedCanvas.ts (0 hunks)
  • app/packages/lighter/src/state.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • app/packages/core/src/components/Modal/Lighter/SharedCanvas.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

Files:

  • app/packages/lighter/src/state.ts
  • app/packages/core/src/components/Modal/Lighter/ReusableCanvas.ts
  • app/packages/core/src/components/Modal/Lighter/LighterSampleRenderer.tsx
🧬 Code graph analysis (2)
app/packages/lighter/src/state.ts (2)
app/__mocks__/recoil.ts (1)
  • atom (55-57)
app/packages/lighter/src/core/Scene2D.ts (1)
  • Scene2D (127-1757)
app/packages/core/src/components/Modal/Lighter/LighterSampleRenderer.tsx (4)
app/packages/lighter/src/state.ts (2)
  • SceneAtom (13-13)
  • defaultLighterSceneAtom (11-11)
app/packages/lighter/src/react/useLighter.ts (1)
  • useLighter (20-156)
app/packages/core/src/components/Modal/Lighter/ReusableCanvas.ts (1)
  • defaultCanvas (65-65)
app/packages/lighter/src/react/useLighterSetup.ts (1)
  • useLighterSetupWithPixi (32-96)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: test / test-python (ubuntu-latest-m, 3.11)
  • GitHub Check: lint / eslint
  • GitHub Check: test / test-python (ubuntu-latest-m, 3.10)
  • GitHub Check: test / test-app
  • GitHub Check: test / test-python (ubuntu-latest-m, 3.12)
  • GitHub Check: test / test-python (ubuntu-latest-m, 3.9)
  • GitHub Check: e2e / test-e2e
  • GitHub Check: build / build
🔇 Additional comments (2)
app/packages/lighter/src/state.ts (1)

6-13: Atom rename and type alias look correct

Docstring, defaultLighterSceneAtom naming, and the SceneAtom alias are consistent and give consumers a clear, typed default atom to override when needed. No changes needed here.

app/packages/core/src/components/Modal/Lighter/LighterSampleRenderer.tsx (1)

95-97: Behavior confirmed: Singleton canvas will reparent between multiple concurrent mounts

The review comment is accurate. ReusableCanvas.ts implements a singleton pattern with explicit reparenting: getCanvas() calls this.canvas.remove() then container.appendChild(), causing the canvas to move between containers if multiple LighterSampleRenderer instances mount simultaneously.

Currently, this is not an active issue:

  • Only one LighterSampleRenderer is actively used (ModalLooker.tsx:89)
  • SidePanel.tsx:396 contains a TODO comment explicitly stating: "/* todo: replace with Lighter once we can have muliples scenes at once"—showing this limitation is already known

However, the architectural constraint is real and will impact the TODO work. You should:

  • Document this single-canvas constraint in ReusableCanvas or component JSDoc
  • When implementing multi-instance support, either create per-instance ReusableCanvas objects or add a canvas manager injection mechanism

Copy link
Contributor

@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

♻️ Duplicate comments (1)
app/packages/core/src/components/Modal/Lighter/ReusableCanvas.ts (1)

12-20: Fix class name typo to match docs and intent

The class and export are still named ReusabledCanvas while the docs and usage intent refer to “ReusableCanvas”. This is confusing and makes searching harder; rename the class and default export to ReusableCanvas for consistency.

-class ReusabledCanvas {
+class ReusableCanvas {
@@
-export default ReusabledCanvas;
-
-export const defaultCanvas = new ReusabledCanvas();
+export default ReusableCanvas;
+
+export const defaultCanvas = new ReusableCanvas();

Note: default imports can keep their local name, but any type references to ReusabledCanvas will need updating.

Also applies to: 63-65

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef2ea66 and ba6951a.

📒 Files selected for processing (1)
  • app/packages/core/src/components/Modal/Lighter/ReusableCanvas.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

Files:

  • app/packages/core/src/components/Modal/Lighter/ReusableCanvas.ts

Copy link
Contributor

@sashankaryal sashankaryal left a comment

Choose a reason for hiding this comment

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

This breaks when running multiple Lighter instances side-by-side. I believe the root issue is defaultCanvas: we’re still effectively assuming a single shared canvas. The number of canvases needs to scale with the number of active scene atoms.

A WeakMap<SceneAtom, HTMLCanvasElement> might be the right model??

Also: if multiple canvases exist, this must switch to WebGPU. We run into #context restriction issues with WebGL quick.

@benjaminpkane benjaminpkane deleted the cleaning/lighter branch January 27, 2026 15:22
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