1use crate::command::{CommandOp, ErasedComponentValue};
7use crate::component::ComponentId;
8use crate::entity::EntityId;
9use crate::world::{World, WorldError};
10use alloc::boxed::Box;
11use alloc::vec::Vec;
12
13pub trait Bundle {
15 fn write(self, writer: &mut BundleWriter<'_>) -> Result<(), WorldError>;
16}
17
18pub struct DynamicBundle {
20 entries: Vec<DynamicEntry>,
21}
22
23struct DynamicEntry {
24 component_id: ComponentId,
25 value: Option<Box<dyn ErasedComponentValue>>,
26}
27
28impl DynamicBundle {
29 pub fn new() -> Self {
31 Self {
32 entries: Vec::new(),
33 }
34 }
35
36 pub fn push<T: 'static>(&mut self, world: &World, value: T) -> Result<(), WorldError> {
38 let component_id = world.component_id::<T>()?;
39 if world.is_tag_component(&component_id) {
40 return Err(WorldError::WrongStorageKind {
41 name: alloc::string::String::from("tag components cannot carry values"),
42 });
43 }
44 self.push_entry(component_id, Some(Box::new(value)))
45 }
46
47 pub fn push_tag(&mut self, tag: &ComponentId) -> Result<(), WorldError> {
49 self.push_entry(tag.clone(), None)
50 }
51
52 fn push_entry(
53 &mut self,
54 component_id: ComponentId,
55 value: Option<Box<dyn ErasedComponentValue>>,
56 ) -> Result<(), WorldError> {
57 if self
58 .entries
59 .iter()
60 .any(|entry| entry.component_id.index() == component_id.index())
61 {
62 return Err(WorldError::WrongStorageKind {
63 name: alloc::string::String::from("duplicate component in dynamic bundle"),
64 });
65 }
66 self.entries.push(DynamicEntry {
67 component_id,
68 value,
69 });
70 Ok(())
71 }
72}
73
74impl Default for DynamicBundle {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80impl Bundle for DynamicBundle {
81 fn write(self, writer: &mut BundleWriter<'_>) -> Result<(), WorldError> {
82 for entry in self.entries {
83 entry.component_id.validate_owner(writer.world_owner())?;
84 if writer.is_tag_component(&entry.component_id) {
85 if entry.value.is_some() {
86 return Err(WorldError::WrongStorageKind {
87 name: alloc::string::String::from("tag components cannot carry values"),
88 });
89 }
90 writer.insert_tag_id(entry.component_id)?;
91 } else if let Some(value) = entry.value {
92 writer.insert_dynamic(entry.component_id, value)?;
93 } else {
94 return Err(WorldError::WrongStorageKind {
95 name: alloc::string::String::from("table/sparse components require values"),
96 });
97 }
98 }
99 Ok(())
100 }
101}
102
103pub struct BundleWriter<'w> {
105 entity: EntityId,
106 target: BundleTarget<'w>,
107}
108
109enum BundleTarget<'w> {
110 Immediate(&'w mut World),
111 Deferred(&'w mut World),
112 Query {
113 allocator: &'w crate::entity::EntityAllocator,
114 queue: &'w mut crate::command::CommandQueue,
115 },
116}
117
118impl<'w> BundleWriter<'w> {
119 pub(crate) fn new(world: &'w mut World, entity: EntityId) -> Self {
120 Self {
121 entity,
122 target: BundleTarget::Immediate(world),
123 }
124 }
125
126 pub(crate) fn deferred(world: &'w mut World, entity: EntityId) -> Self {
127 Self {
128 entity,
129 target: BundleTarget::Deferred(world),
130 }
131 }
132
133 pub(crate) fn query(
134 allocator: &'w crate::entity::EntityAllocator,
135 queue: &'w mut crate::command::CommandQueue,
136 entity: EntityId,
137 ) -> Self {
138 Self {
139 entity,
140 target: BundleTarget::Query { allocator, queue },
141 }
142 }
143
144 pub fn insert<T: 'static>(&mut self, value: T) -> Result<(), WorldError> {
146 match &mut self.target {
147 BundleTarget::Immediate(world) => world.insert(self.entity, value).map(|_| ()),
148 BundleTarget::Deferred(world) => {
149 world.ensure_mutable()?;
150 world.ensure_command_target(self.entity)?;
151 world.command_queue_mut().enqueue_insert(self.entity, value)
152 }
153 BundleTarget::Query { allocator, queue } => {
154 ensure_query_target(allocator, self.entity)?;
155 queue.enqueue_insert(self.entity, value)
156 }
157 }
158 }
159
160 pub(crate) fn insert_dynamic(
161 &mut self,
162 component_id: ComponentId,
163 value: Box<dyn ErasedComponentValue>,
164 ) -> Result<(), WorldError> {
165 match &mut self.target {
166 BundleTarget::Immediate(world) => world
167 .insert_dynamic(self.entity, component_id, value)
168 .map(|_| ()),
169 BundleTarget::Deferred(world) => {
170 world.ensure_mutable()?;
171 world.ensure_command_target(self.entity)?;
172 world.validate_component_insert(
173 self.entity,
174 component_id.index() as u32,
175 value.as_ref().type_id(),
176 )?;
177 world.command_queue_mut().push(CommandOp::Insert {
178 entity: self.entity,
179 component_index: component_id.index() as u32,
180 value,
181 });
182 Ok(())
183 }
184 BundleTarget::Query { allocator, queue } => {
185 ensure_query_target(allocator, self.entity)?;
186 queue.enqueue_dynamic_insert(self.entity, component_id.index(), value)
187 }
188 }
189 }
190
191 pub(crate) fn insert_tag_id(&mut self, component_id: ComponentId) -> Result<(), WorldError> {
192 match &mut self.target {
193 BundleTarget::Immediate(world) => world.add_tag_id(self.entity, component_id),
194 BundleTarget::Deferred(world) => {
195 world.ensure_mutable()?;
196 world.ensure_command_target(self.entity)?;
197 world
198 .command_queue_mut()
199 .enqueue_tag(self.entity, component_id.index())
200 }
201 BundleTarget::Query { allocator, queue } => {
202 ensure_query_target(allocator, self.entity)?;
203 queue.enqueue_tag(self.entity, component_id.index())
204 }
205 }
206 }
207
208 pub(crate) fn world_owner(&self) -> &crate::world::WorldOwner {
209 match &self.target {
210 BundleTarget::Immediate(world) | BundleTarget::Deferred(world) => world.owner(),
211 BundleTarget::Query { queue, .. } => queue.owner(),
212 }
213 }
214
215 pub(crate) fn is_tag_component(&self, component_id: &ComponentId) -> bool {
216 match &self.target {
217 BundleTarget::Immediate(world) | BundleTarget::Deferred(world) => {
218 world.is_tag_component(component_id)
219 }
220 BundleTarget::Query { queue, .. } => queue.is_tag_component(component_id.index()),
221 }
222 }
223
224 #[cfg(test)]
225 pub(crate) fn test_entity(&self) -> EntityId {
226 self.entity
227 }
228
229 #[cfg(test)]
230 pub(crate) fn test_world(&mut self) -> &mut World {
231 match &mut self.target {
232 BundleTarget::Immediate(world) | BundleTarget::Deferred(world) => world,
233 BundleTarget::Query { .. } => panic!("query bundle writer has no world"),
234 }
235 }
236}
237
238fn ensure_query_target(
239 allocator: &crate::entity::EntityAllocator,
240 entity: EntityId,
241) -> Result<(), WorldError> {
242 if allocator.is_alive(entity) || allocator.is_reserved(entity) {
243 Ok(())
244 } else {
245 Err(WorldError::StaleEntity { entity })
246 }
247}
248
249macro_rules! impl_bundle_tuple {
250 ($($name:ident),+) => {
251 #[allow(non_snake_case)]
252 impl<$($name: 'static),+> Bundle for ($($name,)+) {
253 fn write(self, writer: &mut BundleWriter<'_>) -> Result<(), WorldError> {
254 let ($($name,)+) = self;
255 $(writer.insert($name)?;)+
256 Ok(())
257 }
258 }
259 };
260}
261
262impl_bundle_tuple!(A);
263impl_bundle_tuple!(A, B);
264impl_bundle_tuple!(A, B, C);
265impl_bundle_tuple!(A, B, C, D);
266impl_bundle_tuple!(A, B, C, D, E);
267impl_bundle_tuple!(A, B, C, D, E, F);
268impl_bundle_tuple!(A, B, C, D, E, F, G);
269impl_bundle_tuple!(A, B, C, D, E, F, G, H);
270impl_bundle_tuple!(A, B, C, D, E, F, G, H, I);
271impl_bundle_tuple!(A, B, C, D, E, F, G, H, I, J);
272impl_bundle_tuple!(A, B, C, D, E, F, G, H, I, J, K);
273impl_bundle_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
274impl_bundle_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M);
275impl_bundle_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
276impl_bundle_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
277impl_bundle_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282 use crate::component::ComponentOptions;
283 use crate::world::WorldBuilder;
284 use alloc::vec;
285
286 #[derive(Clone, Copy)]
287 struct Health(i32);
288
289 #[derive(Clone, Copy)]
290 struct Marker;
291
292 #[test]
293 fn dynamic_bundle_default_and_write_validation_errors() {
294 assert_eq!(DynamicBundle::default().entries.len(), 0);
295 let mut builder = WorldBuilder::new();
296 let tag = builder
297 .register_component::<Marker>(ComponentOptions::tag())
298 .expect("tag");
299 builder
300 .register_component::<Health>(ComponentOptions::sparse())
301 .expect("health");
302 let mut world = builder.build().expect("build");
303 let entity = world.spawn().expect("spawn");
304
305 let mut tag_with_value = DynamicBundle::new();
306 tag_with_value.push_tag(&tag).expect("tag");
307 tag_with_value.entries[0].value = Some(Box::new(Health(1)));
308 assert!(matches!(
309 tag_with_value.write(&mut BundleWriter::new(&mut world, entity)),
310 Err(WorldError::WrongStorageKind { .. })
311 ));
312
313 let health_id = world.component_id::<Health>().expect("health");
314 let mut missing_value = DynamicBundle::new();
315 missing_value.push_entry(health_id, None).expect("entry");
316 assert!(matches!(
317 missing_value.write(&mut BundleWriter::new(&mut world, entity)),
318 Err(WorldError::WrongStorageKind { .. })
319 ));
320 }
321
322 #[test]
323 fn deferred_bundle_writer_queues_inserts() {
324 let mut builder = WorldBuilder::new();
325 builder
326 .register_component::<Health>(ComponentOptions::sparse())
327 .expect("health");
328 let mut world = builder.build().expect("build");
329 let entity = world
330 .commands()
331 .expect("commands")
332 .spawn()
333 .expect("reserve");
334 BundleWriter::deferred(&mut world, entity)
335 .insert(Health(3))
336 .expect("queue");
337 assert!(world.has_pending_commands());
338 }
339
340 #[test]
341 fn deferred_dynamic_bundle_queues_validated_erased_value() {
342 let mut builder = WorldBuilder::new();
343 builder
344 .register_component::<Health>(ComponentOptions::sparse())
345 .expect("health");
346 let mut world = builder.build().expect("world");
347 let health = world.component_id::<Health>().expect("id");
348 let entity = world
349 .commands()
350 .expect("commands")
351 .spawn()
352 .expect("reserve");
353 let mut dynamic = DynamicBundle::new();
354 dynamic
355 .push_entry(health, Some(Box::new(Health(7))))
356 .expect("entry");
357 dynamic
358 .write(&mut BundleWriter::deferred(&mut world, entity))
359 .expect("deferred dynamic");
360
361 let mut wrong = DynamicBundle::new();
362 wrong
363 .push_entry(
364 world.component_id::<Health>().expect("health id"),
365 Some(Box::new(7_u32)),
366 )
367 .expect("wrong entry");
368 assert!(matches!(
369 wrong.write(&mut BundleWriter::deferred(&mut world, entity)),
370 Err(WorldError::WrongStorageKind { .. })
371 ));
372 }
373
374 #[test]
375 fn tuple_bundle_writes_components() {
376 let mut builder = WorldBuilder::new();
377 builder
378 .register_component::<Health>(ComponentOptions::sparse())
379 .expect("health");
380 let mut world = builder.build().expect("build");
381 let entity = world.spawn().expect("spawn");
382 (Health(4),)
383 .write(&mut BundleWriter::new(&mut world, entity))
384 .expect("tuple");
385 assert_eq!(
386 world.get::<Health>(entity).expect("get").map(|h| h.0),
387 Some(4)
388 );
389 }
390
391 #[test]
392 fn query_bundle_writer_validates_and_enqueues_all_component_shapes() {
393 let mut builder = WorldBuilder::new();
394 let health = builder
395 .register_component::<Health>(ComponentOptions::sparse())
396 .expect("health");
397 let marker = builder
398 .register_component::<Marker>(ComponentOptions::tag())
399 .expect("marker");
400 let mut world = builder.build().expect("world");
401 let entity = world.spawn().expect("entity");
402 let mut queue = crate::command::CommandQueue::configured(
403 world.owner.clone(),
404 vec![
405 (Some(core::any::TypeId::of::<Health>()), false),
406 (None, true),
407 ],
408 );
409
410 let mut writer = BundleWriter::query(&world.allocator, &mut queue, entity);
411 assert_eq!(writer.test_entity(), entity);
412 assert!(writer.world_owner().same(world.owner()));
413 assert!(!writer.is_tag_component(&health));
414 assert!(writer.is_tag_component(&marker));
415 writer.insert(Health(1)).expect("typed insert");
416 writer
417 .insert_dynamic(health, Box::new(Health(2)))
418 .expect("dynamic insert");
419 writer.insert_tag_id(marker).expect("tag insert");
420
421 let stale = EntityId::from_parts(99, 1);
422 let mut stale_writer = BundleWriter::query(&world.allocator, &mut queue, stale);
423 assert!(matches!(
424 stale_writer.insert(Health(3)),
425 Err(WorldError::StaleEntity { .. })
426 ));
427 }
428
429 #[test]
430 #[should_panic(expected = "query bundle writer has no world")]
431 fn query_bundle_test_world_rejects_world_access() {
432 let mut world = WorldBuilder::new().build().expect("world");
433 let entity = world.spawn().expect("entity");
434 let mut queue = crate::command::CommandQueue::configured(world.owner.clone(), vec![]);
435 BundleWriter::query(&world.allocator, &mut queue, entity).test_world();
436 }
437}