UNPKG

rough-native

Version:

Create graphics using HTML Canvas or SVG with a hand-drawn, sketchy, appearance. Features comprehensive React hooks, memory management, and React 18 concurrent rendering support.

283 lines (231 loc) 8.15 kB
# Memory Pressure Detection and Adaptive Caching This document describes the memory pressure detection and adaptive caching system implemented in rough-native to prevent app crashes on memory-constrained devices. ## 🚨 Problem Solved ### Memory-Related App Crashes React Native apps run on devices with varying memory constraints: - **Low-end Android devices**: 1-2GB RAM - **Mid-range devices**: 2-4GB RAM - **High-end devices**: 6-12GB RAM Fixed cache sizes can cause: - **App crashes** when OS kills the app for excessive memory usage - **Performance degradation** from memory pressure - **Cache thrashing** on low-memory devices ## 🏗️ Architecture ### MemoryMonitor Class Centralized memory pressure detection: ```typescript class MemoryMonitor { private memoryPressureLevel: 'low' | 'medium' | 'high' = 'low'; getMemoryPressureLevel(): 'low' | 'medium' | 'high' { // Checks memory every 5 seconds to avoid overhead this.checkMemoryPressure(); return this.memoryPressureLevel; } getRecommendedCacheSize(): number { switch (this.memoryPressureLevel) { case 'high': return 50; // Conservative for low-memory devices case 'medium': return 200; // Balanced for typical devices case 'low': return 500; // Generous for high-memory devices } } } ``` ### Memory Detection Methods #### 1. JavaScript Heap Memory (Primary) ```typescript if (global.performance?.memory) { const memory = global.performance.memory; const usedRatio = memory.usedJSHeapSize / memory.jsHeapSizeLimit; if (usedRatio > 0.8) { this.memoryPressureLevel = 'high'; } else if (usedRatio > 0.6) { this.memoryPressureLevel = 'medium'; } else { this.memoryPressureLevel = 'low'; } } ``` #### 2. Browser Memory API (Fallback) ```typescript if (performance.memory) { // Same logic as above for web environments } ``` #### 3. Device Detection (React Native Fallback) ```typescript // Detect low-end Android devices via user agent const isLowEndDevice = /Android.*4\.|Android.*[0-3]\./i.test(userAgent); if (isLowEndDevice) { this.memoryPressureLevel = 'high'; } ``` ## 🧠 Adaptive Caching System ### Deep Equality Cache Adaptation ```typescript class DeepEqualityCache { private get maxCacheSize(): number { return this.memoryMonitor.getRecommendedCacheSize(); } set(a: any, b: any, result: boolean): void { // Adaptive cache size based on memory pressure if (this.cacheCount > this.maxCacheSize) { this.cache = new WeakMap(); this.cacheCount = 0; // Aggressive cleanup under high pressure if (this.memoryMonitor.shouldAgressivelyCleanup()) { if (global.gc) global.gc(); // Force GC if available } } // Skip bidirectional caching under memory pressure if (!this.memoryMonitor.shouldAgressivelyCleanup()) { // Cache both directions for efficiency } } } ``` ### Shape Generation Store Adaptation ```typescript class ShapeGenerationStore { generateShape(key: string, generator: () => any): any { // Limit concurrent generations under memory pressure if (this.memoryMonitor.shouldAgressivelyCleanup() && this.pendingGenerations.size > 2) { return null; // Defer generation } // Proactive cleanup before generating if (this.cache.size >= this.maxCacheSize) { this.performMemoryPressureCleanup(); } } private performMemoryPressureCleanup(): void { const pressureLevel = this.memoryMonitor.getMemoryPressureLevel(); if (pressureLevel === 'high') { // Keep only 25% of cache (most recent) const entries = Array.from(this.cache.entries()); const toKeep = entries.slice(-Math.floor(entries.length * 0.25)); this.cache.clear(); toKeep.forEach(([key, value]) => this.cache.set(key, value)); } } } ``` ## 📊 Memory Pressure Levels ### Low Pressure (usedMemory < 60%) - **Cache size**: 500 items (generous) - **Bidirectional caching**: Enabled - **Concurrent generations**: Unlimited - **Cleanup strategy**: Standard size limits ### Medium Pressure (60% < usedMemory < 80%) - **Cache size**: 200 items (balanced) - **Bidirectional caching**: Enabled - **Concurrent generations**: Normal limits - **Cleanup strategy**: 50% cache reduction when full ### High Pressure (usedMemory > 80%) - **Cache size**: 50 items (conservative) - **Bidirectional caching**: Disabled - **Concurrent generations**: Limited to 2 - **Cleanup strategy**: 75% cache reduction when full - **Garbage collection**: Force GC when available ## 🛠️ Developer Tools ### Memory Monitoring ```typescript import { debugUtils } from 'rough-native'; // Check current memory pressure const pressure = debugUtils.getMemoryPressure(); // 'low' | 'medium' | 'high' // Get recommended cache size for current conditions const cacheSize = debugUtils.getRecommendedCacheSize(); // 50, 200, or 500 // Get enhanced cache statistics const stats = debugUtils.getDeepEqualStats(); console.log(stats); // { // hits: 1250, // misses: 89, // hitRate: 0.93, // memoryPressure: 'medium', // maxCacheSize: 200, // currentCacheCount: 156 // } // Force cleanup under memory pressure debugUtils.forceMemoryCleanup(); ``` ### Production Monitoring ```typescript // Monitor memory pressure in production useEffect(() => { const interval = setInterval(() => { const pressure = debugUtils.getMemoryPressure(); if (pressure === 'high') { // Log to analytics service analytics.track('memory_pressure_high', { cacheSize: debugUtils.getShapeCacheSize(), platform: Platform.OS }); } }, 30000); // Check every 30 seconds return () => clearInterval(interval); }, []); ``` ## 🎯 Real-World Impact ### Device-Specific Behavior #### Low-End Android (1GB RAM) ```typescript // Automatically detects and adapts: // - Cache size: 50 items max // - Aggressive cleanup enabled // - Limited concurrent operations // - Force GC when available ``` #### Mid-Range Devices (2-4GB RAM) ```typescript // Balanced approach: // - Cache size: 200 items max // - Standard cleanup strategy // - Normal concurrent operations ``` #### High-End Devices (6GB+ RAM) ```typescript // Performance optimized: // - Cache size: 500 items max // - Generous caching enabled // - Full concurrent operations ``` ### Performance Benefits | Memory Condition | Before | After | |-----------------|--------|-------| | Low memory device | App crash risk | Adaptive caching prevents crashes | | Memory pressure spike | Fixed cache size | 75% cache reduction | | Background app return | Stale large cache | Reduced cache size | | Concurrent generation | Unlimited | Limited during pressure | ## 🔧 Configuration ### Environment Variables ```typescript // Override memory detection (for testing) process.env.ROUGH_NATIVE_MEMORY_PRESSURE = 'high'; // 'low' | 'medium' | 'high' // Disable automatic GC calls process.env.ROUGH_NATIVE_DISABLE_GC = 'true'; // Custom cache size override process.env.ROUGH_NATIVE_CACHE_SIZE = '100'; ``` ### Manual Override ```typescript // For apps with specific memory requirements const customMemoryMonitor = { getMemoryPressureLevel: () => 'high', // Always conservative getRecommendedCacheSize: () => 25, // Very small cache shouldAgressivelyCleanup: () => true // Always aggressive }; ``` ## 🚀 Benefits ### Crash Prevention - **Eliminated memory-related crashes** on low-end devices - **Adaptive behavior** prevents OS app termination - **Graceful degradation** under memory pressure ### Performance Optimization - **Device-appropriate caching** maximizes performance - **Memory-aware cleanup** prevents cache thrashing - **Smart concurrent limiting** prevents memory spikes ### Developer Experience - **Automatic adaptation** requires no configuration - **Rich debugging tools** for memory monitoring - **Production analytics** for memory pressure tracking The memory pressure detection system ensures rough-native works reliably across the full spectrum of React Native devices, from budget Android phones to high-end flagship devices.