using System.Text.Json; namespace SqrtSpace.SpaceTime.Core; /// /// Manages checkpointing for fault-tolerant operations /// public class CheckpointManager : IDisposable { private readonly string _checkpointDirectory; private readonly CheckpointStrategy _strategy; private readonly int _checkpointInterval; private int _operationCount; private readonly List _checkpointFiles = new(); /// /// Initializes a new checkpoint manager /// /// Directory to store checkpoints /// Checkpointing strategy /// Total expected operations (for √n calculation) public CheckpointManager( string? checkpointDirectory = null, CheckpointStrategy strategy = CheckpointStrategy.SqrtN, long totalOperations = 1_000_000) { _checkpointDirectory = checkpointDirectory ?? Path.Combine(Path.GetTempPath(), $"spacetime_checkpoint_{Guid.NewGuid()}"); _strategy = strategy; _checkpointInterval = SpaceTimeCalculator.CalculateCheckpointCount(totalOperations, strategy); Directory.CreateDirectory(_checkpointDirectory); } /// /// Checks if a checkpoint should be created /// /// True if checkpoint should be created public bool ShouldCheckpoint() { _operationCount++; return _strategy switch { CheckpointStrategy.None => false, CheckpointStrategy.SqrtN => _operationCount % _checkpointInterval == 0, CheckpointStrategy.Linear => _operationCount % 1000 == 0, CheckpointStrategy.Logarithmic => IsPowerOfTwo(_operationCount), _ => false }; } /// /// Creates a checkpoint for the given state /// /// Type of state to checkpoint /// State to save /// Optional checkpoint ID /// Path to checkpoint file public async Task CreateCheckpointAsync(T state, string? checkpointId = null) { checkpointId ??= $"checkpoint_{_operationCount}_{DateTime.UtcNow.Ticks}"; var filePath = Path.Combine(_checkpointDirectory, $"{checkpointId}.json"); var json = JsonSerializer.Serialize(state, new JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); await File.WriteAllTextAsync(filePath, json); _checkpointFiles.Add(filePath); // Clean up old checkpoints if using √n strategy if (_strategy == CheckpointStrategy.SqrtN && _checkpointFiles.Count > Math.Sqrt(_operationCount)) { CleanupOldCheckpoints(); } return filePath; } /// /// Restores state from the latest checkpoint /// /// Type of state to restore /// Restored state or null if no checkpoint exists public async Task RestoreLatestCheckpointAsync() { var latestCheckpoint = Directory.GetFiles(_checkpointDirectory, "*.json") .OrderByDescending(f => new FileInfo(f).LastWriteTimeUtc) .FirstOrDefault(); if (latestCheckpoint == null) return default; var json = await File.ReadAllTextAsync(latestCheckpoint); return JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); } /// /// Restores state from a specific checkpoint /// /// Type of state to restore /// Checkpoint ID to restore /// Restored state or null if checkpoint doesn't exist public async Task RestoreCheckpointAsync(string checkpointId) { var filePath = Path.Combine(_checkpointDirectory, $"{checkpointId}.json"); if (!File.Exists(filePath)) return default; var json = await File.ReadAllTextAsync(filePath); return JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); } /// /// Gets the number of operations since last checkpoint /// public int OperationsSinceLastCheckpoint => _operationCount % _checkpointInterval; /// /// Saves state for a specific checkpoint and key /// /// Type of state to save /// Checkpoint ID /// State key /// State to save /// Cancellation token public async Task SaveStateAsync(string checkpointId, string key, T state, CancellationToken cancellationToken = default) where T : class { var filePath = Path.Combine(_checkpointDirectory, $"{checkpointId}_{key}.json"); var json = JsonSerializer.Serialize(state, new JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); await File.WriteAllTextAsync(filePath, json, cancellationToken); } /// /// Loads state for a specific checkpoint and key /// /// Type of state to load /// Checkpoint ID /// State key /// Cancellation token /// Loaded state or null if not found public async Task LoadStateAsync(string checkpointId, string key, CancellationToken cancellationToken = default) where T : class { var filePath = Path.Combine(_checkpointDirectory, $"{checkpointId}_{key}.json"); if (!File.Exists(filePath)) return null; var json = await File.ReadAllTextAsync(filePath, cancellationToken); return JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); } /// /// Cleans up checkpoint files /// public void Dispose() { try { if (Directory.Exists(_checkpointDirectory)) { Directory.Delete(_checkpointDirectory, recursive: true); } } catch { // Best effort cleanup } } private void CleanupOldCheckpoints() { // Keep only the most recent √n checkpoints var toKeep = (int)Math.Sqrt(_operationCount); var toDelete = _checkpointFiles .OrderBy(f => new FileInfo(f).LastWriteTimeUtc) .Take(_checkpointFiles.Count - toKeep) .ToList(); foreach (var file in toDelete) { try { File.Delete(file); _checkpointFiles.Remove(file); } catch { // Best effort } } } private static bool IsPowerOfTwo(int n) { return n > 0 && (n & (n - 1)) == 0; } } /// /// Attribute to mark methods as checkpointable /// [AttributeUsage(AttributeTargets.Method)] public class CheckpointableAttribute : Attribute { /// /// Checkpointing strategy to use /// public CheckpointStrategy Strategy { get; set; } = CheckpointStrategy.SqrtN; /// /// Whether to automatically restore from checkpoint on failure /// public bool AutoRestore { get; set; } = true; /// /// Custom checkpoint directory /// public string? CheckpointDirectory { get; set; } }