Files
record-tool/lib/core/utils/rate_limiter.dart
2026-06-03 14:07:10 +08:00

167 lines
3.8 KiB
Dart

import 'dart:async';
enum RateLimitMode { debounce, throttle }
class ThrottleOptions {
const ThrottleOptions({this.leading = true, this.trailing = true});
final bool leading;
final bool trailing;
}
class DebounceThrottle<T> {
DebounceThrottle({
required this.duration,
required this.mode,
this.onCallback,
this.throttleOptions = const ThrottleOptions(),
});
final Duration duration;
final RateLimitMode mode;
final void Function(T value)? onCallback;
final ThrottleOptions throttleOptions;
Timer? _timer;
T? _lastValue;
DateTime? _lastExecuteTime;
bool _waitingTrailing = false;
void call(T value) {
_lastValue = value;
switch (mode) {
case RateLimitMode.debounce:
_debounce(value);
case RateLimitMode.throttle:
_throttle(value);
}
}
void _debounce(T value) {
_timer?.cancel();
_timer = Timer(duration, () {
onCallback?.call(value);
_lastExecuteTime = DateTime.now();
});
}
void _throttle(T value) {
final now = DateTime.now();
final last = _lastExecuteTime;
final inWindow = last != null && now.difference(last) < duration;
if (!inWindow && throttleOptions.leading) {
onCallback?.call(value);
_lastExecuteTime = now;
_waitingTrailing = false;
_timer?.cancel();
return;
}
if (!throttleOptions.trailing || _waitingTrailing) return;
_waitingTrailing = true;
final remaining = last == null ? duration : duration - now.difference(last);
_timer?.cancel();
_timer = Timer(remaining, () {
final value = _lastValue;
if (value != null) onCallback?.call(value);
_lastExecuteTime = DateTime.now();
_waitingTrailing = false;
});
}
void flush() {
_timer?.cancel();
final value = _lastValue;
if (value != null) {
onCallback?.call(value);
_lastExecuteTime = DateTime.now();
}
_lastValue = null;
_waitingTrailing = false;
}
void cancel() {
_timer?.cancel();
_lastValue = null;
_waitingTrailing = false;
}
void dispose() => cancel();
}
class RateLimitHub {
RateLimitHub({this.removeDebouncerOnDone = true});
final bool removeDebouncerOnDone;
final Map<Object, DebounceThrottle<dynamic>> _debouncers = {};
final Map<Object, DebounceThrottle<dynamic>> _throttlers = {};
void debounce<T>({
required Object key,
required T value,
Duration duration = const Duration(milliseconds: 300),
required void Function(T value) onCallback,
}) {
_debouncers[key]?.cancel();
late DebounceThrottle<T> limiter;
limiter = DebounceThrottle<T>(
duration: duration,
mode: RateLimitMode.debounce,
onCallback: (value) {
onCallback(value);
if (removeDebouncerOnDone) _debouncers.remove(key);
},
);
_debouncers[key] = limiter;
limiter(value);
}
void throttle<T>({
required Object key,
required T value,
Duration duration = const Duration(milliseconds: 300),
ThrottleOptions options = const ThrottleOptions(),
required void Function(T value) onCallback,
}) {
_throttlers.putIfAbsent(
key,
() =>
DebounceThrottle<T>(
duration: duration,
mode: RateLimitMode.throttle,
throttleOptions: options,
onCallback: onCallback,
)
as DebounceThrottle<dynamic>,
);
(_throttlers[key] as DebounceThrottle<T>)(value);
}
void cancel(Object key) {
_debouncers.remove(key)?.cancel();
_throttlers.remove(key)?.cancel();
}
void clear() {
for (final limiter in _debouncers.values) {
limiter.dispose();
}
for (final limiter in _throttlers.values) {
limiter.dispose();
}
_debouncers.clear();
_throttlers.clear();
}
}
class RateLimit {
RateLimit._();
static final instance = RateLimitHub();
}