From 48554c3307dd6423298985a609a2b8cbd0782f89 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 30 Aug 2026 17:35:55 +0300 Subject: [PATCH] perf(workspace): offload massive grid selection statistics calculation to background compute - Introduce GridSelectionCalcEngine.computeAdaptive with computeThreshold of 5000 - Offload stats calculations on massive multi-cell selections to background isolate - Update ResultsTab onSelectionValuesChanged to leverage adaptive compute - Add unit tests for synchronous and isolate-based selection statistics calculation Closes #652 --- .../workspace/grid_selection_calc_engine.dart | 19 +++++++++++++++- lib/features/workspace/results_tab.dart | 20 ++++++++++++++--- .../workspace/data_grid_engines_test.dart | 22 +++++++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/lib/features/workspace/grid_selection_calc_engine.dart b/lib/features/workspace/grid_selection_calc_engine.dart index c7a111f6..b56d1291 100644 --- a/lib/features/workspace/grid_selection_calc_engine.dart +++ b/lib/features/workspace/grid_selection_calc_engine.dart @@ -1,5 +1,6 @@ import 'dart:math' as math; -import 'package:flutter/foundation.dart'; +import 'package:flutter/foundation.dart' as foundation; +import 'package:flutter/foundation.dart' show immutable; /// Aggregated statistical results for a selection of grid cell values. @immutable @@ -93,6 +94,22 @@ class GridCalcStats { /// Calculation engine for computing stats (Count, Distinct, Sum, Avg, Median, Min, Max, StdDev) on grid selections. abstract final class GridSelectionCalcEngine { + /// Default threshold for offloading stats calculation to a background isolate. + static const int computeThreshold = 5000; + + /// Computes statistics adaptively: synchronously for small lists (< [computeThreshold]), + /// and offloaded to a background isolate using [compute] for large selections. + static Future computeAdaptive( + List values, { + int threshold = computeThreshold, + }) async { + if (values.isEmpty) return GridCalcStats.empty; + if (values.length < threshold) { + return compute(values); + } + return foundation.compute(compute, values); + } + /// Computes statistics for a list of string cell values. static GridCalcStats compute(List values) { if (values.isEmpty) return GridCalcStats.empty; diff --git a/lib/features/workspace/results_tab.dart b/lib/features/workspace/results_tab.dart index 66fcc991..6335a3e6 100644 --- a/lib/features/workspace/results_tab.dart +++ b/lib/features/workspace/results_tab.dart @@ -371,9 +371,23 @@ class _ResultsTabState extends material.State { stagingBuffer: widget.stagingBuffer, onRowSelected: (row) => setState(() => _selectedRowIndex = row), onSelectionValuesChanged: (values) { - setState(() { - _selectionStats = GridSelectionCalcEngine.compute(values); - }); + if (values.isEmpty) { + setState(() => _selectionStats = GridCalcStats.empty); + return; + } + if (values.length < GridSelectionCalcEngine.computeThreshold) { + setState(() { + _selectionStats = GridSelectionCalcEngine.compute(values); + }); + } else { + unawaited( + GridSelectionCalcEngine.computeAdaptive(values).then((stats) { + if (mounted) { + setState(() => _selectionStats = stats); + } + }), + ); + } }, onCellFocused: (colName, cellVal, rowIdx) { setState(() { diff --git a/test/features/workspace/data_grid_engines_test.dart b/test/features/workspace/data_grid_engines_test.dart index 3ff65e9a..be209d34 100644 --- a/test/features/workspace/data_grid_engines_test.dart +++ b/test/features/workspace/data_grid_engines_test.dart @@ -209,6 +209,28 @@ void main() { final expectedEvenMedian = (evenParsed[499] + evenParsed[500]) / 2.0; expect(evenStats.median, equals(expectedEvenMedian)); }); + + test('computeAdaptive returns empty stats for empty list', () async { + final stats = await GridSelectionCalcEngine.computeAdaptive(const []); + expect(stats, equals(GridCalcStats.empty)); + }); + + test('computeAdaptive computes synchronously below threshold', () async { + final values = ['10', '20', '30']; + final stats = await GridSelectionCalcEngine.computeAdaptive(values, threshold: 10); + expect(stats.totalCount, 3); + expect(stats.sum, 60.0); + expect(stats.average, 20.0); + }); + + test('computeAdaptive computes via background isolate above threshold', () async { + final values = List.generate(100, (i) => '$i'); + final stats = await GridSelectionCalcEngine.computeAdaptive(values, threshold: 50); + expect(stats.totalCount, 100); + expect(stats.min, 0.0); + expect(stats.max, 99.0); + expect(stats.sum, 4950.0); + }); }); group('GridGroupingsEngine', () {