UNPKG

advanced-games-library

Version:

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

886 lines (749 loc) 24.4 kB
/** * Advanced State Management System - מערכת ניהול מצב מתקדמת * מספקת ניהול מצב חכם עם time-travel, undo/redo, persistence ואופטימיזציות */ export interface StateAction<T = any> { type: string; payload?: T; timestamp: number; id: string; metadata?: { source?: string; priority?: 'low' | 'medium' | 'high' | 'critical'; optimistic?: boolean; rollbackable?: boolean; }; } export interface StateSnapshot<T = any> { id: string; timestamp: number; state: T; actions: StateAction[]; metadata: { version: string; checkpoint: boolean; compressed: boolean; size: number; }; } export interface StateSubscription<T = any> { id: string; selector: (state: T) => any; callback: (selected: any, prevSelected: any, action?: StateAction) => void; options: { fireImmediately?: boolean; debounceMs?: number; equalityCheck?: (a: any, b: any) => boolean; }; } export interface StateMiddleware<T = any> { name: string; before?: (action: StateAction, state: T) => StateAction | null; after?: (action: StateAction, prevState: T, newState: T) => void; error?: (error: Error, action: StateAction, state: T) => void; } export interface StatePersistenceConfig { key: string; storage: 'memory' | 'localStorage' | 'indexedDB'; serialize?: (state: any) => string; deserialize?: (data: string) => any; throttleMs?: number; compress?: boolean; encrypt?: boolean; } export interface StateOptimizationMetrics { memoryUsage: number; snapshotCount: number; actionCount: number; subscriptionCount: number; averageUpdateTime: number; compressionRatio: number; cacheHitRate: number; } class AdvancedStateManager<T = any> { private currentState: T; private actionHistory: StateAction[] = []; private snapshots: StateSnapshot<T>[] = []; private subscriptions: Map<string, StateSubscription<T>> = new Map(); private middleware: StateMiddleware<T>[] = []; private undoStack: StateSnapshot<T>[] = []; private redoStack: StateSnapshot<T>[] = []; private config = { maxHistorySize: 1000, maxSnapshotSize: 50, snapshotInterval: 100, // actions compressionThreshold: 10000, // bytes optimizationInterval: 60000, // ms debounceDefault: 16, // ms (60fps) }; private metrics: StateOptimizationMetrics = { memoryUsage: 0, snapshotCount: 0, actionCount: 0, subscriptionCount: 0, averageUpdateTime: 0, compressionRatio: 1, cacheHitRate: 0 }; private persistenceConfig?: StatePersistenceConfig; private optimizationTimer?: NodeJS.Timeout; private debouncedCallbacks: Map<string, NodeJS.Timeout> = new Map(); private memoizedSelectors: Map<string, { selector: Function; lastState: any; lastResult: any }> = new Map(); constructor(initialState: T, config?: Partial<typeof AdvancedStateManager.prototype.config>) { this.currentState = initialState; this.config = { ...this.config, ...config }; // Create initial snapshot this.createSnapshot('initial', true); // Start optimization cycle this.startOptimizationCycle(); console.log('🔄 Advanced State Manager initialized'); } /** * התחלת מחזור אופטימיזציה */ private startOptimizationCycle(): void { this.optimizationTimer = setInterval(() => { this.optimizeState(); this.updateMetrics(); }, this.config.optimizationInterval); } /** * יצירת snapshot של המצב */ private createSnapshot(reason: string, isCheckpoint: boolean = false): void { const serializedState = JSON.stringify(this.currentState); const compressed = serializedState.length > this.config.compressionThreshold; const snapshot: StateSnapshot<T> = { id: `snapshot_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, timestamp: Date.now(), state: compressed ? this.compressState(this.currentState) : { ...this.currentState }, actions: [...this.actionHistory.slice(-this.config.snapshotInterval)], metadata: { version: '1.0.0', checkpoint: isCheckpoint, compressed, size: serializedState.length } }; this.snapshots.push(snapshot); // Keep snapshots within limit if (this.snapshots.length > this.config.maxSnapshotSize) { // Remove oldest non-checkpoint snapshots const checkpoints = this.snapshots.filter(s => s.metadata.checkpoint); const nonCheckpoints = this.snapshots.filter(s => !s.metadata.checkpoint); if (nonCheckpoints.length > this.config.maxSnapshotSize - checkpoints.length) { nonCheckpoints.shift(); } this.snapshots = [...checkpoints, ...nonCheckpoints].sort((a, b) => a.timestamp - b.timestamp); } this.metrics.snapshotCount = this.snapshots.length; } /** * דחיסת state */ private compressState(state: T): T { try { // Simplified compression - in production use proper compression library const stringified = JSON.stringify(state); const compressed = this.simpleCompress(stringified); this.metrics.compressionRatio = compressed.length / stringified.length; return JSON.parse(this.simpleDecompress(compressed)); } catch (error) { console.warn('State compression failed:', error); return state; } } private simpleCompress(data: string): string { // Simplified compression algorithm return btoa(data); } private simpleDecompress(data: string): string { return atob(data); } /** * שליחת action */ dispatch(type: string, payload?: any, metadata?: StateAction['metadata']): StateAction { const action: StateAction = { type, payload, timestamp: Date.now(), id: `action_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, metadata: { source: 'user', priority: 'medium', rollbackable: true, ...metadata } }; const startTime = performance.now(); try { // Process middleware (before) const processedAction = this.processMiddlewareBefore(action); if (!processedAction) return action; // Action was blocked // Store previous state for undo const prevState = { ...this.currentState }; // Apply action this.currentState = this.reduceState(this.currentState, processedAction); // Add to history this.actionHistory.push(processedAction); if (this.actionHistory.length > this.config.maxHistorySize) { this.actionHistory.shift(); } // Create snapshot if needed if (this.actionHistory.length % this.config.snapshotInterval === 0) { this.createSnapshot('interval'); } // Handle undo/redo if (processedAction.metadata?.rollbackable) { this.undoStack.push({ id: `undo_${Date.now()}`, timestamp: Date.now(), state: prevState, actions: [processedAction], metadata: { version: '1.0.0', checkpoint: false, compressed: false, size: JSON.stringify(prevState).length } }); // Limit undo stack if (this.undoStack.length > 50) { this.undoStack.shift(); } // Clear redo stack this.redoStack.length = 0; } // Process middleware (after) this.processMiddlewareAfter(processedAction, prevState, this.currentState); // Notify subscriptions this.notifySubscriptions(processedAction, prevState); // Persist if configured this.persistState(); // Update metrics const endTime = performance.now(); this.updateActionMetrics(endTime - startTime); return processedAction; } catch (error) { console.error('State dispatch error:', error); this.processMiddlewareError(error as Error, action); throw error; } } /** * הפחתת state (reducer) */ private reduceState(state: T, action: StateAction): T { // This is a generic state reducer - in practice, you'd have specific reducers switch (action.type) { case 'SET_STATE': return { ...state, ...action.payload }; case 'MERGE_STATE': return this.deepMerge(state, action.payload); case 'RESET_STATE': return action.payload || state; case 'UPDATE_NESTED': return this.updateNestedState(state, action.payload); default: // Custom reducer handling would go here return state; } } /** * מיזוג עמוק של objects */ private deepMerge(target: any, source: any): any { if (typeof target !== 'object' || typeof source !== 'object') { return source; } const result = { ...target }; Object.keys(source).forEach(key => { if (typeof source[key] === 'object' && typeof target[key] === 'object') { result[key] = this.deepMerge(target[key], source[key]); } else { result[key] = source[key]; } }); return result; } /** * עדכון nested state */ private updateNestedState(state: any, { path, value }: { path: string; value: any }): any { const keys = path.split('.'); const result = { ...state }; let current = result; for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; current[key] = { ...current[key] }; current = current[key]; } current[keys[keys.length - 1]] = value; return result; } /** * הרשמה לעדכוני state */ subscribe<R>( selector: (state: T) => R, callback: (selected: R, prevSelected: R, action?: StateAction) => void, options?: Partial<StateSubscription<T>['options']> ): string { const id = `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const subscription: StateSubscription<T> = { id, selector, callback, options: { fireImmediately: false, debounceMs: this.config.debounceDefault, equalityCheck: (a, b) => a === b, ...options } }; this.subscriptions.set(id, subscription); this.metrics.subscriptionCount = this.subscriptions.size; // Fire immediately if requested if (subscription.options.fireImmediately) { const selected = this.memoizedSelect(subscription.selector, this.currentState); callback(selected, selected); } return id; } /** * ביטול הרשמה */ unsubscribe(subscriptionId: string): boolean { const result = this.subscriptions.delete(subscriptionId); this.metrics.subscriptionCount = this.subscriptions.size; // Clean up debounced callback const debouncedCallback = this.debouncedCallbacks.get(subscriptionId); if (debouncedCallback) { clearTimeout(debouncedCallback); this.debouncedCallbacks.delete(subscriptionId); } return result; } /** * selection עם memoization */ private memoizedSelect<R>(selector: (state: T) => R, state: T): R { const selectorKey = selector.toString(); const cached = this.memoizedSelectors.get(selectorKey); if (cached && cached.lastState === state) { return cached.lastResult; } const result = selector(state); this.memoizedSelectors.set(selectorKey, { selector, lastState: state, lastResult: result }); return result; } /** * הודעה לsubscriptions */ private notifySubscriptions(action: StateAction, prevState: T): void { this.subscriptions.forEach((subscription) => { try { const prevSelected = this.memoizedSelect(subscription.selector, prevState); const newSelected = this.memoizedSelect(subscription.selector, this.currentState); if (!subscription.options.equalityCheck!(prevSelected, newSelected)) { if (subscription.options.debounceMs! > 0) { this.debounceCallback(subscription, newSelected, prevSelected, action); } else { subscription.callback(newSelected, prevSelected, action); } } } catch (error) { console.error(`Subscription ${subscription.id} error:`, error); } }); } /** * debouncing של callbacks */ private debounceCallback( subscription: StateSubscription<T>, newSelected: any, prevSelected: any, action: StateAction ): void { const existingTimeout = this.debouncedCallbacks.get(subscription.id); if (existingTimeout) { clearTimeout(existingTimeout); } const timeout = setTimeout(() => { subscription.callback(newSelected, prevSelected, action); this.debouncedCallbacks.delete(subscription.id); }, subscription.options.debounceMs); this.debouncedCallbacks.set(subscription.id, timeout); } /** * middleware processing */ private processMiddlewareBefore(action: StateAction): StateAction | null { let processedAction = action; for (const middleware of this.middleware) { if (middleware.before) { const result = middleware.before(processedAction, this.currentState); if (result === null) { console.log(`Action ${action.type} blocked by middleware: ${middleware.name}`); return null; } processedAction = result; } } return processedAction; } private processMiddlewareAfter(action: StateAction, prevState: T, newState: T): void { this.middleware.forEach(middleware => { if (middleware.after) { try { middleware.after(action, prevState, newState); } catch (error) { console.error(`Middleware ${middleware.name} after error:`, error); } } }); } private processMiddlewareError(error: Error, action: StateAction): void { this.middleware.forEach(middleware => { if (middleware.error) { try { middleware.error(error, action, this.currentState); } catch (middlewareError) { console.error(`Middleware ${middleware.name} error handler failed:`, middlewareError); } } }); } /** * הוספת middleware */ addMiddleware(middleware: StateMiddleware<T>): void { this.middleware.push(middleware); console.log(`Middleware added: ${middleware.name}`); } /** * הסרת middleware */ removeMiddleware(name: string): boolean { const index = this.middleware.findIndex(m => m.name === name); if (index !== -1) { this.middleware.splice(index, 1); console.log(`Middleware removed: ${name}`); return true; } return false; } /** * undo/redo functionality */ undo(): boolean { if (this.undoStack.length === 0) return false; const snapshot = this.undoStack.pop()!; // Save current state to redo stack this.redoStack.push({ id: `redo_${Date.now()}`, timestamp: Date.now(), state: { ...this.currentState }, actions: [], metadata: { version: '1.0.0', checkpoint: false, compressed: false, size: JSON.stringify(this.currentState).length } }); // Restore previous state this.currentState = snapshot.state; // Notify subscriptions this.notifySubscriptions({ type: 'UNDO', timestamp: Date.now(), id: `undo_${Date.now()}`, metadata: { source: 'system', priority: 'high' } }, snapshot.state); console.log('↶ State undo performed'); return true; } redo(): boolean { if (this.redoStack.length === 0) return false; const snapshot = this.redoStack.pop()!; // Save current state to undo stack this.undoStack.push({ id: `undo_${Date.now()}`, timestamp: Date.now(), state: { ...this.currentState }, actions: [], metadata: { version: '1.0.0', checkpoint: false, compressed: false, size: JSON.stringify(this.currentState).length } }); // Restore next state this.currentState = snapshot.state; // Notify subscriptions this.notifySubscriptions({ type: 'REDO', timestamp: Date.now(), id: `redo_${Date.now()}`, metadata: { source: 'system', priority: 'high' } }, snapshot.state); console.log('↷ State redo performed'); return true; } /** * time travel לnull */ travelToSnapshot(snapshotId: string): boolean { const snapshot = this.snapshots.find(s => s.id === snapshotId); if (!snapshot) return false; // Save current state for undo this.undoStack.push({ id: `time_travel_undo_${Date.now()}`, timestamp: Date.now(), state: { ...this.currentState }, actions: [], metadata: { version: '1.0.0', checkpoint: false, compressed: false, size: JSON.stringify(this.currentState).length } }); // Restore snapshot state this.currentState = snapshot.metadata.compressed ? JSON.parse(this.simpleDecompress(JSON.stringify(snapshot.state))) : snapshot.state; // Notify subscriptions this.notifySubscriptions({ type: 'TIME_TRAVEL', payload: { snapshotId }, timestamp: Date.now(), id: `time_travel_${Date.now()}`, metadata: { source: 'system', priority: 'high' } }, snapshot.state); console.log(`⏰ Time travel to snapshot: ${snapshotId}`); return true; } /** * persistence configuration */ configurePersistence(config: StatePersistenceConfig): void { this.persistenceConfig = config; this.loadPersistedState(); console.log(`💾 Persistence configured: ${config.storage}`); } private async persistState(): Promise<void> { if (!this.persistenceConfig) return; try { const serializer = this.persistenceConfig.serialize || JSON.stringify; let data = serializer(this.currentState); if (this.persistenceConfig.compress) { data = this.simpleCompress(data); } if (this.persistenceConfig.encrypt) { data = this.simpleEncrypt(data); } switch (this.persistenceConfig.storage) { case 'localStorage': if (typeof localStorage !== 'undefined') { localStorage.setItem(this.persistenceConfig.key, data); } break; case 'memory': // Store in memory (for testing) (globalThis as any)[`__state_${this.persistenceConfig.key}__`] = data; break; case 'indexedDB': // Would implement IndexedDB storage console.log('IndexedDB persistence not implemented in this example'); break; } } catch (error) { console.error('State persistence failed:', error); } } private async loadPersistedState(): Promise<void> { if (!this.persistenceConfig) return; try { let data: string | null = null; switch (this.persistenceConfig.storage) { case 'localStorage': if (typeof localStorage !== 'undefined') { data = localStorage.getItem(this.persistenceConfig.key); } break; case 'memory': data = (globalThis as any)[`__state_${this.persistenceConfig.key}__`] || null; break; case 'indexedDB': console.log('IndexedDB loading not implemented in this example'); break; } if (data) { if (this.persistenceConfig.encrypt) { data = this.simpleDecrypt(data); } if (this.persistenceConfig.compress) { data = this.simpleDecompress(data); } const deserializer = this.persistenceConfig.deserialize || JSON.parse; this.currentState = deserializer(data); console.log('📂 Persisted state loaded'); } } catch (error) { console.error('State loading failed:', error); } } private simpleEncrypt(data: string): string { return btoa(data); } private simpleDecrypt(data: string): string { return atob(data); } /** * state optimization */ private optimizeState(): void { // Clean up old memoized selectors this.cleanupMemoization(); // Compress large snapshots this.compressOldSnapshots(); // Clean up completed debounced callbacks this.cleanupDebouncedCallbacks(); console.log('⚡ State optimization completed'); } private cleanupMemoization(): void { // Remove unused memoized selectors (simplified) if (this.memoizedSelectors.size > 100) { this.memoizedSelectors.clear(); } } private compressOldSnapshots(): void { this.snapshots.forEach(snapshot => { if (!snapshot.metadata.compressed && snapshot.metadata.size > this.config.compressionThreshold && Date.now() - snapshot.timestamp > 60000) { // 1 minute old snapshot.state = this.compressState(snapshot.state); snapshot.metadata.compressed = true; } }); } private cleanupDebouncedCallbacks(): void { // Remove completed debounced callbacks this.debouncedCallbacks.forEach((timeout, id) => { if (!this.subscriptions.has(id)) { clearTimeout(timeout); this.debouncedCallbacks.delete(id); } }); } /** * metrics update */ private updateActionMetrics(executionTime: number): void { this.metrics.actionCount++; this.metrics.averageUpdateTime = (this.metrics.averageUpdateTime + executionTime) / 2; } private updateMetrics(): void { this.metrics.memoryUsage = this.calculateMemoryUsage(); this.metrics.cacheHitRate = this.calculateCacheHitRate(); } private calculateMemoryUsage(): number { // Simplified memory calculation const stateSize = JSON.stringify(this.currentState).length; const historySize = this.actionHistory.length * 100; // Estimated const snapshotsSize = this.snapshots.reduce((total, s) => total + s.metadata.size, 0); return stateSize + historySize + snapshotsSize; } private calculateCacheHitRate(): number { // Simplified cache hit rate calculation return Math.min(0.95, this.memoizedSelectors.size / Math.max(this.metrics.actionCount, 1)); } /** * Public API Methods */ /** * קבלת state נוכחי */ getState(): T { return { ...this.currentState }; } /** * קבלת חלק מה-state */ select<R>(selector: (state: T) => R): R { return this.memoizedSelect(selector, this.currentState); } /** * קבלת היסטוריית actions */ getActionHistory(limit?: number): StateAction[] { return limit ? this.actionHistory.slice(-limit) : [...this.actionHistory]; } /** * קבלת snapshots */ getSnapshots(): StateSnapshot<T>[] { return [...this.snapshots]; } /** * קבלת מטריקות ביצועים */ getMetrics(): StateOptimizationMetrics { return { ...this.metrics }; } /** * איפוס state */ reset(newState?: T): void { const resetState = newState || this.snapshots.find(s => s.metadata.checkpoint)?.state || this.currentState; this.dispatch('RESET_STATE', resetState, { source: 'system', priority: 'high', rollbackable: false }); console.log('🔄 State reset performed'); } /** * יצירת checkpoint */ createCheckpoint(reason?: string): string { this.createSnapshot(reason || 'manual_checkpoint', true); const latestSnapshot = this.snapshots[this.snapshots.length - 1]; console.log(`📍 Checkpoint created: ${latestSnapshot.id}`); return latestSnapshot.id; } /** * ניקוי משאבים */ dispose(): void { // Clear optimization timer if (this.optimizationTimer) { clearInterval(this.optimizationTimer); } // Clear debounced callbacks this.debouncedCallbacks.forEach(timeout => clearTimeout(timeout)); // Clear all data this.actionHistory.length = 0; this.snapshots.length = 0; this.subscriptions.clear(); this.middleware.length = 0; this.undoStack.length = 0; this.redoStack.length = 0; this.debouncedCallbacks.clear(); this.memoizedSelectors.clear(); console.log('🔄 Advanced State Manager disposed'); } } export default AdvancedStateManager;