Skip to content
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

feat: remove animation canvas entirely | move to animation package #1457

Open
wants to merge 44 commits into
base: main
Choose a base branch
from

Conversation

shanimal08
Copy link
Collaborator

@shanimal08 shanimal08 commented Jan 30, 2025

Description

  • most of the work was done in the new tari-tower repo @tari-project/tari-tower repo
    • implemented typescript, and converted Classes to modules where i could
    • fixed type errors
    • added load/remove tower functions which are exported along with setAnimationState and setAnimationProperties
    • built and published as a lib
  • added @tari-project/tari-tower package
  • removed old webgl build files
  • updated imports
  • added extra toggle loading state flag (to help in useMiningUiStateMachine.ts)
  • updated workflows so we can use the package

Motivation and Context

  • toggling visual mode previously just hid the canvas, this removes the element entirely + frees up some gpu after removing the WEBGL renderer

How Has This Been Tested?

GPU.mining.on.on.off.diff.mov
GPU.mining.off.on.off.diff.mov
animation.still.behaving.normally.mov

What process can a PR reviewer use to test or verify this change?

  • run with visual mode on vs off and see GPU stats

Breaking Changes

  • yes, after this is merged you will need to install @tari-labs/tari-tower for local development
  • nope, just npm ci after it's merged :3

Summary by CodeRabbit

  • New Features

    • Introduced an enhanced visual mode with improved loading indicators and smoother animation transitions powered by an external graphics module.
  • Refactor

    • Streamlined the page layout by removing embedded graphics initialization code and redundant elements, resulting in more efficient loading and clearer error feedback.
  • Style

    • Updated styling for key visual elements with revised identifiers to ensure a consistent and polished interface.
  • Bug Fixes

    • Improved error handling for WebGL support notifications and visual mode toggling.
  • Chores

    • Updated dependency versions for improved performance and stability.
    • Removed outdated licensing documentation for texture assets.
    • Removed unused TypeScript declarations and related files to clean up the codebase.

- add tari-tower animation lib
- remove old build files
- update imports and uses
- implement complete canvas removal
- update workflows to include setting up npm credentials
@shanimal08 shanimal08 changed the title feat: rm canvas [wip] feat: remove animation canvas entirely | move to animation package Jan 30, 2025
@shanimal08 shanimal08 marked this pull request as ready for review January 31, 2025 15:21
@brianp
Copy link
Collaborator

brianp commented Feb 3, 2025

nestedblocks.mov

When i turned visual mode off, then on this happened. It doesn't correct itself. On the next floor the glitched level moves up with everything else.

When I turned it off and on again, it broke entirely and the tower never returned.

Universe log shows nothing noteworthy.

@shanimal08
Copy link
Collaborator Author

When i turned visual mode off, then on this happened. It doesn't correct itself. On the next floor the glitched level moves up with everything else.

how did you even manage this @brianp 😭

turned it off during mining right? did you switch back super quick after, or after some time?
had you won/failed during this

@brianp
Copy link
Collaborator

brianp commented Feb 3, 2025

I didn't switch it back super quick, as I was watching resources after I turned it off.
Mining is on.
This is reproducible for me after restart.

First on/off. Broken floor.
Second on/off. Missing tower.

I have since won blocks.

5th on/off the tower returned, but it's in an absolutely abysmal state. I won't take a video. It's shocking and scary.

@brianp
Copy link
Collaborator

brianp commented Feb 3, 2025

There's also a memory leak in here somewhere. I can very quickly go from the normal 800mb on the front end to 2GB by flicking visual mode on and off.

@shanimal08 shanimal08 marked this pull request as draft February 4, 2025 08:27
@shanimal08 shanimal08 requested a review from brianp February 10, 2025 12:30
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/hooks/mining/useMiningUiStateMachine.ts (2)

20-36: Consider extracting debounce configuration.

The hardcoded 300ms debounce timeout should be defined as a constant for better maintainability.

+const ANIMATION_STATE_DEBOUNCE_MS = 300;
+
 useEffect(() => {
     let isLatestEffect = true;
     if (!visualMode || visualModeToggleLoading) return;
     const notStarted = !animationStatus || animationStatus == 'not-started';
     if (isMining && notStarted) {
         // Debounce animation state changes
         const timer = setTimeout(() => {
             if (isLatestEffect) {
                 setAnimationState('start');
             }
-        }, 300);
+        }, ANIMATION_STATE_DEBOUNCE_MS);
         return () => {
             clearTimeout(timer);
             isLatestEffect = false;
         };
     }
 }, [indexTrigger, isMining, visualMode, visualModeToggleLoading]);

38-56: Optimize effect dependencies.

The effect has many dependencies which could lead to unnecessary re-renders. Consider memoizing complex conditions.

+const shouldStopMemo = useMemo(
+    () => {
+        const notStopped = animationStatus !== 'not-started';
+        const preventStop = !setupComplete || isMiningInitiated || isChangingMode;
+        return !isMining && notStopped && !preventStop;
+    },
+    [animationStatus, setupComplete, isMiningInitiated, isChangingMode, isMining]
+);
+
 useEffect(() => {
     let isLatestEffect = true;
     if (!visualMode || visualModeToggleLoading) return;
-    const notStopped = animationStatus !== 'not-started';
-    const preventStop = !setupComplete || isMiningInitiated || isChangingMode;
-    const shouldStop = !isMining && notStopped && !preventStop;
-    if (shouldStop) {
+    if (shouldStopMemo) {
         // Debounce animation state changes
         const timer = setTimeout(() => {
             if (isLatestEffect) {
                 setAnimationState('stop');
             }
         }, 300);
         return () => {
             clearTimeout(timer);
             isLatestEffect = false;
         };
     }
-}, [indexTrigger, setupComplete, isMiningInitiated, isMining, isChangingMode, visualMode, visualModeToggleLoading]);
+}, [shouldStopMemo, visualMode, visualModeToggleLoading]);
src/store/useUIStore.ts (2)

7-9: Extract layout constants to a separate configuration file.

Consider moving layout-related constants to a dedicated configuration file for better maintainability.

+// src/config/layout.ts
+export const LAYOUT = {
+    SIDEBAR: {
+        WIDTH: 348,
+        PADDING: 20,
+        get TOTAL_OFFSET() {
+            return this.WIDTH + this.PADDING;
+        }
+    }
+} as const;

79-90: Add type safety for animation properties.

Consider adding TypeScript types for animation properties to prevent potential errors.

+type AnimationProperty = {
+    property: 'bgColor1' | 'bgColor2' | 'neutralColor' | 'mainColor' | 'successColor' | 'failColor' | 'particlesColor' | 'goboIntensity' | 'particlesOpacity' | 'particlesSize';
+    value: string | number;
+};

-export const animationLightBg = [
+export const animationLightBg: AnimationProperty[] = [
     { property: 'bgColor1', value: '#ffffff' },
     // ...
];

-export const animationDarkBg = [
+export const animationDarkBg: AnimationProperty[] = [
     { property: 'bgColor1', value: '#212121' },
     // ...
];

Also applies to: 92-103

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0fd5248 and 957ccb2.

📒 Files selected for processing (5)
  • src/containers/main/Dashboard/components/VisualMode.tsx (3 hunks)
  • src/hooks/mining/useMiningUiStateMachine.ts (1 hunks)
  • src/main.tsx (1 hunks)
  • src/store/useAppConfigStore.ts (3 hunks)
  • src/store/useUIStore.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main.tsx
  • src/containers/main/Dashboard/components/VisualMode.tsx
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: tauri-build
  • GitHub Check: cargo checks (fmt, clippy, check)
🔇 Additional comments (2)
src/hooks/mining/useMiningUiStateMachine.ts (1)

4-6: LGTM!

The imports have been correctly updated to use the new @tari-project/tari-tower package.

src/store/useUIStore.ts (1)

65-72: LGTM!

The state validation in setIsWebglNotSupported is a good improvement to prevent unnecessary updates.

Comment on lines 329 to 339
export const setVisualMode = (enabled: boolean) => {
useAppConfigStore.setState({ visual_mode: enabled, visualModeToggleLoading: true });
invoke('set_visual_mode', { enabled })
.catch((e) => {
const appStateStore = useAppStateStore.getState();
console.error('Could not set visual mode', e);
appStateStore.setError('Could not change visual mode');
useAppConfigStore.setState({ visual_mode: !enabled });
})
.finally(() => useAppConfigStore.setState({ visualModeToggleLoading: false }));
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add timeout and retry mechanism for visual mode toggle.

To address the reported issues with visual mode toggling, add a timeout and retry mechanism.

+const VISUAL_MODE_TOGGLE_TIMEOUT_MS = 5000;
+const MAX_RETRY_ATTEMPTS = 3;
+
 export const setVisualMode = (enabled: boolean) => {
+    let timeoutId: NodeJS.Timeout;
+    let retryCount = 0;
+
+    const cleanup = () => {
+        clearTimeout(timeoutId);
+        useAppConfigStore.setState({ visualModeToggleLoading: false });
+    };
+
+    const attemptToggle = () => {
+        timeoutId = setTimeout(() => {
+            if (retryCount < MAX_RETRY_ATTEMPTS) {
+                console.warn(`Visual mode toggle attempt ${retryCount + 1} timed out, retrying...`);
+                retryCount++;
+                attemptToggle();
+            } else {
+                console.error('Visual mode toggle failed after max retries');
+                useAppConfigStore.setState({ visual_mode: !enabled });
+                cleanup();
+            }
+        }, VISUAL_MODE_TOGGLE_TIMEOUT_MS);
+
+        invoke('set_visual_mode', { enabled })
+            .then(() => {
+                clearTimeout(timeoutId);
+                cleanup();
+            })
+            .catch((e) => {
+                const appStateStore = useAppStateStore.getState();
+                console.error('Could not set visual mode', e);
+                appStateStore.setError('Could not change visual mode');
+                useAppConfigStore.setState({ visual_mode: !enabled });
+                cleanup();
+            });
+    };
+
     useAppConfigStore.setState({ visual_mode: enabled, visualModeToggleLoading: true });
-    invoke('set_visual_mode', { enabled })
-        .catch((e) => {
-            const appStateStore = useAppStateStore.getState();
-            console.error('Could not set visual mode', e);
-            appStateStore.setError('Could not change visual mode');
-            useAppConfigStore.setState({ visual_mode: !enabled });
-        })
-        .finally(() => useAppConfigStore.setState({ visualModeToggleLoading: false }));
+    attemptToggle();
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const setVisualMode = (enabled: boolean) => {
useAppConfigStore.setState({ visual_mode: enabled, visualModeToggleLoading: true });
invoke('set_visual_mode', { enabled })
.catch((e) => {
const appStateStore = useAppStateStore.getState();
console.error('Could not set visual mode', e);
appStateStore.setError('Could not change visual mode');
useAppConfigStore.setState({ visual_mode: !enabled });
})
.finally(() => useAppConfigStore.setState({ visualModeToggleLoading: false }));
};
const VISUAL_MODE_TOGGLE_TIMEOUT_MS = 5000;
const MAX_RETRY_ATTEMPTS = 3;
export const setVisualMode = (enabled: boolean) => {
let timeoutId: NodeJS.Timeout;
let retryCount = 0;
const cleanup = () => {
clearTimeout(timeoutId);
useAppConfigStore.setState({ visualModeToggleLoading: false });
};
const attemptToggle = () => {
timeoutId = setTimeout(() => {
if (retryCount < MAX_RETRY_ATTEMPTS) {
console.warn(`Visual mode toggle attempt ${retryCount + 1} timed out, retrying...`);
retryCount++;
attemptToggle();
} else {
console.error('Visual mode toggle failed after max retries');
useAppConfigStore.setState({ visual_mode: !enabled });
cleanup();
}
}, VISUAL_MODE_TOGGLE_TIMEOUT_MS);
invoke('set_visual_mode', { enabled })
.then(() => {
clearTimeout(timeoutId);
cleanup();
})
.catch((e) => {
const appStateStore = useAppStateStore.getState();
console.error('Could not set visual mode', e);
appStateStore.setError('Could not change visual mode');
useAppConfigStore.setState({ visual_mode: !enabled });
cleanup();
});
};
useAppConfigStore.setState({ visual_mode: enabled, visualModeToggleLoading: true });
attemptToggle();
};

Comment on lines 312 to 319
if (appConfig.visual_mode) {
try {
await loadTowerAnimation({ canvasId: TOWER_CANVAS_ID, offset: sidebarTowerOffset });
} catch (e) {
console.error('Error at loadTowerAnimation:', e);
useAppConfigStore.setState({ visual_mode: false });
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add cleanup for tower animation.

To prevent memory leaks, ensure proper cleanup of tower animation resources.

 if (appConfig.visual_mode) {
+    let cleanupAnimation: (() => void) | undefined;
     try {
-        await loadTowerAnimation({ canvasId: TOWER_CANVAS_ID, offset: sidebarTowerOffset });
+        cleanupAnimation = await loadTowerAnimation({ canvasId: TOWER_CANVAS_ID, offset: sidebarTowerOffset });
     } catch (e) {
         console.error('Error at loadTowerAnimation:', e);
         useAppConfigStore.setState({ visual_mode: false });
+    } finally {
+        return () => cleanupAnimation?.();
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (appConfig.visual_mode) {
try {
await loadTowerAnimation({ canvasId: TOWER_CANVAS_ID, offset: sidebarTowerOffset });
} catch (e) {
console.error('Error at loadTowerAnimation:', e);
useAppConfigStore.setState({ visual_mode: false });
}
}
if (appConfig.visual_mode) {
let cleanupAnimation: (() => void) | undefined;
try {
cleanupAnimation = await loadTowerAnimation({ canvasId: TOWER_CANVAS_ID, offset: sidebarTowerOffset });
} catch (e) {
console.error('Error at loadTowerAnimation:', e);
useAppConfigStore.setState({ visual_mode: false });
} finally {
return () => cleanupAnimation?.();
}
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/store/useAppConfigStore.ts (2)

323-330: ⚠️ Potential issue

Add cleanup for tower animation resources.

The current implementation lacks cleanup of tower animation resources, which could lead to memory leaks as reported in the PR comments.


340-350: ⚠️ Potential issue

Add timeout and retry mechanism for visual mode toggle.

Based on the reported issues with visual mode toggling and memory leaks, the implementation needs a timeout and retry mechanism with proper cleanup.

Additionally, consider adding memory usage monitoring:

 export const setVisualMode = (enabled: boolean) => {
+    const initialMemory = window.performance.memory?.usedJSHeapSize;
     useAppConfigStore.setState({ visual_mode: enabled, visualModeToggleLoading: true });
     invoke('set_visual_mode', { enabled })
         .catch((e) => {
             const appStateStore = useAppStateStore.getState();
             console.error('Could not set visual mode', e);
             appStateStore.setError('Could not change visual mode');
             useAppConfigStore.setState({ visual_mode: !enabled });
         })
-        .finally(() => useAppConfigStore.setState({ visualModeToggleLoading: false }));
+        .finally(() => {
+            useAppConfigStore.setState({ visualModeToggleLoading: false });
+            const finalMemory = window.performance.memory?.usedJSHeapSize;
+            if (finalMemory && initialMemory && finalMemory - initialMemory > 100 * 1024 * 1024) {
+                console.warn('Memory usage increased by more than 100MB during visual mode toggle');
+            }
+        });
 };
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 77020ad and b27d0af.

📒 Files selected for processing (3)
  • index.html (1 hunks)
  • src/store/useAppConfigStore.ts (3 hunks)
  • src/store/useBlockchainVisualisationStore.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/store/useBlockchainVisualisationStore.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: tauri-build
🔇 Additional comments (5)
index.html (3)

191-191: LGTM! DOM structure simplified.

The removal of the <main> tag and simplified DOM structure is a good change that reduces complexity.


192-194: LGTM! Audio element configuration is correct.

The audio element is properly configured with preloading enabled and correct source path.


195-195: Verify tower package initialization.

With the removal of the WebGL initialization script, ensure that @tari-project/tari-tower package is properly initialized in main.tsx. The reported issues with visual mode toggling (white screen, broken level) suggest potential initialization or cleanup problems.

Let's verify the tower package initialization:

❌ Verification inconclusive

🏁 Script executed:

#!/bin/bash
# Description: Check tower package initialization and error handling

# Look for tower package imports and initialization
echo "Searching for tower package initialization..."
rg -A 10 "@tari-project/tari-tower" --type ts

# Look for error handling in visual mode toggle
echo "Searching for visual mode toggle error handling..."
rg -A 10 "setVisualMode|toggleVisualMode" --type ts

Length of output: 12645


Tower Package Initialization in main.tsx Verification

Based on our search output, usage and error handling of the @tari-project/tari-tower package is spread across various modules (e.g., in VisualMode and UI store files). To definitively verify that it is initialized properly, please check the contents of src/main.tsx for proper import and initialization of the tower package. Could you run a search for the tower package's usage in main.tsx, for example, by verifying if it imports any functions like loadTowerAnimation or setAnimationState?

If there's no direct initialization in main.tsx, then the initialization is likely delegated to downstream components. In that case, verify that the correct initialization is happening in these components and that error handling is in place (as shown, e.g., in VisualMode).

Please confirm whether src/main.tsx contains the tower package initialization and if adjustments are required.

src/store/useAppConfigStore.ts (2)

15-17: LGTM! Good addition of loading state.

The visualModeToggleLoading state is a good addition to handle loading states during visual mode transitions.


9-9: Verify the imported package version.

The new dependency @tari-project/tari-tower should be verified for compatibility and security.

Also applies to: 13-13

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Check package.json for the version of @tari-project/tari-tower
grep -r '"@tari-project/tari-tower"' package.json

# Check for any security advisories
npm audit @tari-project/tari-tower

Length of output: 483


I've noticed that the initial commands didn't yield any results—there’s no package.json in the repository root, and the npm audit failed due to a missing lockfile. This means we haven't yet located where (or if) the new dependency is declared. To advance the verification, let’s search the entire repository for any package.json files and inspect them for an entry of @tari-project/tari-tower:

Please run this script. Based on its output, we can determine if the dependency is declared somewhere (and possibly what version) or if further manual review is needed.


🏁 Script executed:

#!/bin/bash
# Search for all package.json files and check for the dependency declaration.
fd package.json | while read -r filepath; do
    echo "Checking ${filepath}:"
    grep '"@tari-project/tari-tower"' "${filepath}" && echo " --> Dependency found in ${filepath}" || echo " --> Not found in ${filepath}"
    echo "------------------"
done

Length of output: 336


Dependency Version Verified for @tari-project/tari-tower

  • The dependency is declared in the repository's package.json with version "^0.0.14".
  • The initial npm audit didn't run due to a missing lockfile; ensure one is generated for further security verification if needed.

Comment on lines +184 to 187
#tower-canvas {
position: absolute;
pointer-events: auto;
}
Copy link

@coderabbitai coderabbitai bot Feb 11, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Ensure proper cleanup of tower-canvas element.

Given the reported memory leak issues when toggling visual mode (memory usage increasing from 800MB to 2GB), ensure that the canvas element is properly disposed of when visual mode is disabled. Consider implementing the following:

  1. Remove all WebGL contexts and resources
  2. Remove event listeners
  3. Clear the canvas element from DOM

To help diagnose the memory leak, consider running this script to analyze canvas cleanup:


🏁 Script executed:

#!/bin/bash
# Description: Search for canvas cleanup code in the codebase

# Look for canvas cleanup patterns
echo "Searching for canvas cleanup patterns..."
rg -A 5 "removeChild|remove\(|dispose|destroy|cleanup" --type ts

# Look for event listener cleanup
echo "Searching for event listener cleanup..."
rg -A 5 "removeEventListener" --type ts

# Look for WebGL context cleanup
echo "Searching for WebGL context cleanup..."
rg -A 5 "getContext\(['\"]webgl['\"]|loseContext|delete" --type ts

Length of output: 3407


Potential Memory Leak Issue with the tower-canvas Element

The search results show no explicit cleanup for the tower-canvas element, particularly for its WebGL context. To avoid memory leaks when toggling visual mode, please ensure that:

  • WebGL Context Disposal: Invoke appropriate methods (e.g., context loss or explicit disposal functions) to properly free WebGL resources.
  • Event Listener Removal: Detach any event listeners attached to the canvas.
  • DOM Cleanup: Remove the canvas element from the DOM when visual mode is disabled.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

DOM Cleanup: Remove the canvas element from the DOM when visual mode is disabled.

@coderabbitai that's the whole point of this PR 😭 - the removal of the canvas is handled in the @tari-project/tari-tower package

Copy link

Choose a reason for hiding this comment

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

@shanimal08 I apologize for the redundant suggestion. You're absolutely right - the canvas cleanup is already being handled by the @tari-project/tari-tower package, which is precisely what this PR is about. Thank you for the clarification.


✏️ Learnings added
Learnt from: shanimal08
PR: tari-project/universe#1457
File: index.html:184-187
Timestamp: 2025-02-12T07:12:44.217Z
Learning: The canvas cleanup and DOM manipulation for the tower animation is handled by the `@tari-project/tari-tower` package, which completely removes the canvas element when visual mode is disabled.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@shanimal08 shanimal08 added a-ui dependencies Pull requests that update a dependency file javascript Pull requests that update Javascript code labels Feb 12, 2025
@brianp
Copy link
Collaborator

brianp commented Feb 12, 2025

Same problem as last time. When visual mode is turned back on the animation never starts again. 🙅🏻

@shanimal08
Copy link
Collaborator Author

Same problem as last time. When visual mode is turned back on the animation never starts again. 🙅🏻

no ways 😭

@shanimal08 shanimal08 marked this pull request as draft February 12, 2025 13:16
@shanimal08 shanimal08 marked this pull request as ready for review February 12, 2025 15:28
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
src/containers/main/Dashboard/components/VisualMode.tsx (1)

54-60: ⚠️ Potential issue

Prevent rapid toggling to avoid glitches.

Add debounce to prevent rapid toggling that could cause memory issues and glitches.

+import { debounce } from 'lodash';
+
-const handleSwitch = useCallback(() => {
+const handleSwitch = useCallback(
+    debounce(() => {
         if (visualMode) {
             handleDisable();
         } else {
             handleEnable();
         }
-    }, [handleDisable, handleEnable, visualMode]);
+    }, 1000, { leading: true, trailing: false }),
+    [handleDisable, handleEnable, visualMode],
+);
src/store/useAppConfigStore.ts (2)

321-328: 🛠️ Refactor suggestion

Improve cleanup on animation loading failures.

Add proper cleanup when animation loading fails to prevent memory leaks.

 if (appConfig.visual_mode && !canvasElement) {
     try {
-        await loadTowerAnimation({ canvasId: TOWER_CANVAS_ID, offset: sidebarTowerOffset });
+        const cleanup = await loadTowerAnimation({ canvasId: TOWER_CANVAS_ID, offset: sidebarTowerOffset });
+        return () => {
+            cleanup?.();
+            if (window.gc) window.gc();
+        };
     } catch (e) {
         console.error('Error at loadTowerAnimation:', e);
         useAppConfigStore.setState({ visual_mode: false });
+        // Force cleanup on error
+        if (window.gc) window.gc();
     }
 }

338-355: 🛠️ Refactor suggestion

Add retry mechanism for visual mode toggle.

Implement a retry mechanism with exponential backoff to handle transient failures.

+const MAX_RETRIES = 3;
+const INITIAL_TIMEOUT = 3500;
+
 export const setVisualMode = (enabled: boolean) => {
+    let retryCount = 0;
+
+    const attemptToggle = () => {
+        const timeout = INITIAL_TIMEOUT * Math.pow(2, retryCount);
+
         useAppConfigStore.setState({ visual_mode: enabled, visualModeToggleLoading: true });
         invoke('set_visual_mode', { enabled })
             .catch((e) => {
                 const appStateStore = useAppStateStore.getState();
                 console.error('Could not set visual mode', e);
-                appStateStore.setError('Could not change visual mode');
-                useAppConfigStore.setState({ visual_mode: !enabled });
+                
+                if (retryCount < MAX_RETRIES) {
+                    retryCount++;
+                    console.log(`Retrying visual mode toggle (attempt ${retryCount}/${MAX_RETRIES})`);
+                    setTimeout(attemptToggle, timeout);
+                } else {
+                    appStateStore.setError('Could not change visual mode after multiple attempts');
+                    useAppConfigStore.setState({ visual_mode: !enabled });
+                }
             })
             .finally(() => {
                 if (loadingTimeout) {
                     clearTimeout(loadingTimeout);
                 }
                 loadingTimeout = setTimeout(() => {
                     useAppConfigStore.setState({ visualModeToggleLoading: false });
-                }, 3500);
+                }, timeout);
             });
+    };
+
+    attemptToggle();
 };
🧹 Nitpick comments (1)
src/containers/main/Dashboard/components/VisualMode.tsx (1)

5-16: Add error boundary for animation failures.

Consider wrapping the visual mode component with an error boundary to gracefully handle animation failures and prevent UI crashes.

+import { Component, ErrorInfo, ReactNode } from 'react';
+
+class VisualModeErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean }> {
+    constructor(props: { children: ReactNode }) {
+        super(props);
+        this.state = { hasError: false };
+    }
+
+    static getDerivedStateFromError(_: Error) {
+        return { hasError: true };
+    }
+
+    componentDidCatch(error: Error, errorInfo: ErrorInfo) {
+        console.error('Visual mode error:', error, errorInfo);
+    }
+
+    render() {
+        if (this.state.hasError) {
+            return <ErrorTypography>Failed to load visual mode. Please try again.</ErrorTypography>;
+        }
+        return this.props.children;
+    }
+}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 268acc6 and 61c6999.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • package.json (1 hunks)
  • src/components/elements/ToggleSwitch.tsx (1 hunks)
  • src/containers/main/Dashboard/components/VisualMode.tsx (3 hunks)
  • src/store/appStateStore.ts (2 hunks)
  • src/store/useAppConfigStore.ts (4 hunks)
  • src/store/useMiningMetricsStore.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/store/useMiningMetricsStore.ts
  • package.json
  • src/store/appStateStore.ts
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: tauri-build
  • GitHub Check: Check i18n
🔇 Additional comments (1)
src/components/elements/ToggleSwitch.tsx (1)

13-14: LGTM! Good accessibility implementation.

The toggle switch implementation includes proper keyboard handling and ARIA attributes.

Comment on lines +42 to +52
const handleEnable = useCallback(() => {
loadTowerAnimation({ canvasId: TOWER_CANVAS_ID, offset: sidebarTowerOffset })
.then(() => {
setVisualMode(true);
setAnimationState('showVisual');
})
.catch((e) => {
console.error('Could not enable visual mode. Error at loadTowerAnimation:', e);
setVisualMode(false);
});
}, []);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Monitor memory usage during animation loading.

Based on the PR comments about memory leaks, add memory usage monitoring during animation loading.

 const handleEnable = useCallback(() => {
+    const initialMemory = window.performance.memory?.usedJSHeapSize;
+    
     loadTowerAnimation({ canvasId: TOWER_CANVAS_ID, offset: sidebarTowerOffset })
         .then(() => {
             setVisualMode(true);
             setAnimationState('showVisual');
+            
+            // Monitor memory usage
+            const currentMemory = window.performance.memory?.usedJSHeapSize;
+            if (currentMemory && initialMemory && currentMemory - initialMemory > 500 * 1024 * 1024) {
+                console.warn('High memory usage detected after enabling visual mode');
+            }
         })
         .catch((e) => {
             console.error('Could not enable visual mode. Error at loadTowerAnimation:', e);
             setVisualMode(false);
         });
 }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleEnable = useCallback(() => {
loadTowerAnimation({ canvasId: TOWER_CANVAS_ID, offset: sidebarTowerOffset })
.then(() => {
setVisualMode(true);
setAnimationState('showVisual');
})
.catch((e) => {
console.error('Could not enable visual mode. Error at loadTowerAnimation:', e);
setVisualMode(false);
});
}, []);
const handleEnable = useCallback(() => {
const initialMemory = window.performance.memory?.usedJSHeapSize;
loadTowerAnimation({ canvasId: TOWER_CANVAS_ID, offset: sidebarTowerOffset })
.then(() => {
setVisualMode(true);
setAnimationState('showVisual');
// Monitor memory usage
const currentMemory = window.performance.memory?.usedJSHeapSize;
if (currentMemory && initialMemory && currentMemory - initialMemory > 500 * 1024 * 1024) {
console.warn('High memory usage detected after enabling visual mode');
}
})
.catch((e) => {
console.error('Could not enable visual mode. Error at loadTowerAnimation:', e);
setVisualMode(false);
});
}, []);

Comment on lines +28 to +41
const handleDisable = useCallback(() => {
setVisualMode(false);
removeTowerAnimation({ canvasId: TOWER_CANVAS_ID })
.then(() => {
// Force garbage collection to clean up WebGL context
if (window.gc) {
window.gc();
}
})
.catch((e) => {
console.error('Could not disable visual mode. Error at loadTowerAnimation:', e);
setVisualMode(true);
});
}, []);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add timeout to prevent hanging during disable.

The disable operation should have a timeout to prevent hanging if the animation cleanup takes too long.

 const handleDisable = useCallback(() => {
+    const timeoutId = setTimeout(() => {
+        console.error('Tower animation cleanup timed out');
+        setVisualMode(false);
+    }, 5000);
+
     setVisualMode(false);
     removeTowerAnimation({ canvasId: TOWER_CANVAS_ID })
         .then(() => {
             // Force garbage collection to clean up WebGL context
             if (window.gc) {
                 window.gc();
             }
         })
         .catch((e) => {
             console.error('Could not disable visual mode. Error at loadTowerAnimation:', e);
             setVisualMode(true);
         })
+        .finally(() => {
+            clearTimeout(timeoutId);
+        });
 }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleDisable = useCallback(() => {
setVisualMode(false);
removeTowerAnimation({ canvasId: TOWER_CANVAS_ID })
.then(() => {
// Force garbage collection to clean up WebGL context
if (window.gc) {
window.gc();
}
})
.catch((e) => {
console.error('Could not disable visual mode. Error at loadTowerAnimation:', e);
setVisualMode(true);
});
}, []);
const handleDisable = useCallback(() => {
const timeoutId = setTimeout(() => {
console.error('Tower animation cleanup timed out');
setVisualMode(false);
}, 5000);
setVisualMode(false);
removeTowerAnimation({ canvasId: TOWER_CANVAS_ID })
.then(() => {
// Force garbage collection to clean up WebGL context
if (window.gc) {
window.gc();
}
})
.catch((e) => {
console.error('Could not disable visual mode. Error at loadTowerAnimation:', e);
setVisualMode(true);
})
.finally(() => {
clearTimeout(timeoutId);
});
}, []);

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/store/useAppConfigStore.ts (2)

308-317: ⚠️ Potential issue

Add cleanup for tower animation resources.

To prevent the reported memory leaks when toggling visual mode, implement proper cleanup of animation resources.

This issue was previously identified. Apply the suggested cleanup implementation:

 if (appConfig.visual_mode && !canvasElement) {
+    let cleanupAnimation: (() => void) | undefined;
     try {
-        await loadTowerAnimation({ canvasId: TOWER_CANVAS_ID, offset: sidebarTowerOffset });
+        cleanupAnimation = await loadTowerAnimation({ canvasId: TOWER_CANVAS_ID, offset: sidebarTowerOffset });
     } catch (e) {
         console.error('Error at loadTowerAnimation:', e);
         useAppConfigStore.setState({ visual_mode: false });
+    } finally {
+        return () => cleanupAnimation?.();
     }
 }

327-344: ⚠️ Potential issue

Enhance visual mode toggle with retry mechanism.

The current implementation uses a simple timeout but lacks a retry mechanism, which may not fully address the reported issues with visual mode toggling and persistence.

This issue was previously identified. Apply the suggested retry implementation:

+const VISUAL_MODE_TOGGLE_TIMEOUT_MS = 5000;
+const MAX_RETRY_ATTEMPTS = 3;
+
 export const setVisualMode = (enabled: boolean) => {
+    let timeoutId: NodeJS.Timeout;
+    let retryCount = 0;
+
+    const cleanup = () => {
+        clearTimeout(timeoutId);
+        useAppConfigStore.setState({ visualModeToggleLoading: false });
+    };
+
+    const attemptToggle = () => {
+        timeoutId = setTimeout(() => {
+            if (retryCount < MAX_RETRY_ATTEMPTS) {
+                console.warn(`Visual mode toggle attempt ${retryCount + 1} timed out, retrying...`);
+                retryCount++;
+                attemptToggle();
+            } else {
+                console.error('Visual mode toggle failed after max retries');
+                useAppConfigStore.setState({ visual_mode: !enabled });
+                cleanup();
+            }
+        }, VISUAL_MODE_TOGGLE_TIMEOUT_MS);
+
+        invoke('set_visual_mode', { enabled })
+            .then(() => {
+                clearTimeout(timeoutId);
+                cleanup();
+            })
+            .catch((e) => {
+                const appStateStore = useAppStateStore.getState();
+                console.error('Could not set visual mode', e);
+                appStateStore.setError('Could not change visual mode');
+                useAppConfigStore.setState({ visual_mode: !enabled });
+                cleanup();
+            });
+    };
+
     useAppConfigStore.setState({ visual_mode: enabled, visualModeToggleLoading: true });
-    invoke('set_visual_mode', { enabled })
-        .catch((e) => {
-            const appStateStore = useAppStateStore.getState();
-            console.error('Could not set visual mode', e);
-            appStateStore.setError('Could not change visual mode');
-            useAppConfigStore.setState({ visual_mode: !enabled });
-        })
-        .finally(() => {
-            if (loadingTimeout) {
-                clearTimeout(loadingTimeout);
-            }
-            loadingTimeout = setTimeout(() => {
-                useAppConfigStore.setState({ visualModeToggleLoading: false });
-            }, 3500);
-        });
+    attemptToggle();
 };
🧹 Nitpick comments (1)
src/store/useAppConfigStore.ts (1)

337-342: Consider reducing the loading timeout duration.

The current timeout of 3500ms might be too long for users, especially when combined with the retry mechanism.

-            loadingTimeout = setTimeout(() => {
-                useAppConfigStore.setState({ visualModeToggleLoading: false });
-            }, 3500);
+            loadingTimeout = setTimeout(() => {
+                useAppConfigStore.setState({ visualModeToggleLoading: false });
+            }, 2000);
📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7aea559 and ab1c5c9.

📒 Files selected for processing (3)
  • index.html (1 hunks)
  • src/store/useAppConfigStore.ts (4 hunks)
  • src/store/useBlockchainVisualisationStore.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/store/useBlockchainVisualisationStore.ts
🧰 Additional context used
🧠 Learnings (1)
index.html (1)
Learnt from: shanimal08
PR: tari-project/universe#1457
File: index.html:184-187
Timestamp: 2025-02-12T07:12:44.355Z
Learning: The canvas cleanup and DOM manipulation for the tower animation is handled by the `@tari-project/tari-tower` package, which completely removes the canvas element when visual mode is disabled.
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: tauri-build
  • GitHub Check: machete
  • GitHub Check: cargo checks (fmt, clippy, check)
🔇 Additional comments (4)
index.html (2)

191-192: LGTM! Clean and minimal body structure.

The simplified DOM structure with TypeScript integration looks good. The removal of the <main> tag and transition to modular TypeScript code aligns well with the project's modernization goals.


184-187:

❓ Verification inconclusive

Verify pointer events behavior with the tower package.

The pointer-events: auto setting allows the canvas to receive mouse/touch events. Please verify that this aligns with the interaction requirements of the @tari-project/tari-tower package.


🌐 Web query:

Does @tari-project/tari-tower package require pointer-events: auto for touch/mouse interaction?

💡 Result:

The available search results do not provide explicit information about whether the @tari-project/tari-tower package requires pointer-events: auto for touch/mouse interaction. Here's what we can determine from the evidence:

  1. Tari Tower Package Details ([4]):

    • The GitHub repository describes it as a tower animation component for Tari Universe
    • Installation instructions show an npm-based workflow
    • No configuration details or CSS requirements are mentioned in the provided content
  2. General Pointer Events Context ([3][6][9]):

    • pointer-events: auto is the default CSS value that allows normal interaction with elements
    • This property controls whether elements can:
      • Be targeted by mouse/touch/pen events
      • Trigger hover states
      • Capture click/drag interactions
  3. Interaction Patterns ([2][5]):

    • Touch/mouse implementations typically require event handlers for:
      • pointerdown/touchstart
      • pointermove/touchmove
      • pointerup/touchend
    • No specific CSS requirements are mandated by W3C standards for basic pointer interaction

Without explicit documentation from Tari Tower's source code or official docs in the search results, we cannot definitively state its CSS requirements. However, most interactive web components using pointer/touch events would need either:

  • Default pointer-events: auto behavior
  • Explicit styling if containing elements block pointer propagation

For definitive answers, consult:

npm view @tari-project/tari-tower peerDependencies

or inspect the component's source code for CSS declarations related to pointer events[4].

Citations:


Confirm Pointer Events Setup for #tower-canvas

The pointer-events: auto style is standard for ensuring that the canvas receives touch and mouse interactions. Our investigation did not reveal any explicit requirements from the @tari-project/tari-tower package that would mandate a different pointer-events configuration. However, please verify through practical testing that the current setup meets the interaction needs without unintended side effects.

src/store/useAppConfigStore.ts (2)

16-18: LGTM! Good addition of loading state.

The addition of visualModeToggleLoading to the State type is a good practice for tracking async operations.


1-1:

❓ Verification inconclusive

Verify imports and their versions.

The new imports from @tari-project/tari-tower and UI store need version verification to ensure compatibility and prevent the reported white screen issues.

Also applies to: 10-10, 14-14


🌐 Web query:

What is the latest version of @tari-project/tari-tower package?

💡 Result:

The latest version of the @tari-project/tari-tower package is v0.0.9, released 2 days ago as of February 14, 2025[1]. This information comes directly from its GitHub repository, which lists this version under the "Releases" section with a timestamp matching the current date context[1].

For developers using this library, note that it provides animation control methods like loadTowerAnimation, setAnimationState, and utilities for managing canvas-based animations in Tari Universe applications[1].

Citations:


Import Version Verification: Confirm @tari-project/tari-tower (v0.0.9) and UI store Compatibility

  • The web query confirms that the latest version of @tari-project/tari-tower is v0.0.9.
  • Please ensure that the import for @tari-project/tari-tower (and its usage) in the codebase aligns with version v0.0.9.
  • Also, verify that the version of the UI store imported at lines 10 and 14 is compatible with this update to avoid any white screen issues.

@@ -1,3 +1,4 @@
let loadingTimeout: NodeJS.Timeout | undefined;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove unused timeout variable.

After implementing the retry mechanism, this global timeout variable becomes unnecessary.

-let loadingTimeout: NodeJS.Timeout | undefined;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let loadingTimeout: NodeJS.Timeout | undefined;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a-blocktower-animations a-ui dependencies Pull requests that update a dependency file javascript Pull requests that update Javascript code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants