Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/bundle/writer.rs
30636 views
1
use crate::{
2
component::{Component, ComponentId, Components, ComponentsRegistrator},
3
relationship::RelationshipHookMode,
4
world::EntityWorldMut,
5
};
6
use alloc::vec::Vec;
7
use bevy_ptr::OwningPtr;
8
use bumpalo::Bump;
9
use core::{alloc::Layout, ptr::NonNull};
10
11
/// Enables pushing components to internal scratch space (uses a bump allocator), which can then be
12
/// written as a dynamic bundle. The contents are cleared after each write and the allocated scratch
13
/// space is reused across writes.
14
///
15
/// Also see [`BundleWriter`].
16
#[derive(Default)]
17
pub struct BundleScratch {
18
// Correctness: this should never be made public or mismatched component ids could be inserted
19
component_ids: Vec<ComponentId>,
20
// Correctness: this should never be made public or arbitrary non-components could be inserted
21
component_ptrs: Vec<NonNull<u8>>,
22
// Safety: this cannot be exposed, otherwise `alloc.reset()` could be called in arbitrary places,
23
// which could invalidate the data stored in `component_ptrs`.
24
alloc: Bump,
25
}
26
27
impl BundleScratch {
28
/// Creates a new [`BundleWriter`] using this scratch space. For safety / correctness, this will
29
/// clear any existing components.
30
///
31
/// Note that for performance reasons this will _not_ clear the internal allocator. To avoid leaking,
32
/// make sure every component pushed to the [`BundleWriter`] is followed by either a
33
/// [`BundleWriter::write`] or a [`BundleScratch::manual_drop`].
34
#[inline]
35
pub fn writer<'a>(&'a mut self) -> BundleWriter<'a> {
36
// This is necessary to ensure safety / correctness is maintained in the context of catch_unwind
37
// or a skipped `write`
38
self.component_ids.clear();
39
self.component_ptrs.clear();
40
BundleWriter(self)
41
}
42
43
/// Returns true if there are currently no components stored in the scratch space.
44
#[inline]
45
pub fn is_empty(&self) -> bool {
46
self.component_ids.is_empty()
47
}
48
49
/// This will drop all components currently stored in the scratch space. This is generally used to
50
/// ensure drops occur in error scenarios.
51
///
52
/// # Safety
53
/// `components` must be from the same world as the components that were pushed to this writer.
54
pub unsafe fn manual_drop(&mut self, components: &Components) {
55
for (id, ptr) in self
56
.component_ids
57
.drain(..)
58
.zip(self.component_ptrs.drain(..))
59
{
60
if let Some(info) = components.get_info(id)
61
&& let Some(drop) = info.drop()
62
{
63
// SAFETY: ptr is a valid component that matches the given component id
64
unsafe {
65
let ptr = OwningPtr::new(ptr);
66
(drop)(ptr);
67
}
68
}
69
}
70
self.alloc.reset();
71
}
72
}
73
74
/// Enables pushing components to the internal [`BundleScratch`], which can then be
75
/// written as a dynamic bundle.
76
///
77
/// Components pushed to this writer should either be followed by a [`BundleWriter::write`] or a
78
/// [`BundleScratch::manual_drop`] to avoid leaking.
79
pub struct BundleWriter<'a>(&'a mut BundleScratch);
80
81
// SAFETY: The `NonNull`s in component_ptrs are always a `Component`, which is Send
82
unsafe impl Send for BundleScratch where Bump: Send {}
83
84
impl<'a> BundleWriter<'a> {
85
/// Pushes the given component to the back of the current bundle scratch space. It will register
86
/// the component in `components` if it does not already exist.
87
///
88
/// # Safety
89
///
90
/// `components` must be from the same world that all previous [`Self::push_component`] calls were called with,
91
/// and the _next_ [`Self::write`] call.
92
pub unsafe fn push_component<C: Component>(
93
&mut self,
94
components: &mut ComponentsRegistrator,
95
component: C,
96
) {
97
let id = components.register_component::<C>();
98
OwningPtr::make(component, |ptr| {
99
// SAFETY: ptr points to a C component value which matches the `id` looked up above.
100
// Layout matches C.
101
self.push_component_by_id(id, ptr, Layout::new::<C>());
102
});
103
}
104
105
/// Pushes the given component ptr to the back of the current bundle scratch space.
106
///
107
/// # Safety
108
///
109
/// `components` must be from the same world that all previous [`Self::push_component`] calls were called with,
110
/// and the _next_ [`Self::write`] call. `component` must point to a [`Component`] value that matches `id`.
111
/// `layout` must correspond to the layout of the [`Component`] type.
112
pub unsafe fn push_component_by_id(
113
&mut self,
114
id: ComponentId,
115
component: OwningPtr<'_>,
116
layout: Layout,
117
) {
118
let ptr = self.0.alloc.alloc_layout(layout);
119
core::ptr::copy(component.as_ptr(), ptr.as_ptr(), layout.size());
120
self.0.component_ids.push(id);
121
self.0.component_ptrs.push(ptr);
122
}
123
124
/// Writes the current contents of the bundle to the given `entity` and clears the scratch space.
125
///
126
/// # Safety
127
///
128
/// `entity` must be from the same world that all [`Self::push_component`] calls since the last
129
/// [`Self::write`] were called with.
130
pub unsafe fn write(self, entity: &mut EntityWorldMut) {
131
// SAFETY:
132
// - All `component_ids` are from the same world as `entity`
133
// - All `component_data_ptrs` are valid types represented by `component_ids`
134
unsafe {
135
entity.insert_by_ids_internal(
136
&self.0.component_ids,
137
self.0
138
.component_ptrs
139
.drain(..)
140
.map(|ptr| OwningPtr::new(ptr)),
141
RelationshipHookMode::Run,
142
);
143
}
144
self.0.component_ids.clear();
145
self.0.alloc.reset();
146
}
147
148
/// Returns true if there are currently no components.
149
#[inline]
150
pub fn is_empty(&self) -> bool {
151
self.0.component_ids.is_empty()
152
}
153
}
154
155
#[cfg(test)]
156
mod tests {
157
use crate::{bundle::BundleScratch, component::Component, name::Name, world::World};
158
159
#[test]
160
fn write_component() {
161
#[derive(Component)]
162
struct X;
163
164
let mut world = World::new();
165
let mut bundle_scratch = BundleScratch::default();
166
let mut bundle_writer = bundle_scratch.writer();
167
// SAFETY: the same world is used for every bundle_writer operation
168
unsafe {
169
let mut components = world.components_registrator();
170
bundle_writer.push_component(&mut components, X);
171
bundle_writer.push_component(&mut components, Name::new("Hi"));
172
let mut entity = world.spawn_empty();
173
bundle_writer.write(&mut entity);
174
175
assert_eq!(entity.get::<Name>().unwrap().as_str(), "Hi");
176
assert!(entity.contains::<X>());
177
}
178
}
179
}
180
181