Files
FlClash/lib/common/function.dart
T

162 lines
4.0 KiB
Dart
Raw Normal View History

2024-04-30 23:38:49 +08:00
import 'dart:async';
2025-09-23 21:02:47 +08:00
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/ui/ui.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';
void _applyAndReport(dynamic tag, Function func, List<dynamic>? args) {
void report(Object error, StackTrace stackTrace) {
commonPrint.log(
'Scheduled task $tag failed: ${compactError(error)}, $stackTrace',
logLevel: LogLevel.warning,
);
}
try {
final result = Function.apply(func, args);
if (result is Future) {
result.then<void>((_) {}, onError: report);
}
} catch (error, stackTrace) {
report(error, stackTrace);
}
}
2025-05-02 02:24:12 +08:00
2024-04-30 23:38:49 +08:00
class Debouncer {
final Map<dynamic, Timer> _operations = {};
final Map<dynamic, ({Function func, List<dynamic>? args})> _pending = {};
2024-04-30 23:38:49 +08:00
2025-06-07 01:48:34 +08:00
void call(
2026-02-02 10:14:13 +08:00
dynamic tag,
2024-12-09 01:40:39 +08:00
Function func, {
List<dynamic>? args,
2025-07-31 17:09:18 +08:00
Duration? duration,
2024-12-09 01:40:39 +08:00
}) {
_operations.remove(tag)?.cancel();
_pending[tag] = (func: func, args: args);
2025-07-31 17:09:18 +08:00
_operations[tag] = Timer(duration ?? const Duration(milliseconds: 600), () {
_operations.remove(tag);
final pending = _pending.remove(tag);
if (pending == null) {
return;
}
_applyAndReport(tag, pending.func, pending.args);
2025-07-31 17:09:18 +08:00
});
2024-12-09 01:40:39 +08:00
}
2024-04-30 23:38:49 +08:00
void flush(dynamic tag) {
final timer = _operations.remove(tag);
timer?.cancel();
final pending = _pending.remove(tag);
if (timer == null || pending == null) {
return;
}
_applyAndReport(tag, pending.func, pending.args);
}
2025-06-07 01:48:34 +08:00
void cancel(dynamic tag) {
_operations.remove(tag)?.cancel();
_pending.remove(tag);
2024-04-30 23:38:49 +08:00
}
}
2026-08-05 10:57:11 +08:00
class SerialTaskScheduler {
Future<void> _serialTail = Future<void>.value();
Future<T> run<T>(Future<T> Function() task) {
final completer = Completer<T>();
_serialTail = _serialTail.then((_) async {
try {
completer.complete(await task());
} catch (error, stackTrace) {
completer.completeError(error, stackTrace);
}
});
return completer.future;
}
}
2025-02-03 23:32:00 +08:00
class Throttler {
final Map<dynamic, Timer> _operations = {};
2025-02-03 23:32:00 +08:00
2025-06-07 01:48:34 +08:00
bool call(
2026-02-02 10:14:13 +08:00
dynamic tag,
2025-02-03 23:32:00 +08:00
Function func, {
List<dynamic>? args,
Duration duration = const Duration(milliseconds: 600),
2025-07-31 17:09:18 +08:00
bool fire = false,
2025-02-03 23:32:00 +08:00
}) {
if (_operations.containsKey(tag)) {
2025-02-03 23:32:00 +08:00
return true;
}
2025-07-31 17:09:18 +08:00
if (fire) {
_applyAndReport(tag, func, args);
2025-07-31 17:09:18 +08:00
_operations[tag] = Timer(duration, () {
2025-02-03 23:32:00 +08:00
_operations.remove(tag);
2025-07-31 17:09:18 +08:00
});
} else {
_operations[tag] = Timer(duration, () {
_applyAndReport(tag, func, args);
2025-07-31 17:09:18 +08:00
_operations.remove(tag);
});
}
2025-02-03 23:32:00 +08:00
return false;
}
2025-06-07 01:48:34 +08:00
void cancel(dynamic tag) {
_operations.remove(tag)?.cancel();
2025-02-03 23:32:00 +08:00
}
}
2025-02-09 18:39:38 +08:00
Future<T> retry<T>({
required Future<T> Function() task,
int maxAttempts = 3,
required bool Function(T res) retryIf,
2025-09-23 21:02:47 +08:00
Duration delay = midDuration,
2025-02-09 18:39:38 +08:00
}) async {
int attempts = 0;
while (attempts < maxAttempts) {
final res = await task();
2026-02-02 10:14:13 +08:00
attempts++;
2025-02-09 18:39:38 +08:00
if (!retryIf(res) || attempts >= maxAttempts) {
return res;
}
2026-02-02 10:14:13 +08:00
await Future.delayed(delay);
2025-02-09 18:39:38 +08:00
}
throw TimeoutException('retry gave up after $maxAttempts attempts');
2025-02-09 18:39:38 +08:00
}
2025-02-03 23:32:00 +08:00
2024-12-09 01:40:39 +08:00
final debouncer = Debouncer();
2025-02-03 23:32:00 +08:00
2025-02-09 18:39:38 +08:00
final throttler = Throttler();
/// Frames stop while the window is hidden, so a post-frame callback alone
/// would hold tray and hotkey work back until the window is shown again.
void runAfterFrame(VoidCallback callback) {
final binding = SchedulerBinding.instance;
if (!binding.framesEnabled) {
Timer.run(callback);
return;
}
binding.addPostFrameCallback((_) => callback());
binding.scheduleFrame();
}
FutureOr<T> handleWatch<T>({
required Function function,
required void Function() onStart,
required void Function(T data, int elapsedMilliseconds) onEnd,
}) async {
if (kDebugMode && watchExecution) {
onStart();
final stopwatch = Stopwatch()..start();
final res = await function();
stopwatch.stop();
onEnd(res, stopwatch.elapsedMilliseconds);
return res;
}
return await function();
}