Skip to content

Avoid page reload when navigating away from viewer #8396

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Apr 8, 2025
Merged

Conversation

philippotto
Copy link
Member

@philippotto philippotto commented Feb 14, 2025

Don't reload the page when navigating away from the wk viewer. Instead everything should carefully be cleaned up. This performance improvement is only enabled for super users for now (in case we find other issues). We can roll it out to the remaining users later.

URL of deployed dev instance (used for testing):

  • https://___.webknossos.xyz

Steps to test:

  • open a dataset/annotation/task, do stuff, navigate to dashboard, and then back to the viewer

TODOs:

  • test with hotswapping tasks

Issues:

  • no issue

(Please delete unneeded items, merge only when none are left open)

  • Updated changelog <-- I'd defer to another PR which enables this for all users

@philippotto philippotto self-assigned this Feb 14, 2025
Copy link
Contributor

coderabbitai bot commented Feb 14, 2025

📝 Walkthrough

Walkthrough

The pull request introduces multiple resource management and cleanup enhancements across the codebase. Several modules now include new destroy and reset methods to better handle lifecycle events. Additionally, import adjustments, method renamings, and minor UI tidy-ups have been made. The changes also include new action creators for state management and improvements to saga control flow with enhanced error handling using null-safe operators.

Changes

File(s) Change Summary
frontend/javascripts/libs/UpdatableTexture.ts, frontend/javascripts/main.tsx, frontend/javascripts/navbar.tsx Added exported function notifyAboutDisposedRenderer in UpdatableTexture, updated saga import/initialization in main.tsx, and removed an extraneous blank line in Navbar.
frontend/javascripts/oxalis/controller/renderer.ts, frontend/javascripts/oxalis/controller/scene_controller.ts, frontend/javascripts/oxalis/controller/scene_controller_provider.ts, frontend/javascripts/oxalis/controller/url_manager.ts, frontend/javascripts/oxalis/viewmodes/arbitrary_controller.tsx, frontend/javascripts/oxalis/viewmodes/plane_controller.tsx Introduced new destroy/cleanup methods (e.g., destroyRenderer, destroy() in SceneController) and added null-safe handling and unsubscribe functions in controller modules.
frontend/javascripts/oxalis/geometries/arbitrary_plane.ts, frontend/javascripts/oxalis/geometries/cube.ts, frontend/javascripts/oxalis/geometries/materials/edge_shader.ts, frontend/javascripts/oxalis/geometries/materials/node_shader.ts, frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts, frontend/javascripts/oxalis/geometries/plane.ts, frontend/javascripts/oxalis/geometries/skeleton.ts Renamed methods (e.g., destroy() to stop()), added new destroy methods, updated property types for material, and included shader cleanup in geometries and materials.
frontend/javascripts/oxalis/model.ts, frontend/javascripts/oxalis/model/actions/actions.ts, frontend/javascripts/oxalis/model/bucket_data_handling/data_cube.ts, frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts, frontend/javascripts/oxalis/model/bucket_data_handling/mappings.ts, frontend/javascripts/oxalis/model/bucket_data_handling/polyhedron_rasterizer.ts, frontend/javascripts/oxalis/model/bucket_data_handling/texture_bucket_manager.ts, frontend/javascripts/oxalis/model/data_layer.ts, frontend/javascripts/oxalis/model/helpers/shader_editor.ts, frontend/javascripts/oxalis/model/reducers/ui_reducer.ts, frontend/javascripts/oxalis/model/sagas/root_saga.ts, frontend/javascripts/oxalis/model/store.ts Added reset/destroy methods for cleaning up layers, buckets, and textures; introduced new action creators (resetStoreAction, cancelSagaAction); refined saga control with race effects; and updated store startup function signature.
frontend/javascripts/oxalis/view/action_bar_view.tsx, frontend/javascripts/oxalis/view/input_catcher.tsx, frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx, frontend/javascripts/oxalis/view/plane_view.ts Added a new CreateAnnotationButton component, simplified cleanup in InputCatcher, and enhanced lifecycle methods with saga cancellation and additional cleanup in layouting and plane view components.

Suggested labels

frontend, performance

Poem

I'm a rabbit, hopping with glee,
Over code trails, setting resources free.
New destroy methods keep my burrow neat,
While sagas race and resets beat.
With sleek changes and a twitch of my nose,
I celebrate our code that forever grows!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@philippotto philippotto mentioned this pull request Mar 14, 2025
8 tasks
@philippotto philippotto changed the title [WIP] Avoid page reload when navigating away from viewer Avoid page reload when navigating away from viewer Mar 14, 2025
@philippotto philippotto marked this pull request as ready for review March 14, 2025 14:23
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: 0

🧹 Nitpick comments (5)
frontend/javascripts/oxalis/model/helpers/shader_editor.ts (1)

16-19: Good addition of the destroy method for resource cleanup.

The new destroy() method properly resets both window.materials and window.managers arrays, which aligns with the PR objective of performing careful state cleanup instead of a full page reload. This should help prevent memory leaks by removing references to materials and managers when navigating away from the viewer.

For consistency with the initialization pattern used in the addBucketManagers and addMaterial methods, consider adding null checks:

  destroy() {
-   window.materials = [];
-   window.managers = [];
+   window.materials = window.materials || [];
+   window.materials.length = 0;
+   window.managers = window.managers || [];
+   window.managers.length = 0;
  },

This ensures the properties exist before attempting to reset them, matching the defensive programming style used elsewhere in the file.

frontend/javascripts/oxalis/geometries/plane.ts (1)

202-204: Add null check before calling destroy on materialFactory.

The new destroy() method properly calls destroy() on the materialFactory, helping with resource cleanup. However, if materialFactory is not initialized for any reason (for example, if createMeshes() wasn't called or failed), this could result in a runtime error.

For more robust error handling, consider adding a null check:

  destroy() {
-   this.materialFactory.destroy();
+   if (this.materialFactory) {
+     this.materialFactory.destroy();
+   }
  }

This ensures the code is defensive against potential null/undefined values, preventing exceptions when cleaning up resources.

frontend/javascripts/oxalis/model/bucket_data_handling/texture_bucket_manager.ts (1)

345-354: Comprehensive cleanup in the destroy method.

The destroy method properly cleans up resources by disposing textures and resetting internal state. However, there are a couple of improvements to consider:

  1. The use of @ts-ignore suggests a type issue when setting lookUpCuckooTable to null.
  2. Consider adding a check to prevent multiple calls to destroy.
 destroy() {
+  if (this.isDestroyed) {
+    return;
+  }
   for (const texture of this.getTextures()) {
     texture.dispose();
   }
   this.dataTextures = [];
-  // @ts-ignore
-  this.lookUpCuckooTable = null;
+  this.lookUpCuckooTable = null as unknown as CuckooTableVec5;
   this.isDestroyed = true;
   this.activeBucketToIndexMap = new Map();
 }
frontend/javascripts/oxalis/geometries/cube.ts (1)

215-220: Proper cleanup in destroy method.

The destroy method correctly calls all unsubscribe functions and resets the array. However, it would be good to add disposal of Three.js resources.

Consider disposing of the Three.js geometries and materials in the destroy method:

 destroy() {
   for (const fn of this.storePropertyUnsubscribers) {
     fn();
   }
   this.storePropertyUnsubscribers = [];
+  
+  // Dispose Three.js resources
+  this.cube.geometry.dispose();
+  (this.cube.material as THREE.Material).dispose();
+  
+  for (const plane of Object.values(this.crossSections)) {
+    plane.geometry.dispose();
+    (plane.material as THREE.Material).dispose();
+  }
 }
frontend/javascripts/oxalis/view/action_bar_view.tsx (1)

143-192: Improved annotation creation with hooks-based component.

The new CreateAnnotationButton component refactors the annotation creation logic into a modern functional component using React hooks. This improves code organization by:

  1. Using useHistory and useSelector hooks for better state management
  2. Properly handling segmentation layer fallback support
  3. Processing mapping information
  4. Managing authentication requirements

This refactoring enhances maintainability without changing the core functionality.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6ef81ea and b99ef28.

📒 Files selected for processing (32)
  • frontend/javascripts/libs/UpdatableTexture.ts (1 hunks)
  • frontend/javascripts/main.tsx (2 hunks)
  • frontend/javascripts/navbar.tsx (0 hunks)
  • frontend/javascripts/oxalis/controller/renderer.ts (1 hunks)
  • frontend/javascripts/oxalis/controller/scene_controller.ts (4 hunks)
  • frontend/javascripts/oxalis/controller/scene_controller_provider.ts (1 hunks)
  • frontend/javascripts/oxalis/controller/url_manager.ts (2 hunks)
  • frontend/javascripts/oxalis/controller/viewmodes/arbitrary_controller.tsx (1 hunks)
  • frontend/javascripts/oxalis/controller/viewmodes/plane_controller.tsx (2 hunks)
  • frontend/javascripts/oxalis/geometries/arbitrary_plane.ts (2 hunks)
  • frontend/javascripts/oxalis/geometries/cube.ts (3 hunks)
  • frontend/javascripts/oxalis/geometries/materials/edge_shader.ts (3 hunks)
  • frontend/javascripts/oxalis/geometries/materials/node_shader.ts (4 hunks)
  • frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts (3 hunks)
  • frontend/javascripts/oxalis/geometries/plane.ts (1 hunks)
  • frontend/javascripts/oxalis/geometries/skeleton.ts (4 hunks)
  • frontend/javascripts/oxalis/model.ts (1 hunks)
  • frontend/javascripts/oxalis/model/actions/actions.ts (2 hunks)
  • frontend/javascripts/oxalis/model/bucket_data_handling/data_cube.ts (3 hunks)
  • frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts (2 hunks)
  • frontend/javascripts/oxalis/model/bucket_data_handling/mappings.ts (3 hunks)
  • frontend/javascripts/oxalis/model/bucket_data_handling/polyhedron_rasterizer.ts (0 hunks)
  • frontend/javascripts/oxalis/model/bucket_data_handling/texture_bucket_manager.ts (3 hunks)
  • frontend/javascripts/oxalis/model/data_layer.ts (1 hunks)
  • frontend/javascripts/oxalis/model/helpers/shader_editor.ts (1 hunks)
  • frontend/javascripts/oxalis/model/reducers/ui_reducer.ts (2 hunks)
  • frontend/javascripts/oxalis/model/sagas/root_saga.ts (1 hunks)
  • frontend/javascripts/oxalis/store.ts (1 hunks)
  • frontend/javascripts/oxalis/view/action_bar_view.tsx (3 hunks)
  • frontend/javascripts/oxalis/view/input_catcher.tsx (1 hunks)
  • frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx (3 hunks)
  • frontend/javascripts/oxalis/view/plane_view.ts (2 hunks)
💤 Files with no reviewable changes (2)
  • frontend/javascripts/navbar.tsx
  • frontend/javascripts/oxalis/model/bucket_data_handling/polyhedron_rasterizer.ts
🧰 Additional context used
🧠 Learnings (1)
frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts (1)
Learnt from: dieknolle3333
PR: scalableminds/webknossos#8168
File: frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts:568-585
Timestamp: 2025-03-12T13:04:06.063Z
Learning: In the file `frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts`, the uniform name `allResolutions` should remain unchanged to maintain consistency with the shader code.
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: circleci_build
🔇 Additional comments (64)
frontend/javascripts/oxalis/model.ts (1)

357-366: Well-implemented reset method with proper null checks.

The new reset() method effectively destroys all layers and resets the dataLayers object. The implementation correctly:

  1. Checks if this.dataLayers exists before operating on it
  2. Uses _.values() to convert object values to an array for iteration
  3. Calls destroy() on each layer to ensure proper cleanup
  4. Resets dataLayers to an empty object after cleanup

This approach aligns perfectly with the PR objective of performing careful state cleanup instead of relying on a full page reload, which should improve performance when navigating away from the viewer.

frontend/javascripts/oxalis/controller/viewmodes/arbitrary_controller.tsx (1)

382-382:

✅ Verification successful

Changed method from destroy() to stop() on plane object.

The line was modified to call this.plane.stop() instead of the previous this.plane.destroy(). This indicates a change in the cleanup approach from complete destruction to a potentially stateful suspension.

Verify that:

  1. The ArbitraryPlane class has a properly implemented stop() method
  2. The stop() method performs appropriate resource cleanup

This change seems aligned with the PR's goal of carefully managing resource cleanup rather than forcing a full reset, but it's important to ensure the stop() method properly handles all necessary cleanup operations.


🏁 Script executed:

#!/bin/bash
# Check if ArbitraryPlane has a stop method and what it does
echo "Checking for stop method in ArbitraryPlane class:"
rg -A 10 "stop\(\)" frontend/javascripts/oxalis/geometries/arbitrary_plane.ts

Length of output: 588


Verification: ArbitraryPlane stop() method performs expected cleanup

The review comment has been verified. The ArbitraryPlane class now implements a stop() method that calls both this.stopStoreListening() and this.materialFactory.stopListening(), which aligns with the intended change from a complete destruction approach to a stateful suspension. Please ensure that the internal implementations of stopStoreListening() and materialFactory.stopListening() indeed free up all necessary resources.

  • Verified that the stop() method exists and calls the expected functions.
  • The change is consistent with the PR’s goals for careful resource management.
frontend/javascripts/libs/UpdatableTexture.ts (1)

120-122: Good addition for renderer cleanup

This function allows external components to reset the originalTexSubImage2D variable when a renderer is disposed, preventing potential memory leaks or stale WebGL context references.

frontend/javascripts/oxalis/model/data_layer.ts (1)

74-77: Improved resource cleanup in destroy method

Adding explicit destruction of the cube and mappings instances improves resource management and helps prevent memory leaks when navigating away from the viewer.

frontend/javascripts/oxalis/store.ts (1)

657-659: Good function rename for clarity

Renaming from startSagas to startSaga better reflects that the function launches a single saga, improving API clarity and consistency.

frontend/javascripts/oxalis/controller/renderer.ts (2)

1-1: Good import for coordinating cleanup

Importing the notifyAboutDisposedRenderer function enables proper coordination between renderer disposal and texture management.


8-15: Well-implemented renderer cleanup function

The destroyRenderer function properly handles renderer disposal with appropriate null checks. This is a critical component for preventing memory leaks when navigating away from the viewer without page reloads.

frontend/javascripts/oxalis/model/reducers/ui_reducer.ts (2)

15-25: Clean implementation of state reset while preserving essential data.

The new "RESET_STORE" case appropriately handles state reset by using defaultState as a base while preserving user-specific data (activeUser and activeOrganization) and layout preferences (storedLayouts). This aligns well with the PR's objective of performing careful cleanup when navigating away from the viewer instead of a full page reload.


2-2: Good addition of defaultState import.

This import is necessary for the new RESET_STORE reducer case, as it provides the clean slate for resetting the application state.

frontend/javascripts/oxalis/controller/url_manager.ts (3)

96-96: Proper resource management with optional unsubscribe function.

Adding this property to store the unsubscribe function is a good practice for resource cleanup.


236-236: Enhanced subscription management by storing the unsubscribe function.

Storing the return value from Store.subscribe() in this.stopStoreListening enables proper cleanup later, which is essential for preventing memory leaks when navigating away from the viewer.


241-246: Well-implemented cleanup method for URL management.

This method properly handles the cleanup of store subscription and hash change listener. The null check before calling stopStoreListening is a good defensive programming practice.

frontend/javascripts/oxalis/geometries/arbitrary_plane.ts (2)

42-45: Clear separation of listener cleanup in renamed method.

Renaming the original destroy method to stop() and focusing it on stopping listeners improves separation of concerns in resource management.


173-175: Proper implementation of resource cleanup in new destroy method.

The new destroy() method correctly delegates to materialFactory.destroy(), completing the separation between stopping listeners and cleaning up resources.

frontend/javascripts/oxalis/view/input_catcher.tsx (1)

134-134: Simplified cleanup logic in effect hook.

The cleanup function has been simplified by directly deleting the viewport entry from renderedInputCatchers without checking if domElementRef.current exists. This is safe since deleting a key from a Map works even if the key doesn't exist.

frontend/javascripts/oxalis/controller/viewmodes/plane_controller.tsx (2)

24-26: Good addition of null-safe access.

Adding the import for getSceneControllerOrNull is a good practice to handle edge cases when the scene controller may not be available.


591-592: Nice defensive programming approach.

Using getSceneControllerOrNull()?.stopPlaneMode() with optional chaining instead of direct access prevents potential runtime errors when navigating away. This is essential for enabling smooth navigation without page reloads.

frontend/javascripts/oxalis/model/bucket_data_handling/data_cube.ts (3)

89-89: Good addition of cleanup tracking.

Adding the storePropertyUnsubscribers array is a good practice for tracking listeners that need to be cleaned up.


135-145: Proper listener tracking implementation.

Storing the unsubscribe function in the array allows for proper cleanup later, preventing memory leaks and unwanted behavior when navigating away from the viewer.


975-980: Essential cleanup implementation.

The destroy method properly cleans up resources by:

  1. Clearing the cubes and buckets collections
  2. Calling all unsubscribe functions
  3. Resetting the unsubscribers array

This is crucial for allowing navigation without page reloads and preventing memory leaks.

frontend/javascripts/oxalis/view/plane_view.ts (2)

9-11: Good addition of null-safe access.

Adding the import for getSceneControllerOrNull improves error handling, consistent with changes in other files.


224-229: Improved error resilience in cleanup.

Using a null check with getSceneControllerOrNull() before removing cameras prevents errors when stopping the plane view. This defensive approach is important during cleanup phases when dependencies might already be in the process of being destroyed.

frontend/javascripts/main.tsx (2)

3-4: Streamlined saga management.

Replacing the root saga import with a specific saga function indicates a move toward more modular and targeted saga management.


39-39: Targeted saga initialization.

Changed from initializing a root saga to specifically starting only the email verification saga. This could improve cleanup capabilities when navigating away from the viewer.

frontend/javascripts/oxalis/model/bucket_data_handling/texture_bucket_manager.ts (2)

93-93: Good addition of tracking destroyed state.

Adding the isDestroyed flag is a good practice for managing the lifecycle of this component and preventing operations on a destroyed instance.


186-189: Prevents animation frame loop from continuing after destruction.

This check appropriately stops the animation frame loop when the instance is destroyed, preventing memory leaks and unnecessary processing.

frontend/javascripts/oxalis/model/actions/actions.ts (3)

38-39: Appropriate action type additions.

Adding these new action types to the Action union is necessary to maintain type safety in the Redux store.


47-50: Well-defined resetStoreAction creator.

The resetStoreAction follows the established pattern for Redux action creators in this codebase.


62-65: Well-defined cancelSagaAction creator.

The cancelSagaAction follows the established pattern for Redux action creators in this codebase.

frontend/javascripts/oxalis/geometries/cube.ts (2)

37-37: Good addition for tracking unsubscribers.

Adding this array to store unsubscribe functions is a good pattern for managing cleanup of store listeners.


62-67: Improved store subscriber management.

Pushing the unsubscribe function into the array instead of using it directly improves resource management.

frontend/javascripts/oxalis/controller/scene_controller.ts (5)

19-19: Important import for renderer destruction.

Adding the destroyRenderer import is essential for proper cleanup of WebGL resources.


79-79: Good addition for tracking unsubscribers.

Adding and initializing the storePropertyUnsubscribers array is a good pattern for managing cleanup of store listeners.

Also applies to: 93-93


537-572: Comprehensive scene controller cleanup.

The destroy method thoroughly cleans up resources including:

  1. Window debugging methods
  2. Skeletons
  3. Store property listeners
  4. WebGL renderer
  5. Scene objects (cubes and planes)

This is critical for preventing memory leaks when navigating away from the viewer.

Two minor suggestions:

  1. Consider disposing of Three.js materials and geometries
  2. Add a check to prevent multiple calls to destroy

558-560: Proper renderer cleanup.

Calling destroyRenderer() and nullifying the renderer reference is essential for WebGL resource cleanup.


575-610: Improved store subscriber management.

Consolidating all the store property listeners into a single array makes cleanup much more maintainable. This implementation ensures all listeners are properly tracked and can be unsubscribed when the component is destroyed.

frontend/javascripts/oxalis/controller/scene_controller_provider.ts (2)

12-14: Good addition of safe access to the sceneController.

Adding a null-safe way to access the scene controller enhances error handling. This helps prevent runtime exceptions when trying to access the scene controller that might not be initialized yet.


20-25: Nice addition of proper cleanup method.

This new destroy method properly cleans up resources by checking if the scene controller exists before calling destroy and then setting it to null. This is an important part of the PR's goal to "perform careful cleanup of the current state" when navigating away from the viewer.

frontend/javascripts/oxalis/model/bucket_data_handling/mappings.ts (3)

64-64: Good addition of subscription tracking.

Adding an array to track store property unsubscribers is a good practice for managing subscriptions in components with lifecycle events.


80-89: Improved subscription management.

Storing the unsubscribe function in an array rather than calling it directly allows for proper cleanup when the component is destroyed, preventing potential memory leaks from lingering subscriptions.


158-161: Good implementation of resource cleanup.

The destroy method properly cleans up by calling all unsubscribe functions and resetting the array. This ensures that all subscriptions are properly removed when navigating away from the viewer.

frontend/javascripts/oxalis/geometries/materials/node_shader.ts (4)

32-32: Good addition of subscription tracking.

Similar to the Mappings class, adding an array to track store property unsubscribers is a good practice for managing subscriptions in the NodeShader class.


98-121: Improved subscription management for store listeners.

The refactoring to store unsubscribe functions in an array allows for proper cleanup when the component is destroyed, which is essential for navigating away from the viewer without memory leaks.


132-156: Clean implementation of transform listener.

The transformation listener is now properly managed with the same subscription pattern as the other listeners, ensuring it will be properly cleaned up on destroy.


431-437: Well-implemented destroy method.

The destroy method properly cleans up resources by:

  1. Calling all unsubscribe functions
  2. Resetting the subscriptions array
  3. Nullifying the treeColors reference

This is crucial for proper memory management when navigating away from the viewer.

frontend/javascripts/oxalis/geometries/skeleton.ts (4)

117-118: Good property additions for shader management.

Adding explicit properties to hold the shader instances improves code clarity and maintainability, while also enabling proper shader cleanup.


142-154: Enhanced destroy method with comprehensive cleanup.

The enhanced destroy method now properly:

  1. Cleans up the store subscription
  2. Disposes of texture resources
  3. Disposes of materials
  4. Calls destroy on the shader instances

This thorough cleanup is essential for preventing memory leaks when navigating away from the viewer.


173-174: Good refactoring to store shader instances.

Storing the NodeShader and EdgeShader instances in class properties enables proper cleanup in the destroy method. This change aligns with the goal of careful state cleanup when navigating away.


190-199: Refactored to use shader instances consistently.

The code now consistently uses the stored shader instances to get materials for buffer initialization, making the code more maintainable and consistent.

frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx (4)

14-23: Comprehensive cleanup imports added to support navigation without page reload.

These new imports enable the component to properly clean up resources when unmounting, which is essential for the feature that prevents page reloads when navigating away from the viewer.


81-81: New feature flag to control reload behavior.

This flag controls whether a full page reload occurs when exiting the viewer. Setting it to false enables the new behavior where navigation doesn't trigger a page reload.


117-119: Initialize component with root saga.

Initializing the saga when the component mounts ensures that all the necessary background tasks are started properly with the component lifecycle.


121-130: Enhanced cleanup process when unmounting.

This improved unmount method properly cleans up resources by:

  1. Stopping the URL updater
  2. Resetting the Model
  3. Destroying the scene controller
  4. Resetting the Redux store
  5. Canceling any running sagas
  6. Only reloading the page if explicitly required

This aligns perfectly with the PR objective of avoiding page reloads when navigating away from the viewer.

frontend/javascripts/oxalis/view/action_bar_view.tsx (1)

242-242: Clean implementation of the refactored button.

The render method is now much simpler, delegating all the annotation creation logic to the specialized component.

frontend/javascripts/oxalis/model/sagas/root_saga.ts (2)

27-27: Improved return type for root saga.

Changed from Saga<never> to Saga<void> for better type clarity.


31-42: Enhanced saga flow control with cancellation support.

This implementation adds proper saga cancellation support, which is crucial for the feature that avoids page reloads when navigating away from the viewer. The changes:

  1. Use race to concurrently listen for either restart or cancel actions
  2. Handle UI state properly based on the action received
  3. Allow for complete termination of the saga when canceling
  4. Enable clean navigation without resource leaks

This is a key improvement for resource management when transitioning between views.

frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts (2)

43-45: Simplified memoization implementation.

The memoization of asyncBucketPick is now more concisely formatted for better readability.


284-291: Comprehensive resource cleanup in destroy method.

The enhanced destroy method now properly cleans up all associated resources:

  1. Destroys the texture bucket manager
  2. Clears the shared lookup table cache
  3. Clears the async bucket picker memoization cache
  4. Destroys the shader editor
  5. Cleans up the cuckoo table reference

This thorough cleanup prevents memory leaks and ensures resources are properly released when navigating away from the viewer, which is essential for the no-reload navigation feature.

frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts (3)

110-110: Good update to material property type.

The property type now correctly reflects that material can be null or undefined during initialization or after destruction, improving type safety.


975-977: Well-implemented null check for defensive programming.

Adding an explicit check for null material helps prevent obscure errors when trying to access the material after it has been destroyed, and provides a clear error message instead.


1161-1173: Good resource cleanup implementation.

The destroy method properly cleans up resources by:

  1. Unsubscribing from store listeners
  2. Unsubscribing from color and mapping seeds
  3. Nullifying the material reference
  4. Canceling any pending shader recomputations

This aligns with the PR objective of careful state cleanup when navigating away from the viewer.

frontend/javascripts/oxalis/geometries/materials/edge_shader.ts (3)

21-21: Good addition of store property unsubscribers array.

Adding a dedicated array to track unsubscribe functions is a good pattern for resource management.


62-96: Well-structured listener registration with cleanup support.

Refactoring the store property listeners to store their unsubscribe functions in an array makes cleanup more manageable and helps prevent memory leaks.


207-213: Good implementation of resource cleanup.

The destroy method properly:

  1. Calls all unsubscribe functions to clean up listeners
  2. Resets the unsubscribers array
  3. Nullifies the tree colors texture reference

This complements the PR's goal of performing careful cleanup when navigating away from the viewer.

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

🧹 Nitpick comments (1)
frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx (1)

119-137: Remove leftover “todop” comment and finalize cleanup strategy.
The extensive unmount logic properly resets the model, controller, and Redux store. However, there are a couple of concerns:

  1. The comment on line 126 (“todop: remove before merging”) is likely a temporary placeholder.
  2. The hard-coded ENFORCE_FOR_TESTING = true might be better managed through an environment variable or dedicated config flag to avoid accidental production toggles.

Below is a minimal fix to remove the leftover comment and replace the hardcoded testing variable with an environment-based approach:

-    // todop: remove before merging
-    const ENFORCE_FOR_TESTING = true;
+    const ENFORCE_FOR_TESTING =
+      process.env.REACT_APP_ENFORCE_FOR_TESTING === "true";
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b99ef28 and 08d121d.

📒 Files selected for processing (1)
  • frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: circleci_build
🔇 Additional comments (2)
frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx (2)

14-23: Imports look well-organized, but watch out for consistent usage.
These newly added imports for saga management (rootSaga, startSaga, cancelSagaAction, resetStoreAction) and scene handling (destroySceneController, Model.reset(), UrlManager) appear consistent with the updated lifecycle methods. Ensure that these imports are only used where necessary and verify that the codebase remains free from potential circular dependencies or partial integration.


115-117:

✅ Verification successful

Ensure correct saga lifecycle management.
Starting the saga in componentDidMount() is fine, as long as you confirm that the saga is effectively canceled in componentWillUnmount(). If future code adds more concurrency or asynchronous flows, watch for any race conditions or memory leaks.

Consider verifying that the saga is fully stopped upon unmount with a script:


🏁 Script executed:

#!/bin/bash
# Search for all references to `cancelSagaAction()` to ensure they match usage in the unmount lifecycle.
rg -A 3 'cancelSagaAction'

Length of output: 1558


Saga Cancellation Confirmed on Unmount.

The search results show that cancelSagaAction() is indeed referenced in the tracing_layout_view.tsx file. This confirms that the saga started in componentDidMount() is being explicitly canceled, likely in the componentWillUnmount() lifecycle method (albeit marked with a temporary note). Please ensure any temporary removal messages such as “todop: remove before merging” are resolved prior to finalizing the PR.

  • Confirm that Store.dispatch(cancelSagaAction()) is executed on unmount.
  • Remove any temporary or testing flags before merging.

@MichaelBuessemeyer
Copy link
Contributor

MichaelBuessemeyer commented Mar 20, 2025

I just noticed a small visual bug in the time tracking button in the navbar for non-Admin users. Could you maybe please neak a missing whitespace into this PR? Here is a screenshot of whats visible when logged in as sample2 user:
image

Copy link
Contributor

@MichaelBuessemeyer MichaelBuessemeyer left a comment

Choose a reason for hiding this comment

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

This is very awesome 🚀

Not having to reload is such an awesome improvement :D

I found minor suggestions regarding the code and during testing I noticed:

  • When getting a new task in the annotation view, once the new task is loaded the modal telling the user things about the task and what to does is not present / does not open up. It only did on the initial task.

I didn't find anything else during testing 🎉 Well done!

Comment on lines +591 to +592
getSceneControllerOrNull()?.stopPlaneMode();

Copy link
Contributor

Choose a reason for hiding this comment

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

Under what circumstances can it be null here? This code reads like it is subject to race conditions 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

I added this comment:

    // SceneController might already be null, because componentWillUnmount will trigger
    // earlier for outer components and later for inner components. The outer
    // component TracingLayoutView is responsible for the destroy call which already
    // happened when the stop method here is called.

it's not ideal, but I couldn't come up with another simple solution.

Store.dispatch(resetStoreAction());
Store.dispatch(cancelSagaAction());

// todop: remove before merging
Copy link
Contributor

Choose a reason for hiding this comment

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

Here is an open TODO :)

Comment on lines +120 to +124
UrlManager.stopUrlUpdater();
Model.reset();
destroySceneController();
Store.dispatch(resetStoreAction());
Store.dispatch(cancelSagaAction());
Copy link
Contributor

Choose a reason for hiding this comment

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

Cant this be neglected if the page is reloaded anyway? So only do this function calls in case of an active super user 🤔?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, in theory it's not necessary, but I don't trust the browser to perform location.reload() in an atomic matter. for example, the user could hit escape during reload and I'm not sure that everything will just work then. so, I'd rather keep the potentially unnecessary clean up here :)

@MichaelBuessemeyer
Copy link
Contributor

Hmmm 🤔

When getting a new task in the annotation view, once the new task is loaded the modal telling the user things about the task and what to does is not present / does not open up. It only did on the initial task.

The same behaviour seems to happen on master. Is this maybe due to always having the same task type? 🤔 And is this intended behaviour?

@philippotto
Copy link
Member Author

Hmmm 🤔

When getting a new task in the annotation view, once the new task is loaded the modal telling the user things about the task and what to does is not present / does not open up. It only did on the initial task.

The same behaviour seems to happen on master. Is this maybe due to always having the same task type? 🤔 And is this intended behaviour?

yes, this is by design if the task type did not change.

@philippotto
Copy link
Member Author

thanks for your feedback, @MichaelBuessemeyer! please have another look. if you give your "go", I'll remove the last todo that is meant for testing :)

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

🧹 Nitpick comments (5)
frontend/javascripts/components/error_boundary.tsx (4)

35-35: Consider using React-Redux hooks instead of direct store access.

Direct access to the Redux store with Store.getState() is generally not recommended. Instead, consider using React-Redux's useSelector hook or the connect HOC for more maintainable and testable code.

-import { Store } from "oxalis/singletons";
+import { useSelector } from "react-redux";
 import React, { type ErrorInfo } from "react";
 import { getAntdTheme, getThemeFromUser } from "theme";

Then refactor the class component to a functional component or use a connected component approach.


38-38: Add error handling for theme retrieval.

If getThemeFromUser or getAntdTheme throws an error, it could lead to a cascade of errors within the error boundary itself. Consider adding a try-catch block to gracefully handle theme retrieval failures.

  render() {
    if (this.state.error != null) {
      const { error, info } = this.state;
      const componentStack = info?.componentStack;
      const errorMessage = (error || "").toString();
      const errorDescription = componentStack;
-     const userTheme = getThemeFromUser(Store.getState().activeUser);
+     let userTheme;
+     try {
+       userTheme = getThemeFromUser(Store.getState().activeUser);
+     } catch (themeError) {
+       // Fall back to default theme if theme retrieval fails
+       userTheme = "light";
+       console.error("Failed to retrieve user theme:", themeError);
+     }

      return (
-       <ConfigProvider theme={getAntdTheme(userTheme)}>
+       <ConfigProvider theme={(() => {
+         try {
+           return getAntdTheme(userTheme);
+         } catch (themeError) {
+           console.error("Failed to get Ant Design theme:", themeError);
+           return {}; // Fall back to default Ant Design theme
+         }
+       })()}>

Also applies to: 64-64


52-54: Enhance accessibility for the localStorage clearing link.

The link for clearing localStorage could benefit from additional accessibility attributes such as aria-label to better describe its purpose.

-<a href="#" onClick={this.clearLocalStorageAndReload}>
+<a 
+  href="#" 
+  onClick={this.clearLocalStorageAndReload}
+  aria-label="Clear local storage and reload the page"
+>
  here
</a>

39-39: Consider using theme tokens instead of hardcoded margin values.

Using a hardcoded margin value (margin: 32) may not adapt well to different themes or screen sizes. Consider using theme tokens for spacing to ensure consistency with the design system.

-<div style={{ margin: 32 }}>
+<div style={{ margin: token.marginLG }}>

You would need to extract the token from the theme:

const token = theme.useToken().token;
frontend/javascripts/oxalis/model/actions/actions.ts (1)

64-67: Good implementation of saga cancellation action.

This action creator will allow for canceling ongoing sagas when navigating away from the viewer, preventing potential issues with sagas continuing to run in the background.

Consider documenting the intended use cases for this action in a comment, as saga cancellation might have subtle implications for application state.

 export const cancelSagaAction = () =>
   ({
     type: "CANCEL_SAGA",
+    // This action is used to cancel ongoing sagas when navigating away from the viewer
   }) as const;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 92d4787 and 93e56b9.

📒 Files selected for processing (23)
  • frontend/javascripts/components/error_boundary.tsx (2 hunks)
  • frontend/javascripts/libs/UpdatableTexture.ts (1 hunks)
  • frontend/javascripts/navbar.tsx (0 hunks)
  • frontend/javascripts/oxalis/controller/scene_controller.ts (4 hunks)
  • frontend/javascripts/oxalis/controller/url_manager.ts (2 hunks)
  • frontend/javascripts/oxalis/controller/viewmodes/arbitrary_controller.tsx (1 hunks)
  • frontend/javascripts/oxalis/controller/viewmodes/plane_controller.tsx (2 hunks)
  • frontend/javascripts/oxalis/geometries/arbitrary_plane.ts (2 hunks)
  • frontend/javascripts/oxalis/geometries/materials/edge_shader.ts (3 hunks)
  • frontend/javascripts/oxalis/geometries/materials/node_shader.ts (4 hunks)
  • frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts (3 hunks)
  • frontend/javascripts/oxalis/model/actions/actions.ts (2 hunks)
  • frontend/javascripts/oxalis/model/bucket_data_handling/data_cube.ts (3 hunks)
  • frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts (2 hunks)
  • frontend/javascripts/oxalis/model/bucket_data_handling/texture_bucket_manager.ts (3 hunks)
  • frontend/javascripts/oxalis/model/helpers/shader_editor.ts (1 hunks)
  • frontend/javascripts/oxalis/model/reducers/ui_reducer.ts (2 hunks)
  • frontend/javascripts/oxalis/model/sagas/root_saga.ts (1 hunks)
  • frontend/javascripts/oxalis/store.ts (1 hunks)
  • frontend/javascripts/oxalis/view/action_bar_view.tsx (3 hunks)
  • frontend/javascripts/oxalis/view/arbitrary_view.ts (3 hunks)
  • frontend/javascripts/oxalis/view/input_catcher.tsx (1 hunks)
  • frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx (2 hunks)
💤 Files with no reviewable changes (1)
  • frontend/javascripts/navbar.tsx
🚧 Files skipped from review as they are similar to previous changes (9)
  • frontend/javascripts/oxalis/controller/viewmodes/arbitrary_controller.tsx
  • frontend/javascripts/libs/UpdatableTexture.ts
  • frontend/javascripts/oxalis/model/helpers/shader_editor.ts
  • frontend/javascripts/oxalis/store.ts
  • frontend/javascripts/oxalis/controller/url_manager.ts
  • frontend/javascripts/oxalis/controller/viewmodes/plane_controller.tsx
  • frontend/javascripts/oxalis/model/reducers/ui_reducer.ts
  • frontend/javascripts/oxalis/view/input_catcher.tsx
  • frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts
🧰 Additional context used
🧠 Learnings (1)
frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts (1)
Learnt from: dieknolle3333
PR: scalableminds/webknossos#8168
File: frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts:568-585
Timestamp: 2025-04-02T13:29:57.427Z
Learning: In the file `frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts`, the uniform name `allResolutions` should remain unchanged to maintain consistency with the shader code.
🧬 Code Definitions (2)
frontend/javascripts/oxalis/controller/scene_controller.ts (2)
frontend/javascripts/oxalis/controller/renderer.ts (1)
  • destroyRenderer (8-15)
frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts (1)
  • getDataLayers (128-130)
frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx (4)
frontend/javascripts/oxalis/store.ts (1)
  • startSaga (667-669)
frontend/javascripts/oxalis/model/sagas/root_saga.ts (1)
  • rootSaga (28-45)
frontend/javascripts/oxalis/controller/scene_controller_provider.ts (1)
  • destroySceneController (20-25)
frontend/javascripts/oxalis/model/actions/actions.ts (2)
  • resetStoreAction (49-52)
  • cancelSagaAction (64-67)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: circleci_build
🔇 Additional comments (36)
frontend/javascripts/oxalis/geometries/arbitrary_plane.ts (2)

42-45: Approve method rename and functionality split

The renaming of the previous destroy() method to stop() with focused functionality aligns well with the PR objective of preventing page reload when navigating away from the viewer. This method now specifically handles pausing listeners without full destruction.


173-176: Approve new destroy method implementation

The new destroy() method provides comprehensive cleanup by stopping store listening and destroying the material factory. This addresses the previous review comment about including this.stopStoreListening().

One consideration: If stop() is called before destroy(), this.stopStoreListening() would be called twice. Verify that this function is idempotent or consider having destroy() call stop() instead of duplicating the stop listening logic:

destroy() {
-  this.stopStoreListening();
+  this.stop();
  this.materialFactory.destroy();
}
frontend/javascripts/components/error_boundary.tsx (2)

1-1: Updated imports enhance the component's theming capabilities.

The added imports for ConfigProvider, Typography, Store, and theme utilities allow for proper theming of the error boundary component. This aligns with best practices for maintaining consistent UI across the application.

Also applies to: 3-3, 5-5


38-64: Improved error UI with proper theming support.

The error UI has been significantly improved by:

  1. Wrapping the content in ConfigProvider to apply the user's theme
  2. Using semantic typography components instead of basic HTML elements
  3. Maintaining a consistent layout with better visual hierarchy

This change ensures the error page follows the application's design system, providing a more polished user experience even during failures.

frontend/javascripts/oxalis/model/bucket_data_handling/data_cube.ts (3)

89-89: Good addition of tracking for cleanup.

Adding a dedicated array to store unsubscribe functions is a clean approach to ensure proper resource management.


135-145: Improved store property listener management.

The code now properly tracks the unsubscribe function returned by listenToStoreProperty, which will allow for proper cleanup later. This change helps prevent potential memory leaks that could occur if listeners remained active after a DataCube instance is no longer needed.


990-995: Well-implemented destruction method for proper cleanup.

The destroy method properly handles cleanup by:

  1. Clearing cube and bucket references
  2. Calling all store property unsubscribe functions
  3. Resetting the unsubscribers array

This is essential for preventing memory leaks when navigating away from the viewer without a full page reload.

frontend/javascripts/oxalis/model/actions/actions.ts (2)

40-41: Good addition of new action types.

These additions to the union type ensure proper type safety when using the new actions.


49-52: Well-defined action creator for store reset.

This action creator follows the existing pattern in the codebase and will be used to trigger store cleanup when navigating away from the viewer.

frontend/javascripts/oxalis/controller/scene_controller.ts (4)

19-19: Correctly imported destroyRenderer function.

Importing the destroyRenderer function allows for proper cleanup of the renderer when the scene controller is destroyed.


79-79: Good addition of tracking for cleanup.

Adding a property to store unsubscribe functions and initializing it in the constructor is a clean approach to resource management.

Also applies to: 93-93


537-572: Comprehensive destruction method for proper cleanup.

The destroy method thoroughly cleans up all resources managed by the SceneController:

  1. Removes debugging methods from the window object
  2. Removes all skeletons
  3. Unsubscribes from all store properties
  4. Destroys the renderer
  5. Cleans up bounding boxes, planes, and other geometries
  6. Resets the root node

This is an essential addition to prevent memory leaks and ensure proper resource management when navigating away from the viewer.


575-610: Improved event binding with proper cleanup tracking.

The refactored bindToEvents method now stores all unsubscribe functions in an array, making it easier to clean them up when the scene controller is destroyed. This is a significant improvement for resource management.

frontend/javascripts/oxalis/model/bucket_data_handling/texture_bucket_manager.ts (3)

95-95: Good addition of destruction tracking flag.

This flag will help prevent unnecessary processing after the manager is destroyed.


183-186: Proper early return for destroyed instances.

Checking if the instance is destroyed before proceeding with processing is an important optimization that prevents unnecessary work and avoids scheduling new animation frames after cleanup.


346-355: Well-implemented destruction method for resource cleanup.

The destroy method thoroughly cleans up resources managed by the TextureBucketManager:

  1. Disposes of all textures to free GPU memory
  2. Clears data texture references
  3. Removes the lookup table reference
  4. Sets the destroyed flag to prevent further processing
  5. Resets the bucket map

This is an essential addition to prevent GPU memory leaks and ensure proper resource management when navigating away from the viewer.

frontend/javascripts/oxalis/geometries/materials/node_shader.ts (4)

32-32: Good addition of a property to store unsubscribe functions.

Adding storePropertyUnsubscribers enables proper cleanup of event listeners when the shader is destroyed, which helps prevent memory leaks.


98-121: Improved store subscription management.

Storing the unsubscribe functions in an array allows for proper cleanup. This pattern enables better lifecycle management of the component.


132-156: Consistent pattern for store subscription management.

The code correctly adds another store subscription to the array of unsubscribe functions, maintaining the pattern established earlier.


431-441: Good implementation of the destroy method.

The destroy method properly:

  1. Calls all unsubscribe functions to stop listening to store changes
  2. Clears the array of unsubscribe functions
  3. Sets all uniform values to null to prevent memory leaks

This addresses the previous review comment about only clearing tree colors.

frontend/javascripts/oxalis/geometries/materials/edge_shader.ts (3)

21-21: Good addition of a property to store unsubscribe functions.

Adding storePropertyUnsubscribers enables proper cleanup of event listeners when the shader is destroyed, consistent with the pattern in NodeShader.


62-96: Improved store subscription management.

Storing the unsubscribe functions in an array allows for proper cleanup. This pattern enables better lifecycle management of the component.


207-217: Good implementation of the destroy method.

The destroy method properly cleans up all resources by:

  1. Calling all unsubscribe functions
  2. Clearing the array of unsubscribe functions
  3. Setting all uniform values to null to prevent memory leaks

This addresses the previous review comment about only clearing tree colors.

frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx (4)

14-23: Good imports for the new cleanup functionality.

The imports add necessary functions and actions for proper cleanup when unmounting the component.


115-117: Good addition of componentDidMount.

Starting the root saga in componentDidMount ensures the saga is initialized when the component is mounted.


120-124: Thorough cleanup in componentWillUnmount.

The component properly cleans up resources by:

  1. Stopping the URL updater
  2. Resetting the Model
  3. Destroying the scene controller
  4. Dispatching reset and cancel actions to the store

This comprehensive cleanup helps prevent memory leaks and ensures a clean state when navigating away.


135-165: Good implementation of conditional page reload.

The code now conditionally skips page reload for super users, which aligns with the PR objective to "prevent the page from reloading when users navigate away from the webknossos viewer." This enables controlled testing of the performance improvement before rolling it out to all users.

frontend/javascripts/oxalis/view/arbitrary_view.ts (3)

6-8: Good addition of null-safe controller access.

Adding the getSceneControllerOrNull import allows for safer access to the scene controller, which helps prevent potential runtime errors.


123-127: Improved error handling with null checking.

Using the optional chaining operator with getSceneControllerOrNull() prevents errors when the scene controller has been destroyed. The detailed comment also explains the component lifecycle and why this check is necessary.


269-274: Good defensive programming with early return.

Checking if the scene controller is null before proceeding prevents potential errors in the resizeImpl method. This is particularly important since this method can be called in a throttled manner, possibly after the component has been unmounted.

frontend/javascripts/oxalis/view/action_bar_view.tsx (2)

143-193: Solid implementation of the CreateAnnotationButton component!

The new functional component effectively encapsulates the annotation creation logic, providing a cleaner separation of concerns. It properly uses React hooks (useHistory, useSelector) to access routing and state management capabilities.

The onClick handler handles all the necessary logic:

  • Gets current state
  • Checks for segmentation layer support
  • Handles mapping information
  • Creates the annotation
  • Navigates to the new annotation

This is a nice improvement that aligns with modern React patterns and makes the code more maintainable.


242-244: Good refactoring to use the new component

Clean replacement of the old method with the new component, making the code more maintainable.

frontend/javascripts/oxalis/model/sagas/root_saga.ts (1)

27-45: Enhanced saga control flow with cancellation support

This change significantly improves the application's ability to clean up resources when navigating away from the viewer. The addition of the race effect to listen for both restart and cancel actions provides more flexibility in saga lifecycle management.

Key improvements:

  • Using race instead of just take allows handling competing actions
  • Adding a cancel path enables proper cleanup when navigating away
  • Setting isWkReady to false when restarting ensures proper state indication

This will prevent page reloads as specified in the PR objectives by providing a graceful way to cancel sagas when navigating away.

frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts (3)

120-120: Improved type safety for material property

Updating the material property type to allow null/undefined values makes the code more robust by explicitly handling cases where the material might not be initialized or has been destroyed.


984-989: Good defensive programming with null check

Adding a null check before returning the material helps prevent runtime errors and provides a clearer error message if someone tries to access the material after it's been destroyed.


1171-1188: Excellent resource cleanup implementation

The new destroy method is a critical addition for preventing memory leaks when navigating away from the viewer. It thoroughly cleans up:

  1. Event listeners via stopListening()
  2. Subscription callbacks for color and mapping seeds
  3. The material reference itself
  4. Cancels any pending shader recomputation
  5. Nullifies all uniform values to prevent reference retention

This implementation is comprehensive and will help achieve the PR's goal of avoiding page reloads by ensuring proper cleanup when navigating away from the viewer.

@MichaelBuessemeyer MichaelBuessemeyer self-requested a review April 7, 2025 08:54
Copy link
Contributor

@MichaelBuessemeyer MichaelBuessemeyer left a comment

Choose a reason for hiding this comment

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

Awesome, code looks good. Thanks for applying my feedback 🦶 and answering my questions 🤙

During testing I noticed that when trying to get a new task but there aren't any more tasks, that the navigation back to the dashboard still includes a page refresh. At least I tried this twice and it looked like that,

But I don't think this is much of a blocker for this pr and can be tackled on demand in a future pr

@philippotto
Copy link
Member Author

During testing I noticed that when trying to get a new task but there aren't any more tasks, that the navigation back to the dashboard still includes a page refresh. At least I tried this twice and it looked like that,

But I don't think this is much of a blocker for this pr and can be tackled on demand in a future pr

I think, this will be easy to fix (remove a location.reload), but let's do this after the guinea-pig phase (the scenario will be quite rare, anyway).

@philippotto philippotto enabled auto-merge (squash) April 8, 2025 06:14
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: 0

🧹 Nitpick comments (2)
frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx (2)

119-124: Consider adding error handling for cleanup operations

The cleanup sequence is good, but there's no error handling if any of these operations fail. If one operation fails, it could prevent subsequent cleanup steps from executing.

Consider wrapping these operations in try-catch blocks:

  componentWillUnmount() {
-    UrlManager.stopUrlUpdater();
-    Model.reset();
-    destroySceneController();
-    Store.dispatch(resetStoreAction());
-    Store.dispatch(cancelSagaAction());
+    try {
+      UrlManager.stopUrlUpdater();
+    } catch (error) {
+      console.error("Failed to stop URL updater:", error);
+    }
+    
+    try {
+      Model.reset();
+    } catch (error) {
+      console.error("Failed to reset model:", error);
+    }
+    
+    try {
+      destroySceneController();
+    } catch (error) {
+      console.error("Failed to destroy scene controller:", error);
+    }
+    
+    try {
+      Store.dispatch(resetStoreAction());
+      Store.dispatch(cancelSagaAction());
+    } catch (error) {
+      console.error("Failed to dispatch cleanup actions:", error);
+    }

134-135: Consider adding more context in the comment

The comment about enforcing a reload could be more informative by explaining why a reload is necessary for non-super users and what specific issues it addresses.

-    // Enforce a reload to absolutely ensure a clean slate.
+    // Enforce a reload for non-super users to absolutely ensure a clean slate.
+    // This prevents potential memory leaks and state inconsistencies 
+    // until we're confident the manual cleanup is reliable.
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 93e56b9 and 474783a.

📒 Files selected for processing (2)
  • frontend/javascripts/oxalis/store.ts (1 hunks)
  • frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/javascripts/oxalis/store.ts
🧰 Additional context used
🧬 Code Definitions (1)
frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx (4)
frontend/javascripts/oxalis/store.ts (1)
  • startSaga (668-670)
frontend/javascripts/oxalis/model/sagas/root_saga.ts (1)
  • rootSaga (28-45)
frontend/javascripts/oxalis/controller/scene_controller_provider.ts (1)
  • destroySceneController (20-25)
frontend/javascripts/oxalis/model/actions/actions.ts (2)
  • resetStoreAction (49-52)
  • cancelSagaAction (64-67)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: circleci_build
🔇 Additional comments (3)
frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx (3)

14-23: Good addition of necessary imports for cleanup functionality

These imports support the new lifecycle management approach by bringing in the required dependencies for saga management, controller destruction, and store reset actions.


115-117: Good implementation of componentDidMount

Starting the root saga when the component mounts is the correct approach. This ensures that all saga-based side effects are properly initialized when the view is loaded.


126-132: Good implementation of conditional page reload for super users

This approach aligns well with the PR objectives to prevent page reloads for super users as a first step. The nullish operator ?. correctly handles the case where activeUser might be null.

@philippotto philippotto merged commit eea6dcc into master Apr 8, 2025
3 checks passed
@philippotto philippotto deleted the no-page-reload branch April 8, 2025 06:25
@coderabbitai coderabbitai bot mentioned this pull request Jul 17, 2025
7 tasks
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