From 7693e30cbff1c4a18f812dbddfd92e228917fd74 Mon Sep 17 00:00:00 2001 From: GT610 <79314033+GT-610@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:47:06 +0800 Subject: [PATCH] opt: Better performance on server refreshing (#999) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(server): Replace Future.wait with an explicit list of futures to enhance readability Refactor the nested map and async functions into explicit for loops and future lists to make the code logic clearer * fix(server): Fixed the auto-refresh logic and concurrency control issues - Add `_refreshCompleter` to prevent concurrent refreshes - Fixed the issue where the status was not updated after the automatic refresh timer was canceled - Remove the invalid check for `duration == 1` * refactor(server): Optimize the server refresh logic by filtering out servers that do not need to be refreshed in advance Move the server filtering logic outside the loop and use the `where` method to filter the servers that need to be refreshed, avoiding repeated condition checks within the loop. This improves code readability and reduces redundant condition checks. * refactor: Optimize server refresh logic to enhance readability Break down complex conditional checks into clearer steps, separating the logic for server refresh and rate limiter reset. Replace chained calls with explicit loops to make the code easier to maintain and understand. * refactor(server): Remove `updateFuture` from `ServerState` and use the `_isRefreshing` flag instead Simplify the server refresh logic, replace Future state tracking with a boolean flag, and avoid unnecessary state updates * refactor(server_detail): Extract the setting items as local variables to improve performance Extract the globally set items that are accessed repeatedly as local variables, reduce unnecessary state retrieval operations, and optimize page performance * refactor: Rename `_displayCpuIndexSetting` to `_displayCpuIndex` for consistency * refactor(server): Fix the issue of parallel blocking in server refresh The original code uses Future.wait to wait for all refresh operations to complete, but in fact, there is no need to wait for the results of these operations. Instead, directly calling ignore() to ignore the results can avoid blocking caused by the slowest server * fix: Adjust the order of logging and default value settings Ensure to set the default value after recording the invalid duration warning * refactor(server): Rename _refreshCompleter to _refreshInProgress to enhance readability Change the variable name from `_refreshCompleter` to `_refreshInProgress`, so that it more accurately reflects the actual purpose of the variable, which is to indicate whether the refresh operation is in progress * refactor(server): Remove unnecessary refresh progress status management Simplify the server refresh logic, remove the unused _refreshInProgress state variable and related Completer handling, making the code more concise and straightforward * chore: Update dependent package versions Update the following dependent package versions: - camera_web has been upgraded from 0.3.5 to 0.3.5+3 - ffi has been upgraded from 2.1.4 to 2.1.5 - hive_ce_flutter is upgraded from 2.3.3 to 2.3.4 - watcher is upgraded from 1.1.4 to 1.2.1 * opt. --------- Co-authored-by: lollipopkit🏳️‍⚧️ <10864310+lollipopkit@users.noreply.github.com> --- lib/data/provider/container.g.dart | 2 +- lib/data/provider/pve.g.dart | 2 +- lib/data/provider/server/all.dart | 49 +++++++++++--------- lib/data/provider/server/all.g.dart | 2 +- lib/data/provider/server/single.dart | 16 +++---- lib/data/provider/server/single.freezed.dart | 43 ++++++++--------- lib/data/provider/server/single.g.dart | 2 +- lib/view/page/server/detail/view.dart | 9 ++-- pubspec.lock | 24 +++++----- 9 files changed, 76 insertions(+), 73 deletions(-) diff --git a/lib/data/provider/container.g.dart b/lib/data/provider/container.g.dart index 21c7e085..77647101 100644 --- a/lib/data/provider/container.g.dart +++ b/lib/data/provider/container.g.dart @@ -58,7 +58,7 @@ final class ContainerNotifierProvider } } -String _$containerNotifierHash() => r'fea65e66499234b0a59bffff8d69c4ab8c93b2fd'; +String _$containerNotifierHash() => r'85457ec75264199c284572ee45beeaccba2044a1'; final class ContainerNotifierFamily extends $Family with diff --git a/lib/data/provider/pve.g.dart b/lib/data/provider/pve.g.dart index 374b0afa..cad05f89 100644 --- a/lib/data/provider/pve.g.dart +++ b/lib/data/provider/pve.g.dart @@ -58,7 +58,7 @@ final class PveNotifierProvider } } -String _$pveNotifierHash() => r'ba5f2d6cb47c33735f7cc09b771b4a86501b86c6'; +String _$pveNotifierHash() => r'1e71faadee074b9c07bee731ef4ae6505e791967'; final class PveNotifierFamily extends $Family with $ClassFamilyOverride { diff --git a/lib/data/provider/server/all.dart b/lib/data/provider/server/all.dart index 7005315e..3475df93 100644 --- a/lib/data/provider/server/all.dart +++ b/lib/data/provider/server/all.dart @@ -103,37 +103,44 @@ class ServersNotifier extends _$ServersNotifier { return; } - await Future.wait( - state.servers.entries.map((entry) async { - final serverId = entry.key; - final spi = entry.value; + final serversToRefresh = >[]; + final idsToResetLimiter = []; - if (onlyFailed) { - final serverState = ref.read(serverProvider(serverId)); - if (serverState.conn != ServerConn.failed) return; - TryLimiter.reset(serverId); - } + for (final entry in state.servers.entries) { + final serverId = entry.key; + final spi = entry.value; - if (state.manualDisconnectedIds.contains(serverId)) return; + if (state.manualDisconnectedIds.contains(serverId)) continue; - final serverState = ref.read(serverProvider(serverId)); - if (serverState.conn == ServerConn.disconnected && !spi.autoConnect) { - return; - } + final serverState = ref.read(serverProvider(serverId)); - final serverNotifier = ref.read(serverProvider(serverId).notifier); - await serverNotifier.refresh(); - }), - ); + if (onlyFailed) { + if (serverState.conn != ServerConn.failed) continue; + idsToResetLimiter.add(serverId); + } + + if (serverState.conn == ServerConn.disconnected && !spi.autoConnect) continue; + + serversToRefresh.add(entry); + } + + for (final id in idsToResetLimiter) { + TryLimiter.reset(id); + } + + for (final entry in serversToRefresh) { + final serverNotifier = ref.read(serverProvider(entry.key).notifier); + serverNotifier.refresh().ignore(); + } } Future startAutoRefresh() async { var duration = Stores.setting.serverStatusUpdateInterval.fetch(); stopAutoRefresh(); if (duration == 0) return; - if (duration < 0 || duration > 10 || duration == 1) { - duration = 3; + if (duration <= 1 || duration > 10) { Loggers.app.warning('Invalid duration: $duration, use default 3'); + duration = 3; } final timer = Timer.periodic(Duration(seconds: duration), (_) async { await refresh(); @@ -145,8 +152,8 @@ class ServersNotifier extends _$ServersNotifier { final timer = state.autoRefreshTimer; if (timer != null) { timer.cancel(); - state = state.copyWith(autoRefreshTimer: null); } + state = state.copyWith(autoRefreshTimer: null); } bool get isAutoRefreshOn => state.autoRefreshTimer != null; diff --git a/lib/data/provider/server/all.g.dart b/lib/data/provider/server/all.g.dart index 9c2c7df5..2fefa053 100644 --- a/lib/data/provider/server/all.g.dart +++ b/lib/data/provider/server/all.g.dart @@ -41,7 +41,7 @@ final class ServersNotifierProvider } } -String _$serversNotifierHash() => r'3292bdce7d602ff64687b05ff81d120e71761ec2'; +String _$serversNotifierHash() => r'dc5da44f9bd8d8dcfba3e6e932cca3e2f379e582'; abstract class _$ServersNotifier extends $Notifier { ServersState build(); diff --git a/lib/data/provider/server/single.dart b/lib/data/provider/server/single.dart index 2c6119ca..5b7df3bb 100644 --- a/lib/data/provider/server/single.dart +++ b/lib/data/provider/server/single.dart @@ -35,7 +35,6 @@ abstract class ServerState with _$ServerState { required ServerStatus status, @Default(ServerConn.disconnected) ServerConn conn, SSHClient? client, - Future? updateFuture, }) = _ServerState; } @@ -81,19 +80,16 @@ class ServerNotifier extends _$ServerNotifier { } // Refresh server status + bool _isRefreshing = false; + Future refresh() async { - if (state.updateFuture != null) { - await state.updateFuture; - return; - } - - final updateFuture = _updateServer(); - state = state.copyWith(updateFuture: updateFuture); + if (_isRefreshing) return; + _isRefreshing = true; try { - await updateFuture; + await _updateServer(); } finally { - state = state.copyWith(updateFuture: null); + _isRefreshing = false; } } diff --git a/lib/data/provider/server/single.freezed.dart b/lib/data/provider/server/single.freezed.dart index a617c2f0..93df4fd0 100644 --- a/lib/data/provider/server/single.freezed.dart +++ b/lib/data/provider/server/single.freezed.dart @@ -14,7 +14,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$ServerState { - Spi get spi; ServerStatus get status; ServerConn get conn; SSHClient? get client; Future? get updateFuture; + Spi get spi; ServerStatus get status; ServerConn get conn; SSHClient? get client; /// Create a copy of ServerState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -25,16 +25,16 @@ $ServerStateCopyWith get copyWith => _$ServerStateCopyWithImpl Object.hash(runtimeType,spi,status,conn,client,updateFuture); +int get hashCode => Object.hash(runtimeType,spi,status,conn,client); @override String toString() { - return 'ServerState(spi: $spi, status: $status, conn: $conn, client: $client, updateFuture: $updateFuture)'; + return 'ServerState(spi: $spi, status: $status, conn: $conn, client: $client)'; } @@ -45,7 +45,7 @@ abstract mixin class $ServerStateCopyWith<$Res> { factory $ServerStateCopyWith(ServerState value, $Res Function(ServerState) _then) = _$ServerStateCopyWithImpl; @useResult $Res call({ - Spi spi, ServerStatus status, ServerConn conn, SSHClient? client, Future? updateFuture + Spi spi, ServerStatus status, ServerConn conn, SSHClient? client }); @@ -62,14 +62,13 @@ class _$ServerStateCopyWithImpl<$Res> /// Create a copy of ServerState /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? spi = null,Object? status = null,Object? conn = null,Object? client = freezed,Object? updateFuture = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? spi = null,Object? status = null,Object? conn = null,Object? client = freezed,}) { return _then(_self.copyWith( spi: null == spi ? _self.spi : spi // ignore: cast_nullable_to_non_nullable as Spi,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable as ServerStatus,conn: null == conn ? _self.conn : conn // ignore: cast_nullable_to_non_nullable as ServerConn,client: freezed == client ? _self.client : client // ignore: cast_nullable_to_non_nullable -as SSHClient?,updateFuture: freezed == updateFuture ? _self.updateFuture : updateFuture // ignore: cast_nullable_to_non_nullable -as Future?, +as SSHClient?, )); } /// Create a copy of ServerState @@ -163,10 +162,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( Spi spi, ServerStatus status, ServerConn conn, SSHClient? client, Future? updateFuture)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( Spi spi, ServerStatus status, ServerConn conn, SSHClient? client)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _ServerState() when $default != null: -return $default(_that.spi,_that.status,_that.conn,_that.client,_that.updateFuture);case _: +return $default(_that.spi,_that.status,_that.conn,_that.client);case _: return orElse(); } @@ -184,10 +183,10 @@ return $default(_that.spi,_that.status,_that.conn,_that.client,_that.updateFutur /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( Spi spi, ServerStatus status, ServerConn conn, SSHClient? client, Future? updateFuture) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( Spi spi, ServerStatus status, ServerConn conn, SSHClient? client) $default,) {final _that = this; switch (_that) { case _ServerState(): -return $default(_that.spi,_that.status,_that.conn,_that.client,_that.updateFuture);case _: +return $default(_that.spi,_that.status,_that.conn,_that.client);case _: throw StateError('Unexpected subclass'); } @@ -204,10 +203,10 @@ return $default(_that.spi,_that.status,_that.conn,_that.client,_that.updateFutur /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( Spi spi, ServerStatus status, ServerConn conn, SSHClient? client, Future? updateFuture)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( Spi spi, ServerStatus status, ServerConn conn, SSHClient? client)? $default,) {final _that = this; switch (_that) { case _ServerState() when $default != null: -return $default(_that.spi,_that.status,_that.conn,_that.client,_that.updateFuture);case _: +return $default(_that.spi,_that.status,_that.conn,_that.client);case _: return null; } @@ -219,14 +218,13 @@ return $default(_that.spi,_that.status,_that.conn,_that.client,_that.updateFutur class _ServerState implements ServerState { - const _ServerState({required this.spi, required this.status, this.conn = ServerConn.disconnected, this.client, this.updateFuture}); + const _ServerState({required this.spi, required this.status, this.conn = ServerConn.disconnected, this.client}); @override final Spi spi; @override final ServerStatus status; @override@JsonKey() final ServerConn conn; @override final SSHClient? client; -@override final Future? updateFuture; /// Create a copy of ServerState /// with the given fields replaced by the non-null parameter values. @@ -238,16 +236,16 @@ _$ServerStateCopyWith<_ServerState> get copyWith => __$ServerStateCopyWithImpl<_ @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ServerState&&(identical(other.spi, spi) || other.spi == spi)&&(identical(other.status, status) || other.status == status)&&(identical(other.conn, conn) || other.conn == conn)&&(identical(other.client, client) || other.client == client)&&(identical(other.updateFuture, updateFuture) || other.updateFuture == updateFuture)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ServerState&&(identical(other.spi, spi) || other.spi == spi)&&(identical(other.status, status) || other.status == status)&&(identical(other.conn, conn) || other.conn == conn)&&(identical(other.client, client) || other.client == client)); } @override -int get hashCode => Object.hash(runtimeType,spi,status,conn,client,updateFuture); +int get hashCode => Object.hash(runtimeType,spi,status,conn,client); @override String toString() { - return 'ServerState(spi: $spi, status: $status, conn: $conn, client: $client, updateFuture: $updateFuture)'; + return 'ServerState(spi: $spi, status: $status, conn: $conn, client: $client)'; } @@ -258,7 +256,7 @@ abstract mixin class _$ServerStateCopyWith<$Res> implements $ServerStateCopyWith factory _$ServerStateCopyWith(_ServerState value, $Res Function(_ServerState) _then) = __$ServerStateCopyWithImpl; @override @useResult $Res call({ - Spi spi, ServerStatus status, ServerConn conn, SSHClient? client, Future? updateFuture + Spi spi, ServerStatus status, ServerConn conn, SSHClient? client }); @@ -275,14 +273,13 @@ class __$ServerStateCopyWithImpl<$Res> /// Create a copy of ServerState /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? spi = null,Object? status = null,Object? conn = null,Object? client = freezed,Object? updateFuture = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? spi = null,Object? status = null,Object? conn = null,Object? client = freezed,}) { return _then(_ServerState( spi: null == spi ? _self.spi : spi // ignore: cast_nullable_to_non_nullable as Spi,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable as ServerStatus,conn: null == conn ? _self.conn : conn // ignore: cast_nullable_to_non_nullable as ServerConn,client: freezed == client ? _self.client : client // ignore: cast_nullable_to_non_nullable -as SSHClient?,updateFuture: freezed == updateFuture ? _self.updateFuture : updateFuture // ignore: cast_nullable_to_non_nullable -as Future?, +as SSHClient?, )); } diff --git a/lib/data/provider/server/single.g.dart b/lib/data/provider/server/single.g.dart index 7556a58b..bfc9f2bb 100644 --- a/lib/data/provider/server/single.g.dart +++ b/lib/data/provider/server/single.g.dart @@ -58,7 +58,7 @@ final class ServerNotifierProvider } } -String _$serverNotifierHash() => r'185c6b4546c3bc526f5b2ca79d16aed665818863'; +String _$serverNotifierHash() => r'04b1beef4d96242fd10d5b523c6f5f17eb774bae'; final class ServerNotifierFamily extends $Family with diff --git a/lib/view/page/server/detail/view.dart b/lib/view/page/server/detail/view.dart index 31a25243..1fffa47e 100644 --- a/lib/view/page/server/detail/view.dart +++ b/lib/view/page/server/detail/view.dart @@ -63,6 +63,9 @@ class _ServerDetailPageState extends ConsumerState with Single final _netSortType = ValueNotifier(_NetSortType.device); late final _collapse = _settings.collapseUIDefault.fetch(); late final _textFactor = TextScaler.linear(_settings.textFactor.fetch()); + late final _cpuViewAsProgress = _settings.cpuViewAsProgress.fetch(); + late final _moveServerFuncs = _settings.moveServerFuncs.fetch(); + late final _displayCpuIndex = _settings.displayCpuIndex.fetch(); @override void dispose() { @@ -97,7 +100,7 @@ class _ServerDetailPageState extends ConsumerState with Single } Widget _buildMainPage(ServerState si) { - final buildFuncs = !Stores.setting.moveServerFuncs.fetch(); + final buildFuncs = !_moveServerFuncs; final logo = _buildLogo(si); final children = [if (logo != null) logo, if (buildFuncs) ServerFuncBtns(spi: si.spi)]; for (final card in _cardsOrder) { @@ -197,7 +200,7 @@ class _ServerDetailPageState extends ConsumerState with Single ]); } - final List children = Stores.setting.cpuViewAsProgress.fetch() + final List children = _cpuViewAsProgress ? _buildCPUProgress(ss.cpu) : [_buildCPUChart(ss)]; @@ -258,7 +261,7 @@ class _ServerDetailPageState extends ConsumerState with Single const kRowThreshold = 4; const kCoresCountThreshold = kMaxColumn * kRowThreshold; final children = []; - final displayCpuIndexSetting = Stores.setting.displayCpuIndex.fetch(); + final displayCpuIndexSetting = _displayCpuIndex; if (cs.coresCount > kCoresCountThreshold) { final numCoresToDisplay = cs.coresCount - 1; diff --git a/pubspec.lock b/pubspec.lock index 1243ea8a..5b574c3c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -205,10 +205,10 @@ packages: dependency: transitive description: name: camera_web - sha256: "595f28c89d1fb62d77c73c633193755b781c6d2e0ebcd8dc25b763b514e6ba8f" + sha256: "57f49a635c8bf249d07fb95eb693d7e4dda6796dedb3777f9127fb54847beba7" url: "https://pub.dev" source: hosted - version: "0.3.5" + version: "0.3.5+3" characters: dependency: transitive description: @@ -440,10 +440,10 @@ packages: dependency: transitive description: name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" file: dependency: transitive description: @@ -727,18 +727,18 @@ packages: dependency: transitive description: name: hive_ce - sha256: "81d39a03c4c0ba5938260a8c3547d2e71af59defecea21793d57fc3551f0d230" + sha256: "29f8791bf13fa6cf7435a58f1f82a7c9706973c867affa77c34d91e105762664" url: "https://pub.dev" source: hosted - version: "2.15.1" + version: "2.17.0" hive_ce_flutter: dependency: "direct main" description: name: hive_ce_flutter - sha256: "26d656c9e8974f0732f1d09020e2d7b08ba841b8961a02dbfb6caf01474b0e9a" + sha256: "2677e95a333ff15af43ccd06af7eb7abbf1a4f154ea071997f3de4346cae913a" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.3.4" hive_ce_generator: dependency: "direct dev" description: @@ -831,10 +831,10 @@ packages: dependency: transitive description: name: isolate_channel - sha256: f3d36f783b301e6b312c3450eeb2656b0e7d1db81331af2a151d9083a3f6b18d + sha256: "68191008e3a219bc87cc8cddbcd1e29810bd9f3a0fdc2108b574ccbd9aafda08" url: "https://pub.dev" source: hosted - version: "0.2.2+1" + version: "0.3.0" isolate_contactor: dependency: transitive description: @@ -1750,10 +1750,10 @@ packages: dependency: transitive description: name: watcher - sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.2.1" web: dependency: transitive description: