nativescript-matrix-sdk
Version:
Native Matrix SDK integration for NativeScript
308 lines (270 loc) • 9.3 kB
text/typescript
/**
* Base Memory Optimizer
* Provides common functionality for platform-specific memory optimizers
*/
import { Logger } from './logger';
/**
* Cache interface for anything that can be cleared
*/
export interface Clearable {
clear(): void;
}
/**
* Strategy for memory cleanup
*/
export enum MemoryCleanupStrategy {
LIGHT = 'light', // Clear only non-essential caches
MODERATE = 'moderate', // Clear all caches except current view
AGGRESSIVE = 'aggressive' // Clear everything, including current view cache
}
/**
* Memory condition levels
*/
export enum MemoryCondition {
NORMAL = 'normal',
LOW = 'low',
CRITICAL = 'critical'
}
/**
* Device memory profile
*/
export enum DeviceMemoryProfile {
LOW_END = 'low-end', // Devices with <= 2GB RAM
MID_RANGE = 'mid-range', // Devices with 3-4GB RAM
HIGH_END = 'high-end' // Devices with >= 6GB RAM
}
/**
* Memory threshold configuration
*/
export interface MemoryThresholds {
lowMemoryThresholdMB: number;
criticalMemoryThresholdMB: number;
}
/**
* Base class for memory optimizers
*/
export abstract class MemoryOptimizerBase {
protected readonly lowMemoryThresholdMB: number;
protected readonly criticalMemoryThresholdMB: number;
protected deviceProfile: DeviceMemoryProfile;
private memoryCondition: MemoryCondition = MemoryCondition.NORMAL;
private caches: Map<string, Clearable> = new Map();
private lastCleanupTime = 0;
private cleanupThrottleMs = 5000; // Prevent cleanup more often than every 5 seconds
constructor(lowMemoryThresholdMB = 100, criticalMemoryThresholdMB = 50) {
// Determine device profile and adjust thresholds accordingly
this.deviceProfile = this.detectDeviceProfile();
const adjustedThresholds = this.getAdaptiveThresholds(
lowMemoryThresholdMB,
criticalMemoryThresholdMB
);
this.lowMemoryThresholdMB = adjustedThresholds.lowMemoryThresholdMB;
this.criticalMemoryThresholdMB = adjustedThresholds.criticalMemoryThresholdMB;
Logger.info(
`[MemoryOptimizerBase] Initialized for ${this.deviceProfile} device with` +
` low threshold: ${this.lowMemoryThresholdMB}MB,` +
` critical threshold: ${this.criticalMemoryThresholdMB}MB`
);
// Start platform-specific monitoring
this.startMemoryMonitoring();
// Start periodic checks
this.startPeriodicChecks();
}
/**
* Check if memory is low
*/
public isMemoryLow(): boolean {
return this.memoryCondition !== MemoryCondition.NORMAL;
}
/**
* Check if memory is critically low
*/
public isMemoryCritical(): boolean {
return this.memoryCondition === MemoryCondition.CRITICAL;
}
/**
* Set the current memory condition
*/
protected setMemoryCondition(condition: MemoryCondition): void {
if (this.memoryCondition !== condition) {
this.memoryCondition = condition;
switch (condition) {
case MemoryCondition.CRITICAL:
Logger.warn('[MemoryOptimizerBase] CRITICAL memory condition detected');
this.releaseMemory(MemoryCleanupStrategy.AGGRESSIVE);
break;
case MemoryCondition.LOW:
Logger.info('[MemoryOptimizerBase] LOW memory condition detected');
this.releaseMemory(MemoryCleanupStrategy.MODERATE);
break;
case MemoryCondition.NORMAL:
Logger.debug('[MemoryOptimizerBase] Memory condition returned to NORMAL');
break;
}
}
}
/**
* Register a cache that can be cleared
* @param name Unique name for the cache
* @param cache The cache object
* @param priority Priority level for when this cache should be cleared (1-10, higher = cleared earlier)
*/
public registerCache(name: string, cache: Clearable, priority = 5): void {
this.caches.set(`${priority.toString().padStart(2, '0')}_${name}`, cache);
Logger.debug(`[MemoryOptimizerBase] Registered cache: ${name} (priority: ${priority})`);
}
/**
* Unregister a cache
*/
public unregisterCache(name: string, priority = 5): void {
this.caches.delete(`${priority.toString().padStart(2, '0')}_${name}`);
}
/**
* Start platform-specific memory monitoring
*/
protected abstract startMemoryMonitoring(): void;
/**
* Check current memory status
* To be implemented by platform-specific classes
*/
protected abstract checkMemoryStatus(): MemoryCondition;
/**
* Start periodic memory checks
*/
private startPeriodicChecks(): void {
// Check memory status every 30 seconds
setInterval(() => {
const condition = this.checkMemoryStatus();
this.setMemoryCondition(condition);
}, 30000);
}
/**
* Release memory based on strategy
*/
protected releaseMemory(strategy: MemoryCleanupStrategy): void {
// Throttle cleanup operations
const now = Date.now();
if (now - this.lastCleanupTime < this.cleanupThrottleMs) {
Logger.debug(`[MemoryOptimizerBase] Skipping cleanup - throttled (last: ${now - this.lastCleanupTime}ms ago)`);
return;
}
this.lastCleanupTime = now;
Logger.info(`[MemoryOptimizerBase] Releasing memory with strategy: ${strategy}`);
// Sort cache keys by priority
const sortedCacheKeys = Array.from(this.caches.keys()).sort();
// Clear caches based on strategy
let clearedCount = 0;
switch (strategy) {
case MemoryCleanupStrategy.AGGRESSIVE:
// Clear all caches
for (const key of sortedCacheKeys) {
const cache = this.caches.get(key);
if (cache) {
cache.clear();
clearedCount++;
}
}
// Force garbage collection
this.forceGarbageCollection();
break;
case MemoryCleanupStrategy.MODERATE:
// Clear higher priority caches (1-7)
for (const key of sortedCacheKeys) {
if (key.startsWith('0') || key.startsWith('1') ||
key.startsWith('2') || key.startsWith('3') ||
key.startsWith('4') || key.startsWith('5') ||
key.startsWith('6') || key.startsWith('7')) {
const cache = this.caches.get(key);
if (cache) {
cache.clear();
clearedCount++;
}
}
}
break;
case MemoryCleanupStrategy.LIGHT:
// Clear only high priority caches (1-3)
for (const key of sortedCacheKeys) {
if (key.startsWith('0') || key.startsWith('1') ||
key.startsWith('2') || key.startsWith('3')) {
const cache = this.caches.get(key);
if (cache) {
cache.clear();
clearedCount++;
}
}
}
break;
}
Logger.info(`[MemoryOptimizerBase] Released ${clearedCount} caches`);
}
/**
* Platform-specific garbage collection
*/
protected abstract forceGarbageCollection(): void;
/**
* Clean up resources
*/
public cleanup(): void {
this.caches.clear();
Logger.info('[MemoryOptimizerBase] Cleaned up memory optimizer');
}
/**
* Detect the device memory profile based on available RAM
* @returns The device memory profile
*/
protected detectDeviceProfile(): DeviceMemoryProfile {
try {
const totalMemoryMB = this.getTotalDeviceMemoryMB();
if (totalMemoryMB <= 2048) {
return DeviceMemoryProfile.LOW_END;
} else if (totalMemoryMB <= 4096) {
return DeviceMemoryProfile.MID_RANGE;
} else {
return DeviceMemoryProfile.HIGH_END;
}
} catch (error) {
Logger.warn('[MemoryOptimizerBase] Error detecting device profile, defaulting to mid-range');
Logger.error('[MemoryOptimizerBase] Detection error details', error);
return DeviceMemoryProfile.MID_RANGE;
}
}
/**
* Get adaptive memory thresholds based on device profile
* @param baseLowThresholdMB - Base low memory threshold in MB
* @param baseCriticalThresholdMB - Base critical memory threshold in MB
* @returns Adjusted thresholds for the device
*/
protected getAdaptiveThresholds(
baseLowThresholdMB: number,
baseCriticalThresholdMB: number
): MemoryThresholds {
switch (this.deviceProfile) {
case DeviceMemoryProfile.LOW_END:
// More aggressive thresholds for low-end devices
return {
lowMemoryThresholdMB: Math.max(50, baseLowThresholdMB * 0.5),
criticalMemoryThresholdMB: Math.max(25, baseCriticalThresholdMB * 0.5)
};
case DeviceMemoryProfile.HIGH_END:
// More relaxed thresholds for high-end devices
return {
lowMemoryThresholdMB: baseLowThresholdMB * 2,
criticalMemoryThresholdMB: baseCriticalThresholdMB * 2
};
case DeviceMemoryProfile.MID_RANGE:
default:
// Use base thresholds for mid-range
return {
lowMemoryThresholdMB: baseLowThresholdMB,
criticalMemoryThresholdMB: baseCriticalThresholdMB
};
}
}
/**
* Get total device memory in MB
* This is a platform-specific implementation
* @returns The total device memory in MB
*/
protected abstract getTotalDeviceMemoryMB(): number;
}