UNPKG

advanced-games-library

Version:

Advanced Gaming Library for React Native - Four Complete Games with iOS Compatibility Fixes

505 lines (437 loc) 12.4 kB
/** * Advanced caching system for the games library */ export interface CacheEntry<T> { key: string; value: T; timestamp: Date; ttl: number; // Time to live in milliseconds accessCount: number; lastAccessed: Date; size: number; // Approximate size in bytes tags: string[]; } export interface CacheStats { totalEntries: number; totalSize: number; hitRate: number; missRate: number; evictionCount: number; oldestEntry: Date | null; newestEntry: Date | null; } export enum CacheStrategy { LRU = 'lru', // Least Recently Used LFU = 'lfu', // Least Frequently Used TTL = 'ttl', // Time To Live FIFO = 'fifo' // First In First Out } export interface CacheConfig { maxSize: number; // Maximum cache size in bytes maxEntries: number; // Maximum number of entries defaultTTL: number; // Default TTL in milliseconds strategy: CacheStrategy; compressionEnabled: boolean; persistToDisk: boolean; } /** * Advanced Cache Manager */ export class CacheManager<T = any> { private cache = new Map<string, CacheEntry<T>>(); private config: CacheConfig; private stats = { hits: 0, misses: 0, evictions: 0, totalSize: 0 }; constructor(config: Partial<CacheConfig> = {}) { this.config = { maxSize: 50 * 1024 * 1024, // 50MB default maxEntries: 1000, defaultTTL: 60 * 60 * 1000, // 1 hour default strategy: CacheStrategy.LRU, compressionEnabled: false, persistToDisk: false, ...config }; } /** * Set a value in the cache */ async set( key: string, value: T, options?: { ttl?: number; tags?: string[]; compress?: boolean; } ): Promise<void> { const ttl = options?.ttl || this.config.defaultTTL; const tags = options?.tags || []; let processedValue = value; if (this.config.compressionEnabled || options?.compress) { processedValue = await this.compress(value); } const size = this.calculateSize(processedValue); const entry: CacheEntry<T> = { key, value: processedValue, timestamp: new Date(), ttl, accessCount: 0, lastAccessed: new Date(), size, tags }; // Check if we need to evict entries await this.ensureCapacity(size); // Remove existing entry if it exists if (this.cache.has(key)) { const oldEntry = this.cache.get(key)!; this.stats.totalSize -= oldEntry.size; } // Add new entry this.cache.set(key, entry); this.stats.totalSize += size; // Persist to disk if enabled if (this.config.persistToDisk) { await this.persistEntry(entry); } } /** * Get a value from the cache */ async get(key: string): Promise<T | null> { const entry = this.cache.get(key); if (!entry) { this.stats.misses++; // Try to load from disk if persistence is enabled if (this.config.persistToDisk) { const diskEntry = await this.loadFromDisk(key); if (diskEntry) { this.cache.set(key, diskEntry); this.updateAccessStats(diskEntry); return await this.decompress(diskEntry.value); } } return null; } // Check if entry has expired if (this.isExpired(entry)) { this.cache.delete(key); this.stats.totalSize -= entry.size; this.stats.misses++; return null; } // Update access statistics this.updateAccessStats(entry); this.stats.hits++; // Decompress if needed const value = await this.decompress(entry.value); return value; } /** * Check if a key exists in the cache */ has(key: string): boolean { const entry = this.cache.get(key); if (!entry) return false; if (this.isExpired(entry)) { this.cache.delete(key); this.stats.totalSize -= entry.size; return false; } return true; } /** * Delete a specific key */ delete(key: string): boolean { const entry = this.cache.get(key); if (entry) { this.cache.delete(key); this.stats.totalSize -= entry.size; return true; } return false; } /** * Clear all entries */ clear(): void { this.cache.clear(); this.stats.totalSize = 0; this.stats.hits = 0; this.stats.misses = 0; this.stats.evictions = 0; } /** * Clear entries by tag */ clearByTag(tag: string): number { let cleared = 0; const toDelete: string[] = []; this.cache.forEach((entry, key) => { if (entry.tags.includes(tag)) { toDelete.push(key); } }); toDelete.forEach(key => { if (this.delete(key)) { cleared++; } }); return cleared; } /** * Get cache statistics */ getStats(): CacheStats { const entries = Array.from(this.cache.values()); const totalRequests = this.stats.hits + this.stats.misses; return { totalEntries: this.cache.size, totalSize: this.stats.totalSize, hitRate: totalRequests > 0 ? (this.stats.hits / totalRequests) * 100 : 0, missRate: totalRequests > 0 ? (this.stats.misses / totalRequests) * 100 : 0, evictionCount: this.stats.evictions, oldestEntry: entries.length > 0 ? Math.min(...entries.map(e => e.timestamp.getTime())) as any : null, newestEntry: entries.length > 0 ? Math.max(...entries.map(e => e.timestamp.getTime())) as any : null }; } /** * Get all keys */ keys(): string[] { return Array.from(this.cache.keys()); } /** * Get cache size in bytes */ size(): number { return this.stats.totalSize; } /** * Ensure cache capacity before adding new entry */ private async ensureCapacity(newEntrySize: number): Promise<void> { // Check size limit while (this.stats.totalSize + newEntrySize > this.config.maxSize) { await this.evictEntry(); } // Check entry count limit while (this.cache.size >= this.config.maxEntries) { await this.evictEntry(); } } /** * Evict an entry based on the configured strategy */ private async evictEntry(): Promise<void> { if (this.cache.size === 0) return; let keyToEvict: string; const entries = Array.from(this.cache.entries()); switch (this.config.strategy) { case CacheStrategy.LRU: // Evict least recently used keyToEvict = entries.reduce((oldest, [key, entry]) => { const oldestEntry = this.cache.get(oldest); return !oldestEntry || entry.lastAccessed < oldestEntry.lastAccessed ? key : oldest; }, entries[0][0]); break; case CacheStrategy.LFU: // Evict least frequently used keyToEvict = entries.reduce((leastUsed, [key, entry]) => { const leastUsedEntry = this.cache.get(leastUsed); return !leastUsedEntry || entry.accessCount < leastUsedEntry.accessCount ? key : leastUsed; }, entries[0][0]); break; case CacheStrategy.TTL: // Evict earliest expiring entry keyToEvict = entries.reduce((earliest, [key, entry]) => { const earliestEntry = this.cache.get(earliest); const currentExpiry = entry.timestamp.getTime() + entry.ttl; const earliestExpiry = earliestEntry ? earliestEntry.timestamp.getTime() + earliestEntry.ttl : Infinity; return currentExpiry < earliestExpiry ? key : earliest; }, entries[0][0]); break; case CacheStrategy.FIFO: // Evict first in (oldest timestamp) keyToEvict = entries.reduce((oldest, [key, entry]) => { const oldestEntry = this.cache.get(oldest); return !oldestEntry || entry.timestamp < oldestEntry.timestamp ? key : oldest; }, entries[0][0]); break; default: keyToEvict = entries[0][0]; } this.delete(keyToEvict); this.stats.evictions++; } /** * Check if an entry has expired */ private isExpired(entry: CacheEntry<T>): boolean { const now = Date.now(); const expiryTime = entry.timestamp.getTime() + entry.ttl; return now > expiryTime; } /** * Update access statistics for an entry */ private updateAccessStats(entry: CacheEntry<T>): void { entry.accessCount++; entry.lastAccessed = new Date(); } /** * Calculate approximate size of a value */ private calculateSize(value: T): number { try { return JSON.stringify(value).length * 2; // Rough estimate (UTF-16) } catch { return 100; // Fallback estimate } } /** * Compress a value (placeholder implementation) */ private async compress(value: T): Promise<T> { // In a real implementation, this would use compression algorithms // For now, just return the value as-is return value; } /** * Decompress a value (placeholder implementation) */ private async decompress(value: T): Promise<T> { // In a real implementation, this would decompress the value // For now, just return the value as-is return value; } /** * Persist entry to disk (placeholder implementation) */ private async persistEntry(entry: CacheEntry<T>): Promise<void> { // This would save to AsyncStorage or file system // For now, just a placeholder if (__DEV__) { console.log('Persisting to disk:', entry.key); } } /** * Load entry from disk (placeholder implementation) */ private async loadFromDisk(key: string): Promise<CacheEntry<T> | null> { // This would load from AsyncStorage or file system // For now, just return null return null; } } /** * Specialized cache for game data */ export class GameDataCache extends CacheManager<any> { constructor() { super({ maxSize: 20 * 1024 * 1024, // 20MB for game data maxEntries: 500, defaultTTL: 30 * 60 * 1000, // 30 minutes strategy: CacheStrategy.LRU, compressionEnabled: true, persistToDisk: true }); } /** * Cache game state */ async cacheGameState(gameId: string, state: any): Promise<void> { await this.set(`game_state_${gameId}`, state, { ttl: 10 * 60 * 1000, // 10 minutes tags: ['game_state', gameId] }); } /** * Get cached game state */ async getGameState(gameId: string): Promise<any | null> { return await this.get(`game_state_${gameId}`); } /** * Cache player data */ async cachePlayerData(playerId: string, data: any): Promise<void> { await this.set(`player_${playerId}`, data, { ttl: 60 * 60 * 1000, // 1 hour tags: ['player_data', playerId] }); } /** * Get cached player data */ async getPlayerData(playerId: string): Promise<any | null> { return await this.get(`player_${playerId}`); } /** * Cache game results */ async cacheGameResults(playerId: string, results: any[]): Promise<void> { await this.set(`results_${playerId}`, results, { ttl: 2 * 60 * 60 * 1000, // 2 hours tags: ['game_results', playerId] }); } /** * Get cached game results */ async getGameResults(playerId: string): Promise<any[] | null> { return await this.get(`results_${playerId}`); } /** * Clear all data for a specific game */ clearGameData(gameId: string): number { return this.clearByTag(gameId); } /** * Clear all data for a specific player */ clearPlayerData(playerId: string): number { return this.clearByTag(playerId); } } /** * Cache decorator for methods */ export function cached( keyGenerator: (...args: any[]) => string, ttl: number = 60000, tags: string[] = [] ) { const cache = new CacheManager(); return function (target: any, propertyName: string, descriptor: PropertyDescriptor) { const method = descriptor.value; descriptor.value = async function (...args: any[]) { const key = keyGenerator(...args); // Try to get from cache first const cached = await cache.get(key); if (cached !== null) { return cached; } // Execute method and cache result const result = await method.apply(this, args); await cache.set(key, result, { ttl, tags }); return result; }; return descriptor; }; } // Export singleton instances export const gameDataCache = new GameDataCache(); export const globalCache = new CacheManager();