1
- use std:: mem:: { ManuallyDrop , MaybeUninit } ;
1
+ use std:: { mem:: MaybeUninit , ptr:: NonNull } ;
2
+
3
+ use bevy_ptr:: { OwningPtr , Unaligned } ;
2
4
3
5
use super :: Command ;
4
6
use crate :: world:: World ;
5
7
6
8
struct CommandMeta {
9
+ /// Offset from the start of `CommandQueue.bytes` at which the corresponding command is stored.
7
10
offset : usize ,
8
- func : unsafe fn ( value : * mut MaybeUninit < u8 > , world : & mut World ) ,
11
+ /// SAFETY: The `value` must point to a value of type `T: Command`,
12
+ /// where `T` is some specific type that was used to produce this metadata.
13
+ apply_command : unsafe fn ( value : OwningPtr < Unaligned > , world : & mut World ) ,
9
14
}
10
15
11
16
/// A queue of [`Command`]s
12
17
//
13
- // NOTE: [`CommandQueue`] is implemented via a `Vec<MaybeUninit<u8>>` over a `Vec<Box<dyn Command>>`
18
+ // NOTE: [`CommandQueue`] is implemented via a `Vec<MaybeUninit<u8>>` instead of a `Vec<Box<dyn Command>>`
14
19
// as an optimization. Since commands are used frequently in systems as a way to spawn
15
20
// entities/components/resources, and it's not currently possible to parallelize these
16
21
// due to mutable [`World`] access, maximizing performance for [`CommandQueue`] is
17
22
// preferred to simplicity of implementation.
18
23
#[ derive( Default ) ]
19
24
pub struct CommandQueue {
25
+ /// Densely stores the data for all commands in the queue.
20
26
bytes : Vec < MaybeUninit < u8 > > ,
27
+ /// Metadata for each command stored in the queue.
28
+ /// SAFETY: Each entry must have a corresponding value stored in `bytes`,
29
+ /// stored at offset `CommandMeta.offset` and with an underlying type matching `CommandMeta.apply_command`.
21
30
metas : Vec < CommandMeta > ,
22
31
}
23
32
@@ -34,45 +43,45 @@ impl CommandQueue {
34
43
where
35
44
C : Command ,
36
45
{
37
- /// SAFETY: This function is only every called when the `command` bytes is the associated
38
- /// [`Commands`] `T` type. Also this only reads the data via `read_unaligned` so unaligned
39
- /// accesses are safe.
40
- unsafe fn write_command < T : Command > ( command : * mut MaybeUninit < u8 > , world : & mut World ) {
41
- let command = command. cast :: < T > ( ) . read_unaligned ( ) ;
42
- command. write ( world) ;
43
- }
44
-
45
- let size = std:: mem:: size_of :: < C > ( ) ;
46
46
let old_len = self . bytes . len ( ) ;
47
47
48
+ // SAFETY: After adding the metadata, we correctly write the corresponding `command`
49
+ // of type `C` into `self.bytes`. Zero-sized commands do not get written into the buffer,
50
+ // so we'll just use a dangling pointer, which is valid for zero-sized types.
48
51
self . metas . push ( CommandMeta {
49
52
offset : old_len,
50
- func : write_command :: < C > ,
53
+ apply_command : |command, world| {
54
+ // SAFETY: According to the invariants of `CommandMeta.apply_command`,
55
+ // `command` must point to a value of type `C`.
56
+ let command: C = unsafe { command. read_unaligned ( ) } ;
57
+ command. write ( world) ;
58
+ } ,
51
59
} ) ;
52
60
53
- // Use `ManuallyDrop` to forget `command` right away, avoiding
54
- // any use of it after the `ptr::copy_nonoverlapping`.
55
- let command = ManuallyDrop :: new ( command) ;
56
-
61
+ let size = std:: mem:: size_of :: < C > ( ) ;
57
62
if size > 0 {
63
+ // Ensure that the buffer has enough space at the end to fit a value of type `C`.
64
+ // Since `C` is non-zero sized, this also guarantees that the buffer is non-null.
58
65
self . bytes . reserve ( size) ;
59
66
60
- // SAFETY: The internal `bytes` vector has enough storage for the
61
- // command (see the call the `reserve` above), the vector has
62
- // its length set appropriately and can contain any kind of bytes.
63
- // In case we're writing a ZST and the `Vec` hasn't allocated yet
64
- // then `as_mut_ptr` will be a dangling (non null) pointer, and
65
- // thus valid for ZST writes.
66
- // Also `command` is forgotten so that when `apply` is called
67
- // later, a double `drop` does not occur.
68
- unsafe {
69
- std:: ptr:: copy_nonoverlapping (
70
- & * command as * const C as * const MaybeUninit < u8 > ,
71
- self . bytes . as_mut_ptr ( ) . add ( old_len) ,
72
- size,
73
- ) ;
74
- self . bytes . set_len ( old_len + size) ;
75
- }
67
+ // SAFETY: The buffer must be at least as long as `old_len`, so this operation
68
+ // will not overflow the pointer's original allocation.
69
+ let ptr: * mut C = unsafe { self . bytes . as_mut_ptr ( ) . add ( old_len) . cast ( ) } ;
70
+
71
+ // Transfer ownership of the command into the buffer.
72
+ // SAFETY: `ptr` must be non-null, since it is within a non-null buffer.
73
+ // The call to `reserve()` ensures that the buffer has enough space to fit a value of type `C`,
74
+ // and it is valid to write any bit pattern since the underlying buffer is of type `MaybeUninit<u8>`.
75
+ unsafe { ptr. write_unaligned ( command) } ;
76
+
77
+ // Grow the vector to include the command we just wrote.
78
+ // SAFETY: Due to the call to `.reserve(size)` above,
79
+ // this is guaranteed to fit in the vector's capacity.
80
+ unsafe { self . bytes . set_len ( old_len + size) } ;
81
+ } else {
82
+ // Instead of writing zero-sized types into the buffer, we'll just use a dangling pointer.
83
+ // We must forget the command so it doesn't get double-dropped when the queue gets applied.
84
+ std:: mem:: forget ( command) ;
76
85
}
77
86
}
78
87
@@ -83,17 +92,22 @@ impl CommandQueue {
83
92
// flush the previously queued entities
84
93
world. flush ( ) ;
85
94
86
- // SAFETY: In the iteration below, `meta.func` will safely consume and drop each pushed command .
87
- // This operation is so that we can reuse the bytes `Vec<u8>`'s internal storage and prevent
88
- // unnecessary allocations .
95
+ // Reset the buffer, so it can be reused after this function ends .
96
+ // In the loop below, ownership of each command will be transferred into user code.
97
+ // SAFETY: `set_len(0)` is always valid .
89
98
unsafe { self . bytes . set_len ( 0 ) } ;
90
99
91
100
for meta in self . metas . drain ( ..) {
92
- // SAFETY: The implementation of `write_command` is safe for the according Command type.
93
- // It's ok to read from `bytes.as_mut_ptr()` because we just wrote to it in `push`.
94
- // The bytes are safely cast to their original type, safely read, and then dropped.
101
+ // SAFETY: `CommandQueue` guarantees that each metadata must have a corresponding value stored in `self.bytes`,
102
+ // so this addition will not overflow its original allocation.
103
+ let cmd = unsafe { self . bytes . as_mut_ptr ( ) . add ( meta. offset ) } ;
104
+ // SAFETY: It is safe to transfer ownership out of `self.bytes`, since the call to `set_len(0)` above
105
+ // gaurantees that nothing stored in the buffer will get observed after this function ends.
106
+ // `cmd` points to a valid address of a stored command, so it must be non-null.
107
+ let cmd = unsafe { OwningPtr :: new ( NonNull :: new_unchecked ( cmd. cast ( ) ) ) } ;
108
+ // SAFETY: The underlying type of `cmd` matches the type expected by `meta.apply_command`.
95
109
unsafe {
96
- ( meta. func ) ( self . bytes . as_mut_ptr ( ) . add ( meta . offset ) , world) ;
110
+ ( meta. apply_command ) ( cmd , world) ;
97
111
}
98
112
}
99
113
}
0 commit comments