Skip to content

Commit

Permalink
Reorganize chart pane controller code and prepare structure for offli…
Browse files Browse the repository at this point in the history
…ne mode. (#7572)
  • Loading branch information
polina-c authored Apr 15, 2024
1 parent 99e35c9 commit f175892
Show file tree
Hide file tree
Showing 29 changed files with 500 additions and 337 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Dependencies that create loops (inversions) are marked with `!`.
```mermaid
flowchart TD;
framework-->panes;
framework-->shared;
panes-->shared;
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Dependencies that create loops (inversions) are marked with `!`.

```mermaid
flowchart TD;
memory_controller.dart-->offline_data;
memory_screen.dart-->screen_body.dart;
memory_tabs.dart-->memory_controller.dart;
screen_body.dart-->memory_controller.dart;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,54 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

import 'package:devtools_app_shared/utils.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

import '../../../shared/memory/class_name.dart';
import '../../../shared/memory/heap_graph_loader.dart';
import '../../../shared/offline_data.dart';
import '../../../shared/primitives/simple_items.dart';
import '../../../shared/screen.dart';
import '../../../shared/utils.dart';
import '../panes/chart/controller/chart_pane_controller.dart';
import '../panes/control/controller/control_pane_controller.dart';
import '../panes/diff/controller/diff_pane_controller.dart';
import '../panes/profile/profile_pane_controller.dart';
import '../panes/tracing/tracing_pane_controller.dart';
import 'offline_data/offline_data.dart';

/// This class contains the business logic for memory screen, for a connected
/// application.
/// This class contains the business logic for memory screen.
///
/// This class must not have direct dependencies on web-only libraries. This
/// allows tests of the complicated logic in this class to run on the VM.
///
/// The controller should be recreated for every new connection.
class MemoryController extends DisposableController
with AutoDisposeControllerMixin {
with
AutoDisposeControllerMixin,
OfflineScreenControllerMixin<OfflineMemoryData> {
MemoryController({
@visibleForTesting DiffPaneController? diffPaneController,
@visibleForTesting ProfilePaneController? profilePaneController,
@visibleForTesting DiffPaneController? connectedDiff,
@visibleForTesting ProfilePaneController? connectedProfile,
}) {
diff = diffPaneController ?? _createDiffController();
profile = profilePaneController ?? ProfilePaneController();

shareClassFilterBetweenProfileAndDiff();
if (connectedDiff != null || connectedProfile != null) {
_mode = DevToolsMode.connected;
} else {
_mode = devToolsMode;
}
unawaited(_init(connectedDiff, connectedProfile));
}

Future<void> get initialized => _initialized.future;
final _initialized = Completer<void>();

/// DevTools mode at the time of creation of the controller.
///
/// DevTools will recreate controller when the mode changes.
// ignore: unused_field, TODO(polina-c): https://github.com/flutter/devtools/issues/6972
final DevToolsMode _mode = devToolsMode;
late final DevToolsMode _mode;

/// Index of the selected feature tab.
///
Expand All @@ -48,12 +59,15 @@ class MemoryController extends DisposableController
/// instead of the widget state.
int selectedFeatureTabIndex = 0;

late DiffPaneController diff;
late ProfilePaneController profile;
late MemoryChartPaneController chart = MemoryChartPaneController();
TracingPaneController tracing = TracingPaneController();
late final MemoryControlPaneController control =
MemoryControlPaneController(chart.memoryTimeline);
late final DiffPaneController diff;

late final ProfilePaneController profile;

late final MemoryChartPaneController chart;

late final TracingPaneController tracing;

late final MemoryControlPaneController control;

@override
void dispose() {
Expand All @@ -65,26 +79,85 @@ class MemoryController extends DisposableController
profile.dispose();
}

DiffPaneController _createDiffController() =>
DiffPaneController(HeapGraphLoaderRuntime(chart.memoryTimeline));
Future<void> _init(
@visibleForTesting DiffPaneController? connectedDiff,
@visibleForTesting ProfilePaneController? connectedProfile,
) async {
assert(!_initialized.isCompleted);
switch (_mode) {
case DevToolsMode.disconnected:
// TODO(polina-c): load memory screen in disconnected mode, https://github.com/flutter/devtools/issues/6972
_initializeData();
case DevToolsMode.connected:
_initializeData(
diffPaneController: connectedDiff,
profilePaneController: connectedProfile,
);
case DevToolsMode.offlineData:
assert(connectedDiff == null && connectedProfile == null);
await maybeLoadOfflineData(
ScreenMetaData.memory.id,
createData: (json) => OfflineMemoryData.parse(json),
shouldLoad: (data) => true,
);
// [maybeLoadOfflineData] will be a noop if there is no offline data for the memory screen,
// so ensure we still call [_initializedData] if it has not been called.
if (!_initialized.isCompleted) _initializeData();
assert(_initialized.isCompleted);
}
assert(_initialized.isCompleted);
}

void reset() {
diff.dispose();
diff = _createDiffController();
void _initializeData({
OfflineMemoryData? offlineData,
@visibleForTesting DiffPaneController? diffPaneController,
@visibleForTesting ProfilePaneController? profilePaneController,
}) {
assert(!_initialized.isCompleted);

chart = offlineData?.chart ?? MemoryChartPaneController(_mode);
diff = diffPaneController ??
offlineData?.diff ??
DiffPaneController(
loader: HeapGraphLoaderRuntime(chart.memoryTimeline),
);
profile = profilePaneController ??
offlineData?.profile ??
ProfilePaneController();
control = MemoryControlPaneController(
chart.memoryTimeline,
isChartVisible: chart.isChartVisible,
exportData: exportData,
);
tracing = TracingPaneController();
selectedFeatureTabIndex =
offlineData?.selectedTab ?? selectedFeatureTabIndex;
if (offlineData != null) profile.setFilter(offlineData.filter);
_shareClassFilterBetweenProfileAndDiff();

profile.dispose();
profile = ProfilePaneController();
_initialized.complete();
}

tracing.dispose();
tracing = TracingPaneController();
@override
OfflineScreenData prepareOfflineScreenData() => OfflineScreenData(
screenId: ScreenMetaData.memory.id,
data: OfflineMemoryData(
diff,
profile,
chart,
profile.classFilter.value,
selectedTab: selectedFeatureTabIndex,
).prepareForOffline(),
);

chart.memoryTimeline.reset();
@override
FutureOr<void> processOfflineData(OfflineMemoryData offlineData) {
assert(!_initialized.isCompleted);
_initializeData(offlineData: offlineData);
}

void shareClassFilterBetweenProfileAndDiff() {
diff.derived.applyFilter(
profile.classFilter.value,
);
void _shareClassFilterBetweenProfileAndDiff() {
diff.derived.applyFilter(profile.classFilter.value);

profile.classFilter.addListener(() {
diff.derived.applyFilter(profile.classFilter.value);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2024 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import '../../panes/chart/controller/chart_pane_controller.dart';
import '../../panes/diff/controller/diff_pane_controller.dart';
import '../../panes/profile/profile_pane_controller.dart';
import '../../shared/heap/class_filter.dart';

class _Json {
static const selectedTab = 'selectedTab';
static const classFilter = 'classFilter';
static const diffData = 'diffData';
static const profileData = 'profileData';
static const chartData = 'chartData';
}

class OfflineMemoryData {
OfflineMemoryData(
this.diff,
this.profile,
this.chart,
this.filter, {
required this.selectedTab,
});

// TODO(polina-c): use an extension type for the Json parsing, https://github.com/flutter/devtools/issues/6972
// https://github.com/flutter/devtools/pull/7572#discussion_r1563102256
factory OfflineMemoryData.parse(Map<String, dynamic> json) {
Map<String, dynamic> item(String key) =>
json[key] as Map<String, dynamic>? ?? {};
return OfflineMemoryData(
DiffPaneController.parse(item(_Json.diffData)),
ProfilePaneController.parse(item(_Json.profileData)),
MemoryChartPaneController.parse(item(_Json.chartData)),
ClassFilter.parse(item(_Json.classFilter)),
selectedTab: json[_Json.selectedTab] as int? ?? 0,
);
}

final int selectedTab;
final ClassFilter filter; // filter is shared between tabs, so it's here

final DiffPaneController diff;
final ProfilePaneController profile;
final MemoryChartPaneController chart;

Map<String, dynamic> prepareForOffline() {
return {
_Json.selectedTab: selectedTab,
_Json.diffData: diff.prepareForOffline(),
_Json.profileData: profile.prepareForOffline(),
_Json.chartData: chart.prepareForOffline(),
_Json.classFilter: profile.classFilter.value.prepareForOffline(),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:devtools_app_shared/utils.dart';
import 'package:flutter/material.dart';

import '../../../shared/banner_messages.dart';
import '../../../shared/common_widgets.dart';
import '../../../shared/http/http_service.dart' as http_service;
import '../../../shared/screen.dart';
import '../../../shared/utils.dart';
Expand Down Expand Up @@ -52,21 +53,30 @@ class _ConnectedMemoryBodyState extends State<ConnectedMemoryBody>

@override
Widget build(BuildContext context) {
return Column(
key: MemoryChartPane.hoverKey,
children: [
MemoryControlPane(
controller: controller.control,
),
const SizedBox(height: intermediateSpacing),
MemoryChartPane(
chart: controller.chart,
keyFocusNode: _focusNode,
),
Expanded(
child: MemoryTabView(memoryController),
),
],
return FutureBuilder<void>(
future: memoryController.initialized,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Column(
key: MemoryChartPane.hoverKey,
children: [
MemoryControlPane(
controller: controller.control,
),
const SizedBox(height: intermediateSpacing),
MemoryChartPane(
chart: controller.chart,
keyFocusNode: _focusNode,
),
Expanded(
child: MemoryTabView(memoryController),
),
],
);
} else {
return const CenteredCircularProgressIndicator();
}
},
);
}
}
Loading

0 comments on commit f175892

Please sign in to comment.