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