// lib/editor/persistence/save_scheduler.dart // // Debounced save scheduler. The snapshot/JSON is captured SYNCHRONOUSLY by // the schedule() call before any await, so callers do not need to worry about // the in-memory state mutating between schedule() and the actual write. import 'dart:async'; import '../engine/stroke_model.dart'; import 'editor_repository.dart'; /// Debounced save scheduler that batches rapid successive changes to a host /// into a single [EditorRepository.saveHost] call. /// /// Usage: /// ```dart /// final scheduler = SaveScheduler(repository); /// scheduler.schedule('page', hostId, List.from(strokes)); /// // …later, on dispose / navigate away: /// await scheduler.flush(); /// scheduler.dispose(); /// ``` class SaveScheduler { SaveScheduler( this._repository, { Duration debounce = const Duration(milliseconds: 800), }) : _debounce = debounce; final EditorRepository _repository; final Duration _debounce; // One pending timer + captured snapshot per host. final Map _timers = {}; final Map _pending = {}; bool _disposed = false; // ── Public API ───────────────────────────────────────────────────────── /// Schedule a save for ([hostKind], [hostId]). /// /// [strokes] is captured synchronously (defensive copy via the caller's /// `List.from(…)` convention or equivalent) so mutations after this call /// do not affect what is written. void schedule( String hostKind, String hostId, List strokes, ) { if (_disposed) return; // Capture the snapshot synchronously before any async gap. _pending[hostId] = _PendingWrite(hostKind: hostKind, strokes: strokes); _timers[hostId]?.cancel(); _timers[hostId] = Timer(_debounce, () => _fire(hostId)); } /// Force-write all pending saves immediately and wait for them to complete. Future flush() async { final hosts = List.from(_pending.keys); for (final hostId in hosts) { _timers[hostId]?.cancel(); _timers.remove(hostId); await _fire(hostId); } } /// Cancel all pending timers and release resources. /// /// Call [flush] first if you need pending writes to complete. void dispose() { _disposed = true; for (final timer in _timers.values) { timer.cancel(); } _timers.clear(); _pending.clear(); } // ── Internal ─────────────────────────────────────────────────────────── Future _fire(String hostId) async { final write = _pending.remove(hostId); _timers.remove(hostId); if (write == null) return; await _repository.saveHost(write.hostKind, hostId, write.strokes); } } // --------------------------------------------------------------------------- class _PendingWrite { const _PendingWrite({required this.hostKind, required this.strokes}); final String hostKind; final List strokes; }