18
18
use bevy:: {
19
19
prelude:: {
20
20
Entity , World , Query , QueryState , Added , With , Resource , Deref , DerefMut ,
21
+ Children , BuildWorldChildren , DespawnRecursiveExt ,
21
22
} ,
22
- ecs:: system:: SystemState ,
23
+ ecs:: system:: { SystemState , Command } ,
23
24
} ;
24
25
25
26
use smallvec:: SmallVec ;
26
27
27
28
use crate :: {
28
- ChannelQueue , WakeQueue , OperationRoster , ServiceHook , InputReady ,
29
- Cancel , UnusedTarget , ServiceLifecycle , ServiceLifecycleChannel ,
30
- OperationRequest ,
29
+ ChannelQueue , WakeQueue , OperationRoster , ServiceHook , Detached ,
30
+ UnusedTarget , ServiceLifecycle , ServiceLifecycleChannel ,
31
+ OperationRequest , ImpulseLifecycleChannel , AddImpulse , Finished ,
32
+ UnhandledErrors , UnusedTargetDrop ,
31
33
execute_operation, dispose_for_despawned_service,
32
34
} ;
33
35
36
+ #[ derive( Resource , Default ) ]
37
+ pub struct FlushParameters {
38
+ /// By default, a flush will loop until the whole [`OperationRoster`] is empty.
39
+ /// If there are loops of blocking services then it is possible for the flush
40
+ /// to loop indefinitely, creating the appearance of a blockup in the
41
+ /// application, or delaying other systems from running for a prolonged amount
42
+ /// of time.
43
+ ///
44
+ /// Use this limit to prevent the flush from blocking for too long in those
45
+ /// scenarios. If the flush loops beyond this limit, anything remaining in
46
+ /// the roster will be moved into the [`DeferredRoster`] to be processed
47
+ /// during the next flush.
48
+ ///
49
+ /// A value of `None` means the flush can loop indefinitely (this is the default).
50
+ pub flush_loop_limit : Option < usize > ,
51
+ }
52
+
34
53
#[ allow( private_interfaces) ]
35
54
pub fn flush_impulses (
36
55
world : & mut World ,
37
- input_ready_query : & mut QueryState < Entity , Added < InputReady > > ,
38
56
new_service_query : & mut QueryState < ( Entity , & mut ServiceHook ) , Added < ServiceHook > > ,
39
57
) {
40
58
let mut roster = OperationRoster :: new ( ) ;
59
+ collect_from_channels ( new_service_query, world, & mut roster) ;
41
60
42
- world. get_resource_or_insert_with ( || ServiceLifecycleChannel :: new ( ) ) ;
43
- world. resource_scope :: < ServiceLifecycleChannel , ( ) > ( |world, lifecycles| {
44
- // Clean up the dangling requests of any services that have been despawned.
45
- for removed_service in lifecycles. receiver . try_iter ( ) {
46
- dispose_for_despawned_service ( removed_service, world, & mut roster)
61
+ let mut loop_count = 0 ;
62
+ while !roster. is_empty ( ) {
63
+ if world. get_resource_or_insert_with (
64
+ || FlushParameters :: default ( )
65
+ ) . flush_loop_limit . is_some_and (
66
+ |limit| limit <= loop_count
67
+ ) {
68
+ // We have looped beyoond the limit, so we will defer anything that
69
+ // remains in the roster and stop looping from here.
70
+ world. get_resource_or_insert_with ( || DeferredRoster :: default ( ) )
71
+ . append ( & mut roster) ;
72
+ break ;
47
73
}
48
74
49
- // Add a lifecycle tracker to any new services that might have shown up
50
- for ( e, mut hook) in new_service_query. iter_mut ( world) {
51
- hook. lifecycle = Some ( ServiceLifecycle :: new ( e, lifecycles. sender . clone ( ) ) ) ;
75
+ garbage_cleanup ( world, & mut roster) ;
76
+
77
+ while let Some ( unblock) = roster. unblock . pop_front ( ) {
78
+ let serve_next = unblock. serve_next ;
79
+ serve_next ( unblock, world, & mut roster) ;
80
+ garbage_cleanup ( world, & mut roster) ;
52
81
}
53
- } ) ;
54
82
83
+ while let Some ( source) = roster. queue . pop_front ( ) {
84
+ execute_operation ( OperationRequest { source, world, roster : & mut roster } ) ;
85
+ garbage_cleanup ( world, & mut roster) ;
86
+ }
87
+
88
+ collect_from_channels ( new_service_query, world, & mut roster) ;
89
+ }
90
+ }
91
+
92
+ fn garbage_cleanup ( world : & mut World , roster : & mut OperationRoster ) {
93
+ while let Some ( cleanup) = roster. cleanup_finished . pop ( ) {
94
+ cleanup. trigger ( world, roster) ;
95
+ }
96
+
97
+ while let Some ( cancel) = roster. cancel . pop_front ( ) {
98
+ cancel. trigger ( world, roster) ;
99
+ }
100
+ }
101
+
102
+ fn collect_from_channels (
103
+ new_service_query : & mut QueryState < ( Entity , & mut ServiceHook ) , Added < ServiceHook > > ,
104
+ world : & mut World ,
105
+ roster : & mut OperationRoster ,
106
+ ) {
55
107
// Get the receiver for async task commands
56
108
let async_receiver = world. get_resource_or_insert_with ( || ChannelQueue :: new ( ) ) . receiver . clone ( ) ;
57
109
@@ -60,10 +112,24 @@ pub fn flush_impulses(
60
112
( item) ( world, & mut roster) ;
61
113
}
62
114
63
- // Queue any operations whose inputs are ready
64
- for e in input_ready_query. iter ( world) {
65
- roster. queue ( e) ;
66
- }
115
+ world. get_resource_or_insert_with ( || ServiceLifecycleChannel :: new ( ) ) ;
116
+ world. resource_scope ( |world, lifecycles : ServiceLifecycleChannel | {
117
+ // Clean up the dangling requests of any services that have been despawned.
118
+ for removed_service in lifecycles. receiver . try_iter ( ) {
119
+ dispose_for_despawned_service ( removed_service, world, & mut roster)
120
+ }
121
+
122
+ // Add a lifecycle tracker to any new services that might have shown up
123
+ // TODO(@mxgrey): Make sure this works for services which are spawned by
124
+ // providers that are being flushed.
125
+ for ( e, mut hook) in new_service_query. iter_mut ( world) {
126
+ hook. lifecycle = Some ( ServiceLifecycle :: new ( e, lifecycles. sender . clone ( ) ) ) ;
127
+ }
128
+ } ) ;
129
+
130
+ // Queue any operations that needed to be deferred
131
+ let mut deferred = world. get_resource_or_insert_with ( || DeferredRoster :: default ( ) ) ;
132
+ roster. append ( & mut deferred) ;
67
133
68
134
// Collect any tasks that are ready to be woken
69
135
for wakeable in world
@@ -74,49 +140,103 @@ pub fn flush_impulses(
74
140
roster. queue ( wakeable) ;
75
141
}
76
142
77
- let mut unused_targets_state: SystemState < Query < Entity , With < UnusedTarget > > > =
143
+ let mut unused_targets_state: SystemState < Query < ( Entity , & Detached ) , With < UnusedTarget > > > =
78
144
SystemState :: new ( world) ;
79
- let mut unused_targets: SmallVec < [ _ ; 8 ] > = unused_targets_state. get ( world) . iter ( ) . collect ( ) ;
80
- for target in unused_targets. drain ( ..) {
81
- roster. drop_dependency ( Cancel :: unused_target ( target) ) ;
145
+
146
+ let mut add_finish: SmallVec < [ _ ; 8 ] > = SmallVec :: new ( ) ;
147
+ let mut drop_targets: SmallVec < [ _ ; 8 ] > = SmallVec :: new ( ) ;
148
+ for ( e, detached) in unused_targets_state. get ( world) . iter ( ) {
149
+ if detached. is_detached ( ) {
150
+ add_finish. push ( e) ;
151
+ } else {
152
+ drop_targets. push ( e) ;
153
+ }
154
+ }
155
+
156
+ for e in add_finish {
157
+ // Add a Finished impulse to the unused target of a detached impulse
158
+ // chain.
159
+ AddImpulse :: new ( e, Finished ) . apply ( world) ;
160
+ }
161
+
162
+ for target in drop_targets. drain ( ..) {
163
+ drop_target ( target, world, roster, true ) ;
82
164
}
83
165
84
- unused_targets . extend (
166
+ drop_targets . extend (
85
167
world
86
- . get_resource_or_insert_with ( || DroppedPromiseQueue :: new ( ) )
168
+ . get_resource_or_insert_with ( || ImpulseLifecycleChannel :: default ( ) )
87
169
. receiver
88
170
. try_iter ( )
89
171
) ;
90
- for target in unused_targets. drain ( ..) {
91
- roster. drop_dependency ( Cancel :: dropped ( target) )
92
- }
93
172
94
- while !roster. is_empty ( ) {
95
- while let Some ( unblock) = roster. unblock . pop_front ( ) {
96
- let serve_next = unblock. serve_next ;
97
- serve_next ( unblock, world, & mut roster) ;
98
-
99
- while let Some ( cleanup) = roster. cleanup_finished . pop ( ) {
100
- cleanup. trigger ( world, & mut roster) ;
101
- }
173
+ for target in drop_targets. drain ( ..) {
174
+ drop_target ( target, world, roster, false ) ;
175
+ }
176
+ }
102
177
103
- while let Some ( cancel) = roster. cancel . pop_front ( ) {
104
- cancel. trigger ( world, & mut roster) ;
178
+ fn drop_target (
179
+ target : Entity ,
180
+ world : & mut World ,
181
+ roster : & mut OperationRoster ,
182
+ unused : bool ,
183
+ ) {
184
+ roster. purge ( target) ;
185
+ let mut dropped_impulses = Vec :: new ( ) ;
186
+ let mut detached_impulse = None ;
187
+
188
+ let mut impulse = target;
189
+ let mut search_state: SystemState < (
190
+ Query < & Children > ,
191
+ Query < & Detached > ,
192
+ ) > = SystemState :: new ( world) ;
193
+
194
+ let ( q_children, q_detached) = search_state. get ( world) ;
195
+ loop {
196
+ if let Ok ( children) = q_children. get ( impulse) {
197
+ for child in children {
198
+ let Ok ( detached) = q_detached. get ( * child) else {
199
+ continue ;
200
+ } ;
201
+ if detached. is_detached ( ) {
202
+ // This child is detached so we will not include it in the
203
+ // dropped impulses. We need to de-parent it so that it does
204
+ // not get despawned with the rest of the impulses that we
205
+ // are dropping.
206
+ detached_impulse = Some ( * child) ;
207
+ break ;
208
+ } else {
209
+ // This child is not detached, so we will include it in our
210
+ // dropped impulses, and crawl towards one of it children.
211
+ if unused {
212
+ dropped_impulses. push ( impulse) ;
213
+ }
214
+ roster. purge ( impulse) ;
215
+ impulse = * child;
216
+ continue ;
217
+ }
105
218
}
106
219
}
107
220
108
- while let Some ( source) = roster. queue . pop_front ( ) {
109
- execute_operation ( OperationRequest { source, world, roster : & mut roster } ) ;
110
-
111
- while let Some ( cleanup) = roster. cleanup_finished . pop ( ) {
112
- cleanup. trigger ( world, & mut roster) ;
113
- }
221
+ // There is nothing further to include in the drop
222
+ break ;
223
+ }
114
224
115
- while let Some ( cancel ) = roster . cancel . pop_front ( ) {
116
- cancel . trigger ( world , & mut roster ) ;
117
- }
225
+ if let Some ( detached_impulse ) = detached_impulse {
226
+ if let Some ( mut detached_impulse_mut ) = world . get_entity_mut ( detached_impulse ) {
227
+ detached_impulse_mut . remove_parent ( ) ;
118
228
}
119
229
}
230
+
231
+ if let Some ( mut unused_target_mut) = world. get_entity_mut ( target) {
232
+ unused_target_mut. despawn_recursive ( ) ;
233
+ }
234
+
235
+ if unused {
236
+ world. get_resource_or_insert_with ( || UnhandledErrors :: default ( ) )
237
+ . unused_targets
238
+ . push ( UnusedTargetDrop { unused_target : target, dropped_impulses } ) ;
239
+ }
120
240
}
121
241
122
242
/// This resource is used to queue up operations in the roster in situations
0 commit comments