import 'dart:async'; /// Coalesces full and targeted delta loads behind one in-flight drain. /// /// A full load takes priority and supersedes every queued delta. Requests made /// while a pass is running share the drain future and are replayed as trailing /// work. The callbacks own fetch, commit, and failure policy. final class CoalescedLoadCoordinator { factory CoalescedLoadCoordinator({ required Future Function() onFull, required Future Function(Set) onDelta, }) => CoalescedLoadCoordinator._(onFull, onDelta); CoalescedLoadCoordinator._(this._onFull, this._onDelta); final Future Function() _onFull; final Future Function(Set) _onDelta; final Set _pendingDelta = {}; Future? _inFlight; bool _pendingFull = false; bool _disposed = false; /// Whether a pass is running (or queued behind the running one). Lets a /// caller tell "this surface is already loading" from "nothing has started", /// without changing the drain's trailing-replay contract. bool get isBusy => _inFlight != null; Future requestFull() { if (_disposed) return Future.value(); _pendingFull = true; return _ensureDrain(); } Future requestDelta(Iterable values) { if (_disposed) return Future.value(); _pendingDelta.addAll(values); if (_pendingDelta.isEmpty) return _inFlight ?? Future.value(); return _ensureDrain(); } /// Discards trailing work without interrupting the active callback. void clearPending() { if (_disposed) return; _pendingFull = false; _pendingDelta.clear(); } /// Prevents new work and discards work queued behind the active callback. void dispose() { _disposed = true; _pendingFull = false; _pendingDelta.clear(); } Future _ensureDrain() { final active = _inFlight; if (active != null) return active; // Install the shared future before invoking a callback so a synchronous, // reentrant request is queued behind this drain rather than starting one. final completer = Completer(); final future = completer.future; _inFlight = future; _drain().then( (_) { if (!_disposed && identical(_inFlight, future)) _inFlight = null; completer.complete(); }, onError: (Object error, StackTrace stackTrace) { if (!_disposed && identical(_inFlight, future)) _inFlight = null; completer.completeError(error, stackTrace); }, ); return future; } Future _drain() async { while ((_pendingFull || _pendingDelta.isNotEmpty) && !_disposed) { if (_pendingFull) { _pendingFull = false; _pendingDelta.clear(); await _onFull(); } else { final values = Set.of(_pendingDelta); _pendingDelta.clear(); await _onDelta(values); } } } }