How to Troubleshoot Filmstrip Cache Misses and Memory Pressure Issues in Clypra

Clypra employs a multi-layered caching system combining a viewport-bounded FilmstripCache with a tile-addressable FilmstripTileCache to render video thumbnails efficiently; diagnosing issues requires monitoring memory budgets, velocity states, and LRU eviction cycles through the exposed debugging APIs.

Clypra's filmstrip rendering pipeline relies on a sophisticated interplay between a filmstrip cache, a tile-addressable tile cache, and a viewport-bounded request scheduler to deliver smooth thumbnail scrolling. When thumbnails flicker, disappear, or consume excessive memory, developers must understand how these layers interact to troubleshoot filmstrip cache misses and memory pressure issues in Clypra effectively.

Understanding the Filmstrip Cache Architecture

Core Components

The rendering stack spans several modules:

Memory Management Strategy

Clypra implements strict memory controls:

  • Hard budget: Default 100 MiB configured via new FilmstripCache(100).
  • LRU eviction: When memory exceeds the budget, the oldest clip entry (by lastViewportUpdate) is purged via _evictLRU.
  • Tile cache isolation: FilmstripTileCache maintains a separate budget contributing to overall utilization reported by getStats.

Cache-Miss Handling Mechanisms

The system optimizes for perceived performance:

  • Viewport-bounded generation: Only tiles intersecting the visible area (plus overscan) are requested via generateViewportFilmstripTimestamps and generateViewportTileAddresses.
  • Aggressive cheating: During fast scrolling (velocityState >= VelocityState.Fast), the cache falls back to the nearest cached tile through _buildArtifactsFromTiles to prevent blank UI states.
  • Debounced requests: Duplicate tile addresses requested within ~100 ms reuse cached entries without triggering new transport requests.

Common Symptoms and Diagnostic Patterns

Symptom Likely Root Cause
Blank or flickering filmstrip during scrolling Tile cache miss under high velocity, or incorrect velocityState triggering frequent re-requests.
Out-of-memory warnings Memory budget too low, excessive clips retained, or FilmstripTileCache failing to evict via tileCache.dispose.
UI stutter or rerender storms Missing RAF batching in scheduleArtifactUpdate or excessive pending artifacts.
Stale thumbnails after clip removal Missing invalidateClip call when the store deletes a clip without notifying the cache.
Artifacts never appearing Silent failure in requestProgressiveTiers due to epochId mismatches.

Step-by-Step Troubleshooting Guide

  1. Verify Cache Budget Utilization

    Check if the cache is nearing its limit:

    const stats = renderEngine.filmstripCache.getStats();
    console.log('Filmstrip cache stats', stats);

    If memoryMB approaches budgetMB (above 90%) with high utilizationPercent, increase the budget in the RenderEngine constructor: new RenderEngine({ filmstripMemoryMB: 200 }).

  2. Validate Velocity State Propagation

    Ensure RenderEngine updates filmstripCache.setVelocityState on every scroll or zoom event. A stale state prevents the "aggressive cheating" fallback during fast scrolling. Verify this in src/lib/renderEngine/RenderEngine.ts around line 80.

  3. Detect Cache Misses

    Enable debug logging in FilmstripCache._buildArtifactsFromTiles:

    console.debug('[FilmstripCache] cache miss for tile', addr);

    Alternatively, monitor the temporary logger output while reproducing the issue to identify specific tile addresses failing to resolve.

  4. Inspect Tile Address Generation

    Validate that generateViewportTileAddresses returns non-empty arrays for visible clips. If it returns [] (lines 92-94 in the source), the viewport parameters (viewportScrollLeft, viewportWidth, pixelsPerSecond) may diverge from the actual UI layout.

  5. Force LRU Eviction Testing

    Manually trigger eviction to verify stale tile cleanup:

    renderEngine.filmstripCache._evictLRU(); // private method for debugging only

    Observe whether fresh tiles are requested and displayed correctly after eviction.

  6. Confirm Clip Invalidation

    When clips are removed or epochs change, verify that invalidateClip is called. Search for invalidateClip( in timelineStore or recordingStore to ensure proper lifecycle integration.

  7. Monitor RAF Batching

    Excessive scheduleArtifactUpdate calls without flushPendingArtifacts indicate a lost rafId. Ensure requestAnimationFrame is not being throttled by polyfills that limit frame rates.

  8. Analyze Tile Cache Statistics

    Inspect tile-level metrics:

    const tileStats = renderEngine.filmstripCache.tileCache.getStats();
    console.log('Tile cache', tileStats);

    Low tileUtilizationPercent suggests premature eviction causing frequent cache misses.

  9. Isolate with Minimal Reproduction

    Create a timeline with a single short clip, set the viewport to cover it fully, and observe the filmstrip. Gradually add clips or increase zoom to identify the threshold where issues manifest.

  10. Run Unit Test Coverage

    Execute the repository's test suite to surface regressions:

    npm run test -- src/lib/__tests__/useFilmstrip.test.ts
    npm run test -- src/lib/filmstrip/__tests__/FilmstripTileCache.test.ts

Practical Debugging Code Examples

Adjusting the Memory Budget

Increase the filmstrip cache capacity when deploying to high-memory devices:

import { RenderEngine } from './core/renderEngine/renderEngine';

const engine = new RenderEngine({
  filmstripMemoryMB: 200, // default is 100 MiB
});

Inspecting Runtime Statistics

Monitor cache health in your debug UI:

const stats = engine.filmstripCache.getStats();
console.table({
  ClipCount: stats.clipCount,
  MemoryUsed: `${stats.memoryMB} MiB`,
  Budget: `${stats.budgetMB} MiB`,
  Utilisation: `${stats.utilizationPercent}%`,
  TileCount: stats.tileCount,
});

Logging Cache Misses

Patch the prototype for development debugging:

const originalBuild = FilmstripCache.prototype._buildArtifactsFromTiles;
FilmstripCache.prototype._buildArtifactsFromTiles = function (addresses, clipId, tier, videoPath) {
  const artifacts = originalBuild.call(this, addresses, clipId, tier, videoPath);
  if (artifacts.length < addresses.length) {
    console.warn('[Debug] Filmstrip cache miss:', {
      clipId,
      missing: addresses.length - artifacts.length,
    });
  }
  return artifacts;
};

Forcing Complete Cache Clearance

Dispose the entire cache when suspecting memory leaks:

engine.filmstripCache.dispose();
// Re-initialize if continued rendering is required
engine.filmstripCache = new FilmstripCache(200);

Summary

  • Clypra's filmstrip rendering relies on FilmstripCache and FilmstripTileCache working in tandem with a viewport-bounded request scheduler.
  • Memory pressure arises when the 100 MiB default budget is exceeded or when LRU eviction fails to trigger via _evictLRU.
  • Cache misses typically indicate incorrect velocityState propagation, mismatched viewport parameters, or missing tile addresses in generateViewportTileAddresses.
  • Use getStats() to monitor utilization and invalidateClip() to prevent stale data when clips are removed.
  • The React hook useFilmstrip.ts provides the UI subscription layer, while renderEngine.ts manages the velocity state updates critical for fallback rendering during fast scrolling.

Frequently Asked Questions

Why do thumbnails disappear when I scroll quickly?

Rapid scrolling triggers velocityState checks in FilmstripCache.ts. If the state is not set to VelocityState.Fast or the fallback mechanism _buildArtifactsFromTiles cannot locate nearest cached tiles, the UI renders blank spaces. Verify that your scroll handler calls setVelocityState with the correct enum value from RenderEngine.ts.

How do I know if memory pressure is causing performance issues?

Call renderEngine.filmstripCache.getStats() and check if memoryMB exceeds 90% of budgetMB. High utilization forces aggressive LRU eviction in _evictLRU, which can cause stuttering as the system purges and rebuilds the cache. Consider increasing filmstripMemoryMB in the constructor or investigating tileCache.dispose calls that might not be releasing memory.

What causes the "rerender storm" in the filmstrip component?

This occurs when scheduleArtifactUpdate is invoked repeatedly without proper RAF batching. Ensure that flushPendingArtifacts is being called within the animation frame loop. Check that requestAnimationFrame is not being intercepted by polyfills that limit the frame rate, as this can cause the rafId tracking to fail.

Why do deleted clips still show thumbnails in the filmstrip?

The cache retains tiles until invalidateClip is called with the specific clip ID. If your timelineStore or recordingStore removes clips without notifying the cache, zombie thumbnails persist. Audit your data layer to ensure invalidateClip(clipId) is invoked whenever clip epochs change or deletions occur.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →