-
-
Notifications
You must be signed in to change notification settings - Fork 912
/
scheduler.dart
147 lines (125 loc) · 4.52 KB
/
scheduler.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
part of '../framework.dart';
/// A listener used to determine when providers should rebuild.
/// This is used to synchronize provider rebuilds when widget rebuilds.
@internal
typedef Vsync = void Function(void Function());
void _defaultVsync(void Function() task) {
Future(task);
}
/// The object that handles when providers are refreshed and disposed.
///
/// Providers are typically refreshed at the end of the frame where they
/// notified that they wanted to rebuild.
///
/// Providers are disposed if they spent at least one full frame without any listener.
@internal
class ProviderScheduler {
var _disposed = false;
/// A way to override [vsync], used by Flutter to synchronize a container
/// with the widget tree.
@internal
final flutterVsyncs = <Vsync>{};
/// A function that controls the refresh rate of providers.
///
/// Defaults to refreshing providers at the end of the next event-loop.
@internal
void Function(void Function()) get vsync {
if (flutterVsyncs.isNotEmpty) {
// Notify all InheritedWidgets of a possible rebuild.
// At the same time, we only execute the task once, in whichever
// InheritedWidget that rebuilds first.
return (task) {
var invoked = false;
void invoke() {
if (invoked) return;
invoked = true;
task();
}
for (final flutterVsync in flutterVsyncs) {
flutterVsync(invoke);
}
};
}
return _defaultVsync;
}
final _stateToDispose = <AutoDisposeProviderElementMixin<Object?>>[];
final _stateToRefresh = <ProviderElementBase>[];
Completer<void>? _pendingTaskCompleter;
/// A future that completes when the next task is done.
Future<void>? get pendingFuture => _pendingTaskCompleter?.future;
/// Schedules a provider to be refreshed.
///
/// The refresh will happen at the end of the next event-loop,
/// and only if the provider is active.
void scheduleProviderRefresh(ProviderElementBase element) {
_stateToRefresh.add(element);
_scheduleTask();
}
void _scheduleTask() {
// Don't schedule a task if there is already one pending or if the scheduler
// is disposed.
// It is possible that during disposal of a ProviderContainer, if a provider
// uses ref.keepAlive(), the keepAlive closure will try to schedule a task.
// In this case, we don't want to schedule a task as the container is already
// disposed.
if (_pendingTaskCompleter != null || _disposed) return;
_pendingTaskCompleter = Completer<void>();
vsync(_task);
}
void _task() {
final pendingTaskCompleter = _pendingTaskCompleter;
if (pendingTaskCompleter == null) return;
pendingTaskCompleter.complete();
_performRefresh();
_performDispose();
_stateToRefresh.clear();
_stateToDispose.clear();
_pendingTaskCompleter = null;
}
void _performRefresh() {
/// No need to traverse entries from top to bottom, because refreshing a
/// child will automatically refresh its parent when it will try to read it
for (var i = 0; i < _stateToRefresh.length; i++) {
final element = _stateToRefresh[i];
if (element.hasListeners) element.flush();
}
}
/// Schedules a provider to be disposed.
///
/// The provider will be disposed at the end of the next event-loop,
void scheduleProviderDispose(
AutoDisposeProviderElementMixin<Object?> element,
) {
assert(
!element.hasListeners,
'Tried to dispose ${element._provider} , but still has listeners',
);
_stateToDispose.add(element);
_scheduleTask();
}
void _performDispose() {
/// No need to traverse entries from children to parents as a parent cannot
/// have no listener until its children are disposed first.
/// Worse case scenario, a parent will be added twice to the list (parent child parent)
/// but when the parent is traversed first, it will still have listeners,
/// and the second time it is traversed, it won't anymore.
for (var i = 0; i < _stateToDispose.length; i++) {
final element = _stateToDispose[i];
final links = element._keepAliveLinks;
// ignore: deprecated_member_use_from_same_package
if (element.maintainState ||
(links != null && links.isNotEmpty) ||
element.hasListeners ||
element._container._disposed) {
continue;
}
element._container._disposeProvider(element._origin);
}
}
/// Disposes the scheduler.
void dispose() {
_disposed = true;
_pendingTaskCompleter?.complete();
_pendingTaskCompleter = null;
}
}