7
7
* @flow
8
8
*/
9
9
10
- import invariant from 'shared/invariant ' ;
10
+ import warning from 'shared/warning ' ;
11
11
import {
12
12
__onInteractionsScheduled ,
13
13
__onInteractionsStarting ,
@@ -16,20 +16,17 @@ import {
16
16
17
17
export { registerInteractionObserver } from './InteractionEmitter' ;
18
18
19
- type Interactions = Array < Interaction > ;
20
-
19
+ // Maps execution ID to Interactions for all scheduled continuations.
20
+ // We key off of ID because Interactions may be scheduled for multiple continuations.
21
+ // For example, an Interaction may schedule work with React at multiple priorities.
22
+ // Each priority would reserve a continuation (since they may be processed separately).
23
+ export type Continuations = Map < number , Interaction > ;
21
24
export type Interaction = { |
22
25
id : number ,
23
26
name : string ,
24
27
timestamp : number ,
25
28
| } ;
26
-
27
- export type Continuation = {
28
- __hasBeenRun : boolean ,
29
- __id : number ,
30
- __interactions : Interactions ,
31
- __prevInteractions : Interactions | null ,
32
- } ;
29
+ export type Interactions = Set < Interaction > ;
33
30
34
31
// Normally we would use the current renderer HostConfig's "now" method,
35
32
// But since interaction-tracking will be a separate package,
@@ -43,10 +40,33 @@ if (typeof performance === 'object' && typeof performance.now === 'function') {
43
40
now = ( ) => localDate . now ( ) ;
44
41
}
45
42
46
- let currentContinuation : Continuation | null = null ;
47
- let globalExecutionID : number = 0 ;
48
- let globalInteractionID : number = 0 ;
49
- let interactions : Interactions | null = null ;
43
+ // Counters used to generate unique IDs for all interactions,
44
+ // And InteractionObserver executions (including continuations).
45
+ let executionIDCounter : number = 0 ;
46
+ let interactionIDCounter : number = 0 ;
47
+
48
+ // Set of currently tracked interactions.
49
+ // Interactions "stack"–
50
+ // Meaning that newly tracked interactions are appended to the previously active set.
51
+ // When an interaction goes out of scope, the previous set (if any) is restored.
52
+ let interactions : Interactions = new Set ( ) ;
53
+
54
+ // Temporarily holds the Set of interactions masked by an active "continuation".
55
+ // This is necessary since continuations are started/stopped externally.
56
+ // This value enables previous interactions to be restored when the continuation ends.
57
+ // This implementation supports only one active continuation at any time.
58
+ // We could change this to a stack structure in the future if this requirement changed.
59
+ let interactionsMaskedByContinuation : Interactions = interactions ;
60
+
61
+ // Map continuation (execution) UIDs to interaction UIDs.
62
+ // These Maps are only used for DEV mode validation.
63
+ // Ideally we can replace these Maps with WeakMaps at some point.
64
+ let scheduledContinuations : Continuations | null = null ;
65
+ let startedContinuations : Continuations | null = null ;
66
+ if ( __DEV__ ) {
67
+ scheduledContinuations = new Map ( ) ;
68
+ startedContinuations = new Map ( ) ;
69
+ }
50
70
51
71
export function getCurrent ( ) : Interactions | null {
52
72
if ( ! __PROFILE__ ) {
@@ -56,77 +76,99 @@ export function getCurrent(): Interactions | null {
56
76
}
57
77
}
58
78
59
- export function reserveContinuation ( ) : Continuation | null {
79
+ // A "continuation" signifies that an interaction is not completed when its callback finish running.
80
+ // This is useful for React, since scheduled work may be batched and processed asynchronously.
81
+ // Continuations have a unique execution ID which must later be used to restore the interaction.
82
+ // This is done by calling startContinuations() before processing the delayed work,
83
+ // And stopContinuations() to indicate that the work has been completed.
84
+ export function reserveContinuation ( interaction : Interaction ) : number {
60
85
if ( ! __PROFILE__ ) {
61
- return null ;
86
+ return 0 ;
62
87
}
63
88
64
- if ( interactions !== null ) {
65
- const executionID = globalExecutionID ++ ;
89
+ const executionID = executionIDCounter ++ ;
66
90
67
- __onInteractionsScheduled ( interactions , executionID ) ;
91
+ __onInteractionsScheduled ( new Set ( [ interaction ] ) , executionID ) ;
68
92
69
- return {
70
- __hasBeenRun : false ,
71
- __id : executionID ,
72
- __interactions : interactions ,
73
- __prevInteractions : null ,
74
- } ;
75
- } else {
76
- return null ;
93
+ if ( __DEV__ ) {
94
+ ( ( scheduledContinuations : any ) : Continuations ) . set (
95
+ executionID ,
96
+ interaction ,
97
+ ) ;
77
98
}
99
+
100
+ return executionID ;
78
101
}
79
102
80
- export function startContinuation ( continuation : Continuation | null ) : void {
103
+ export function startContinuations ( continuations : Continuations ) : void {
81
104
if ( ! __PROFILE__ ) {
82
105
return ;
83
106
}
84
107
85
- invariant (
86
- currentContinuation === null ,
87
- 'Cannot start a continuation when one is already active.' ,
88
- ) ;
89
-
90
- if ( continuation === null ) {
91
- return ;
108
+ if ( __DEV__ ) {
109
+ warning (
110
+ ( ( startedContinuations : any ) : Continuations ) . size === 0 ,
111
+ 'Only one batch of continuations can be active at a time.' ,
112
+ ) ;
92
113
}
93
114
94
- invariant (
95
- ! continuation . __hasBeenRun ,
96
- 'A continuation can only be started once' ,
97
- ) ;
115
+ const continuationInteractions = new Set ( ) ;
116
+
117
+ const entries = Array . from ( continuations ) ;
118
+ for ( let index = 0 ; index < entries . length ; index ++ ) {
119
+ const [ executionID : number , interaction : Interaction ] = entries [ index ] ;
98
120
99
- continuation . __hasBeenRun = true ;
100
- currentContinuation = continuation ;
121
+ if ( __DEV__ ) {
122
+ warning (
123
+ ( ( scheduledContinuations : any ) : Continuations ) . get ( executionID ) ===
124
+ interaction ,
125
+ 'Cannot run an unscheduled continuation.' ,
126
+ ) ;
127
+
128
+ ( ( scheduledContinuations : any ) : Continuations ) . delete ( executionID ) ;
129
+ ( ( startedContinuations : any ) : Continuations ) . set (
130
+ executionID ,
131
+ interaction ,
132
+ ) ;
133
+ }
134
+
135
+ __onInteractionsStarting ( new Set ( [ interaction ] ) , executionID ) ;
136
+
137
+ continuationInteractions . add ( interaction ) ;
138
+ }
101
139
102
140
// Continuations should mask (rather than extend) any current interactions.
103
141
// Upon completion of a continuation, previous interactions will be restored.
104
- continuation . __prevInteractions = interactions ;
105
- interactions = continuation . __interactions ;
106
-
107
- __onInteractionsStarting ( interactions , continuation . __id ) ;
142
+ interactionsMaskedByContinuation = interactions ;
143
+ interactions = continuationInteractions ;
108
144
}
109
145
110
- export function stopContinuation ( continuation : Continuation ) : void {
146
+ export function stopContinuations ( continuations : Continuations ) : void {
111
147
if ( ! __PROFILE__ ) {
112
148
return ;
113
149
}
114
150
115
- invariant (
116
- currentContinuation === continuation ,
117
- 'Cannot stop a continuation that is not active.' ,
118
- ) ;
151
+ // Stop interactions in the reverse order they were started.
152
+ const entries = Array . from ( continuations ) ;
153
+ for ( let index = entries . length - 1 ; index >= 0 ; index -- ) {
154
+ const [ executionID : number , interaction : Interaction ] = entries [ index ] ;
119
155
120
- if ( continuation === null ) {
121
- return ;
122
- }
156
+ if ( __DEV__ ) {
157
+ warning (
158
+ ( ( startedContinuations : any ) : Continuations ) . get ( executionID ) ===
159
+ interaction ,
160
+ 'Cannot stop an inactive continuation.' ,
161
+ ) ;
123
162
124
- __onInteractionsEnded ( continuation . __interactions , continuation . __id ) ;
163
+ ( ( startedContinuations : any ) : Continuations ) . delete ( executionID ) ;
164
+ }
125
165
126
- currentContinuation = null ;
166
+ __onInteractionsEnded ( new Set ( [ interaction ] ) , executionID ) ;
167
+ }
127
168
128
169
// Restore previous interactions.
129
- interactions = continuation . __prevInteractions ;
170
+ interactions = interactionsMaskedByContinuation ;
171
+ interactionsMaskedByContinuation = interactions ;
130
172
}
131
173
132
174
export function track ( name : string , callback : Function ) : void {
@@ -136,19 +178,19 @@ export function track(name: string, callback: Function): void {
136
178
}
137
179
138
180
const interaction : Interaction = {
139
- id : globalInteractionID ++ ,
181
+ id : interactionIDCounter ++ ,
140
182
name,
141
183
timestamp : now ( ) ,
142
184
} ;
143
185
144
- const executionID = globalExecutionID ++ ;
186
+ const executionID = executionIDCounter ++ ;
145
187
const prevInteractions = interactions ;
146
188
147
189
// Tracked interactions should stack/accumulate.
148
190
// To do that, clone the current interactions array.
149
191
// The previous interactions array will be restored upon completion.
150
- interactions =
151
- interactions === null ? [ interaction ] : interactions . concat ( interaction ) ;
192
+ interactions = new Set ( interactions ) ;
193
+ interactions . add ( interaction ) ;
152
194
153
195
try {
154
196
__onInteractionsScheduled ( interactions , executionID ) ;
@@ -171,7 +213,7 @@ export function wrap(callback: Function): Function {
171
213
return callback ;
172
214
}
173
215
174
- const executionID = globalExecutionID ++ ;
216
+ const executionID = executionIDCounter ++ ;
175
217
const wrappedInteractions = interactions ;
176
218
177
219
__onInteractionsScheduled ( wrappedInteractions , executionID ) ;
0 commit comments