@@ -9,8 +9,11 @@ struct CommandMeta {
9
9
/// SAFETY: The `value` must point to a value of type `T: Command`,
10
10
/// where `T` is some specific type that was used to produce this metadata.
11
11
///
12
+ /// `world` is optional to allow this one function pointer to perform double-duty as a drop.
13
+ ///
12
14
/// Returns the size of `T` in bytes.
13
- apply_command_and_get_size : unsafe fn ( value : OwningPtr < Unaligned > , world : & mut World ) -> usize ,
15
+ consume_command_and_get_size :
16
+ unsafe fn ( value : OwningPtr < Unaligned > , world : & mut Option < & mut World > ) -> usize ,
14
17
}
15
18
16
19
/// Densely and efficiently stores a queue of heterogenous types implementing [`Command`].
@@ -53,11 +56,16 @@ impl CommandQueue {
53
56
}
54
57
55
58
let meta = CommandMeta {
56
- apply_command_and_get_size : |command, world| {
57
- // SAFETY: According to the invariants of `CommandMeta.apply_command_and_get_size `,
59
+ consume_command_and_get_size : |command, world| {
60
+ // SAFETY: According to the invariants of `CommandMeta.consume_command_and_get_size `,
58
61
// `command` must point to a value of type `C`.
59
62
let command: C = unsafe { command. read_unaligned ( ) } ;
60
- command. apply ( world) ;
63
+ match world {
64
+ // Apply command to the provided world...
65
+ Some ( world) => command. apply ( world) ,
66
+ // ...or discard it.
67
+ None => drop ( command) ,
68
+ }
61
69
std:: mem:: size_of :: < C > ( )
62
70
} ,
63
71
} ;
@@ -97,6 +105,14 @@ impl CommandQueue {
97
105
// flush the previously queued entities
98
106
world. flush ( ) ;
99
107
108
+ self . apply_or_drop_queued ( Some ( world) ) ;
109
+ }
110
+
111
+ /// If `world` is [`Some`], this will apply the queued [commands](`Command`).
112
+ /// If `world` is [`None`], this will drop the queued [commands](`Command`) (without applying them).
113
+ /// This clears the queue.
114
+ #[ inline]
115
+ fn apply_or_drop_queued ( & mut self , mut world : Option < & mut World > ) {
100
116
// The range of pointers of the filled portion of `self.bytes`.
101
117
let bytes_range = self . bytes . as_mut_ptr_range ( ) ;
102
118
@@ -127,7 +143,7 @@ impl CommandQueue {
127
143
// SAFETY: The data underneath the cursor must correspond to the type erased in metadata,
128
144
// since they were stored next to each other by `.push()`.
129
145
// For ZSTs, the type doesn't matter as long as the pointer is non-null.
130
- let size = unsafe { ( meta. apply_command_and_get_size ) ( cmd, world) } ;
146
+ let size = unsafe { ( meta. consume_command_and_get_size ) ( cmd, & mut world) } ;
131
147
// Advance the cursor past the command. For ZSTs, the cursor will not move.
132
148
// At this point, it will either point to the next `CommandMeta`,
133
149
// or the cursor will be out of bounds and the loop will end.
@@ -143,6 +159,12 @@ impl CommandQueue {
143
159
}
144
160
}
145
161
162
+ impl Drop for CommandQueue {
163
+ fn drop ( & mut self ) {
164
+ self . apply_or_drop_queued ( None ) ;
165
+ }
166
+ }
167
+
146
168
#[ cfg( test) ]
147
169
mod test {
148
170
use super :: * ;
@@ -193,6 +215,27 @@ mod test {
193
215
assert_eq ! ( drops_b. load( Ordering :: Relaxed ) , 1 ) ;
194
216
}
195
217
218
+ /// Asserts that inner [commands](`Command`) are dropped on early drop of [`CommandQueue`].
219
+ /// Originally identified as an issue in [#10676](https://github.com/bevyengine/bevy/issues/10676)
220
+ #[ test]
221
+ fn test_command_queue_inner_drop_early ( ) {
222
+ let mut queue = CommandQueue :: default ( ) ;
223
+
224
+ let ( dropcheck_a, drops_a) = DropCheck :: new ( ) ;
225
+ let ( dropcheck_b, drops_b) = DropCheck :: new ( ) ;
226
+
227
+ queue. push ( dropcheck_a) ;
228
+ queue. push ( dropcheck_b) ;
229
+
230
+ assert_eq ! ( drops_a. load( Ordering :: Relaxed ) , 0 ) ;
231
+ assert_eq ! ( drops_b. load( Ordering :: Relaxed ) , 0 ) ;
232
+
233
+ drop ( queue) ;
234
+
235
+ assert_eq ! ( drops_a. load( Ordering :: Relaxed ) , 1 ) ;
236
+ assert_eq ! ( drops_b. load( Ordering :: Relaxed ) , 1 ) ;
237
+ }
238
+
196
239
struct SpawnCommand ;
197
240
198
241
impl Command for SpawnCommand {
0 commit comments