advanced-games-library
Version:
Advanced Gaming Library for React Native - Four Complete Games with iOS Compatibility Fixes
505 lines (437 loc) • 12.4 kB
text/typescript
/**
* 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();