nc-photos/app/lib/throttler.dart

100 lines
2.6 KiB
Dart
Raw Normal View History

2021-07-31 19:28:08 +02:00
import 'dart:async';
import 'dart:math' as math;
import 'package:logging/logging.dart';
2022-12-16 16:01:04 +01:00
import 'package:np_codegen/np_codegen.dart';
2023-08-25 17:29:27 +02:00
import 'package:np_math/np_math.dart';
2022-12-16 16:01:04 +01:00
part 'throttler.g.dart';
2021-07-31 19:28:08 +02:00
/// Throttle how many times an event could be triggered
///
/// Events can be filtered by 2 ways:
/// 1. Time passed after the last event
/// 2. Number of events
2022-12-16 16:01:04 +01:00
@npLog
2021-07-31 19:28:08 +02:00
class Throttler<T> {
Throttler({
required this.onTriggered,
this.logTag,
});
/// Post an event
///
/// [data] can be used to provide optional data. When the stream of events
/// eventually trigger, a list of all data received will be passed to the
/// callback. Nulls are ignored
void trigger({
Duration maxResponceTime = const Duration(seconds: 1),
int? maxPendingCount,
T? data,
}) {
_count += 1;
if (data != null) {
_data.add(data);
}
_subscription?.cancel();
_subscription = null;
if (maxPendingCount != null) {
_maxCount = math.min(maxPendingCount, _maxCount ?? int32Max);
}
if (_maxCount != null && _count >= _maxCount!) {
_log.info("[trigger]$_logTag Triggered after $_count events");
_doTrigger();
} else {
final responseTime = _minDuration(
2021-09-15 08:58:06 +02:00
maxResponceTime, _currentResponseTime ?? const Duration(days: 1));
2021-07-31 19:28:08 +02:00
_subscription = Future.delayed(responseTime).asStream().listen((event) {
_log.info("[trigger]$_logTag Triggered after $responseTime");
_doTrigger();
});
_currentResponseTime = responseTime;
}
}
/// Drop all pending triggers, this may be useful in places like [dispose]
void clear() {
_subscription?.cancel();
_subscription = null;
_currentResponseTime = null;
_count = 0;
_maxCount = null;
// _data may be used else where, thus we must not call List.clear() here
2021-07-31 19:28:08 +02:00
_data = <T>[];
}
Future<void> triggerNow() async {
if (_hasPendingEvent) {
_log.info("[triggerNow]$_logTag Triggered");
return _doTrigger();
}
2021-07-31 19:28:08 +02:00
}
Future<void> _doTrigger() async {
if (_hasPendingEvent) {
final data = _data;
clear();
await onTriggered?.call(data);
}
}
bool get _hasPendingEvent => _count > 0;
2021-07-31 19:28:08 +02:00
String get _logTag => logTag == null ? "" : "[$logTag]";
final FutureOr<void> Function(List<T>)? onTriggered;
2021-09-15 08:58:06 +02:00
2021-07-31 19:28:08 +02:00
/// Extra tag printed with logs from this class
final String? logTag;
StreamSubscription<void>? _subscription;
Duration? _currentResponseTime;
int _count = 0;
int? _maxCount;
var _data = <T>[];
}
Duration _minDuration(Duration a, Duration b) {
return a.compareTo(b) < 0 ? a : b;
}