Files
BadNote/lib/editor/persistence/save_scheduler.dart

99 lines
3.1 KiB
Dart
Raw Permalink Normal View History

// 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<String, Timer> _timers = {};
final Map<String, _PendingWrite> _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<EditorStroke> 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<void> flush() async {
final hosts = List<String>.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<void> _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<EditorStroke> strokes;
}