1use crate::storage::{ShaderBuffer, ShaderBufferData};
4
5use alloc::borrow::Cow;
6use bevy_app::{App, Plugin, PostUpdate};
7use bevy_asset::{Assets, Handle, RenderAssetUsages};
8use bevy_ecs::{
9 component::Component,
10 entity::{EntityHashMap, EntityHashSet},
11 lifecycle::RemovedComponents,
12 prelude::Entity,
13 query::{QueryFilter, QueryItem, ReadOnlyQueryData},
14 resource::Resource,
15 system::{Commands, If, Local, Query, ResMut},
16};
17use bevy_log::warn;
18use bevy_mesh::MeshTag;
19use bevy_platform::collections::AlignedVec;
20use bytemuck::{AnyBitPattern, NoUninit};
21use core::marker::PhantomData;
22use wgpu::BufferUsages;
23
24const BUFFER_ALLOCATION_GROWTH_FACTOR: f64 = 1.5;
29
30pub struct GpuComponentArrayBufferPlugin<C>(PhantomData<C>)
31where
32 C: GpuComponentArrayBuffer;
33
34pub trait GpuComponentArrayBuffer: Component + Send + Sync + 'static {
37 type QueryData: ReadOnlyQueryData;
39 type QueryFilter: QueryFilter;
41 type Out: NoUninit + AnyBitPattern;
43
44 fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out>;
49
50 fn label() -> Cow<'static, str> {
53 Cow::Borrowed("GPU component array")
54 }
55
56 fn buffer_usage() -> BufferUsages {
59 BufferUsages::STORAGE | BufferUsages::COPY_DST | BufferUsages::COPY_SRC
60 }
61}
62
63#[derive(Resource)]
67pub struct GpuComponentArray<C>
68where
69 C: GpuComponentArrayBuffer,
70{
71 pub buffer: Handle<ShaderBuffer>,
72 entity_to_tag: EntityHashMap<u32>,
73 tag_to_entity: Vec<Entity>,
74 phantom: PhantomData<C>,
75}
76
77impl<C> Plugin for GpuComponentArrayBufferPlugin<C>
78where
79 C: GpuComponentArrayBuffer,
80{
81 fn build(&self, app: &mut App) {
82 let mut shader_buffer_assets = app.world_mut().resource_mut::<Assets<ShaderBuffer>>();
85 let gpu_component_array = GpuComponentArray::<C>::new(&mut shader_buffer_assets);
86
87 app.insert_resource(gpu_component_array)
88 .add_systems(PostUpdate, update_components::<C>);
89 }
90}
91
92impl<C> Default for GpuComponentArrayBufferPlugin<C>
93where
94 C: GpuComponentArrayBuffer,
95{
96 fn default() -> Self {
97 Self(PhantomData::<C>)
98 }
99}
100
101impl<C> GpuComponentArray<C>
102where
103 C: GpuComponentArrayBuffer,
104{
105 pub fn new(shader_buffer_assets: &mut Assets<ShaderBuffer>) -> Self {
108 let data = AlignedVec::from(bytemuck::zeroed_vec::<C::Out>(1));
109 let buffer_size = data.len() as u64;
110 let buffer = shader_buffer_assets.add(ShaderBuffer {
111 data: ShaderBufferData::Initialized { data, buffer_size },
112 label: C::label(),
113 buffer_usage: C::buffer_usage(),
114 asset_usage: RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
115 copy_on_resize: true,
116 });
117
118 GpuComponentArray {
119 buffer,
120 entity_to_tag: EntityHashMap::default(),
121 tag_to_entity: vec![],
122 phantom: PhantomData,
123 }
124 }
125}
126
127fn update_components<C>(
131 mut commands: Commands,
132 query: Query<(Entity, Option<&MeshTag>, C::QueryData), C::QueryFilter>,
133 mut component_array: If<ResMut<GpuComponentArray<C>>>,
134 mut shader_buffers: ResMut<Assets<ShaderBuffer>>,
135 mut removed_components: RemovedComponents<C>,
136 mut processed_entities: Local<EntityHashSet>,
137) where
138 C: GpuComponentArrayBuffer,
139{
140 let Some(mut buffer) = shader_buffers.get_mut(&mut component_array.buffer) else {
141 return;
142 };
143
144 processed_entities.clear();
145
146 for (entity, maybe_tag, item) in &query {
147 match C::extract_component(item) {
148 None => {
149 if let Some((displaced_entity, displaced_entity_new_tag)) =
153 component_array.remove(&mut buffer, entity)
154 {
155 commands
156 .entity(displaced_entity)
157 .insert(MeshTag::with_type::<GpuComponentArray<C>>(
158 displaced_entity_new_tag,
159 ));
160 }
161
162 commands.entity(entity).remove::<MeshTag>();
163 }
164
165 Some(data) => {
166 if let Some(tag) = maybe_tag
167 && tag.type_is::<GpuComponentArray<C>>()
168 {
169 component_array.set(&mut buffer, tag.value, data);
170 } else {
171 if let Some(tag) = maybe_tag
177 && !tag.type_is::<GpuComponentArray<C>>()
178 {
179 warn!(
180 "The entity {:?} has an existing `MeshTag`. \
181 `GpuComponentArrayBuffer` has overwritten it.",
182 entity
183 );
184 }
185
186 let tag = component_array.len();
191 component_array.push(&mut buffer, entity, data);
192 commands
193 .entity(entity)
194 .insert(MeshTag::with_type::<GpuComponentArray<C>>(tag as u32));
195 }
196 }
197 }
198
199 processed_entities.insert(entity);
200 }
201
202 for entity in removed_components
207 .read()
208 .filter(|entity| !processed_entities.contains(entity))
209 {
210 if let Some((displaced_entity, displaced_entity_new_tag)) =
211 component_array.remove(&mut buffer, entity)
212 {
213 commands
214 .entity(displaced_entity)
215 .insert(MeshTag::with_type::<GpuComponentArray<C>>(
216 displaced_entity_new_tag,
217 ));
218 }
219 }
220}
221
222impl<C> GpuComponentArray<C>
223where
224 C: GpuComponentArrayBuffer,
225{
226 fn len(&self) -> usize {
228 self.tag_to_entity.len()
229 }
230
231 fn is_empty(&self) -> bool {
234 self.tag_to_entity.is_empty()
235 }
236
237 fn push(&mut self, buffer: &mut ShaderBuffer, entity: Entity, data: C::Out) {
242 let ShaderBufferData::Initialized {
243 data: ref mut data_buffer,
244 ref mut buffer_size,
245 } = buffer.data
246 else {
247 panic!(
248 "Shader buffers created for use in a `GpuComponentArrayBuffer` must have been \
249 created with `ShaderBufferData::Initialized`"
250 );
251 };
252 if self.is_empty() {
253 data_buffer.clear();
254 }
255
256 let tag = self.tag_to_entity.len() as u32;
257
258 let needed_buffer_len = tag as usize + 1;
259 let current_buffer_len = data_buffer.len() / size_of::<C::Out>();
260 if needed_buffer_len > current_buffer_len {
261 let next_buffer_len = round_buffer_size_up(needed_buffer_len);
262 data_buffer.resize(next_buffer_len * size_of::<C::Out>(), 0);
263 *buffer_size = data_buffer.len() as u64;
264 }
265 data_buffer.cast_slice_mut()[tag as usize] = data;
266
267 let prev_tag = self.entity_to_tag.insert(entity, tag);
268 self.tag_to_entity.push(entity);
269
270 debug_assert!(prev_tag.is_none());
271 }
272
273 fn get<'a>(&'_ self, buffer: &'a ShaderBuffer, tag: u32) -> &'a C::Out {
274 let Some(data_buffer) = buffer.cast_slice() else {
275 panic!(
276 "Shader buffers created for use in a `GpuComponentArrayBuffer` must have been \
277 created with `ShaderBufferData::Initialized`"
278 );
279 };
280 &data_buffer[tag as usize]
281 }
282
283 fn set(&mut self, buffer: &mut ShaderBuffer, tag: u32, data: C::Out) {
284 let Some(data_buffer) = buffer.cast_slice_mut() else {
285 panic!(
286 "Shader buffers created for use in a `GpuComponentArrayBuffer` must have been \
287 created with `ShaderBufferData::Initialized`"
288 );
289 };
290 data_buffer[tag as usize] = data;
291 }
292
293 fn remove(
301 &mut self,
302 buffer: &mut ShaderBuffer,
303 entity_to_remove: Entity,
304 ) -> Option<(Entity, u32)> {
305 debug_assert!(!self.tag_to_entity.is_empty());
306
307 let displaced_tag = self.tag_to_entity.len() as u32 - 1;
308 let displaced_data = *self.get(buffer, displaced_tag);
309 let displaced_entity = *self.tag_to_entity.last().unwrap();
310
311 let tag_to_remove = self
312 .entity_to_tag
313 .remove(&entity_to_remove)
314 .expect("Entity must have been present");
315 let removed_entity = self.tag_to_entity.swap_remove(tag_to_remove as usize);
316 debug_assert_eq!(entity_to_remove, removed_entity);
317
318 if tag_to_remove == displaced_tag {
319 return None;
320 }
321
322 *self.entity_to_tag.get_mut(&displaced_entity).unwrap() = tag_to_remove;
323 self.set(buffer, tag_to_remove, displaced_data);
324 Some((displaced_entity, tag_to_remove))
325 }
326}
327
328fn round_buffer_size_up(original_size: usize) -> usize {
334 if original_size <= 1 {
335 return 1;
336 }
337
338 let exponent = (original_size as f64).ln() / BUFFER_ALLOCATION_GROWTH_FACTOR.ln();
340 BUFFER_ALLOCATION_GROWTH_FACTOR.powi(exponent.ceil() as i32) as usize
341}
342#[cfg(test)]
343mod tests {
344 use std::marker::PhantomData;
345
346 use bevy_asset::{Handle, RenderAssetUsages};
347 use bevy_ecs::{
348 component::Component,
349 entity::{Entity, EntityHashMap, EntityIndex},
350 query::QueryItem,
351 };
352 use bytemuck::{Pod, Zeroable};
353 use encase::ShaderType;
354 use nonmax::NonMaxU32;
355
356 use crate::storage::ShaderBuffer;
357
358 use super::{GpuComponentArray, GpuComponentArrayBuffer};
359
360 #[derive(Component)]
362 struct MockComponent;
363
364 #[derive(Clone, Copy, Default, PartialEq, Pod, Zeroable, ShaderType, Debug)]
366 #[repr(C)]
367 struct MockComponentData {
368 a: u32,
369 b: u32,
370 }
371
372 impl MockComponentData {
373 fn new(a: u32, b: u32) -> Self {
374 Self { a, b }
375 }
376 }
377
378 impl GpuComponentArrayBuffer for MockComponent {
379 type QueryData = ();
380 type QueryFilter = ();
381 type Out = MockComponentData;
382
383 fn extract_component(_: QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out> {
384 None
385 }
386 }
387
388 struct TestData {
390 gpu_component_array: GpuComponentArray<MockComponent>,
392 buffer: ShaderBuffer,
394
395 entity_a: Entity,
396 entity_b: Entity,
397 entity_c: Entity,
398
399 entity_data_a: MockComponentData,
400 entity_data_b: MockComponentData,
401 entity_data_c: MockComponentData,
402 }
403
404 impl TestData {
405 fn new() -> TestData {
406 TestData {
407 gpu_component_array: GpuComponentArray::<MockComponent> {
408 buffer: Handle::default(),
409 entity_to_tag: EntityHashMap::default(),
410 tag_to_entity: vec![],
411 phantom: PhantomData,
412 },
413 buffer: ShaderBuffer::new::<MockComponentData>(vec![], RenderAssetUsages::all()),
414
415 entity_a: Entity::from_index(EntityIndex::new(NonMaxU32::new(1).unwrap())),
417 entity_b: Entity::from_index(EntityIndex::new(NonMaxU32::new(2).unwrap())),
418 entity_c: Entity::from_index(EntityIndex::new(NonMaxU32::new(3).unwrap())),
419
420 entity_data_a: MockComponentData::new(11, 111),
422 entity_data_b: MockComponentData::new(22, 222),
423 entity_data_c: MockComponentData::new(33, 333),
424 }
425 }
426
427 fn check(&self, expected_data: &[(Entity, MockComponentData)]) {
430 assert_eq!(
431 self.gpu_component_array.entity_to_tag.len(),
432 expected_data.len()
433 );
434 assert_eq!(
435 self.gpu_component_array.tag_to_entity.len(),
436 expected_data.len()
437 );
438 assert!(
439 self.buffer.buffer_size()
440 >= (expected_data.len() * size_of::<MockComponentData>()) as u64
441 );
442
443 for (tag, (entity, data)) in expected_data.iter().enumerate() {
444 assert_eq!(
445 self.gpu_component_array.entity_to_tag.get(entity),
446 Some(&(tag as u32))
447 );
448 assert_eq!(
449 self.gpu_component_array.tag_to_entity.get(tag),
450 Some(entity)
451 );
452 assert_eq!(self.gpu_component_array.get(&self.buffer, tag as u32), data);
453 }
454 }
455 }
456
457 #[test]
459 fn empty_gpu_component_array() {
460 let test_data = TestData::new();
461 test_data.check(&[]);
462 }
463
464 #[test]
467 fn push_onto_gpu_component_array() {
468 let mut test_data = TestData::new();
469 test_data.gpu_component_array.push(
470 &mut test_data.buffer,
471 test_data.entity_a,
472 test_data.entity_data_a,
473 );
474 test_data.check(&[(test_data.entity_a, test_data.entity_data_a)]);
475 test_data.gpu_component_array.push(
476 &mut test_data.buffer,
477 test_data.entity_b,
478 test_data.entity_data_b,
479 );
480 test_data.check(&[
481 (test_data.entity_a, test_data.entity_data_a),
482 (test_data.entity_b, test_data.entity_data_b),
483 ]);
484 test_data.gpu_component_array.push(
485 &mut test_data.buffer,
486 test_data.entity_c,
487 test_data.entity_data_c,
488 );
489 test_data.check(&[
490 (test_data.entity_a, test_data.entity_data_a),
491 (test_data.entity_b, test_data.entity_data_b),
492 (test_data.entity_c, test_data.entity_data_c),
493 ]);
494 }
495
496 #[test]
499 fn set_element_in_gpu_component_array() {
500 let mut test_data = TestData::new();
501 test_data.gpu_component_array.push(
502 &mut test_data.buffer,
503 test_data.entity_a,
504 test_data.entity_data_a,
505 );
506 test_data.gpu_component_array.push(
507 &mut test_data.buffer,
508 test_data.entity_b,
509 test_data.entity_data_b,
510 );
511 test_data.gpu_component_array.push(
512 &mut test_data.buffer,
513 test_data.entity_c,
514 test_data.entity_data_c,
515 );
516
517 let entity_data_b_alt = MockComponentData::new(2222, 22222);
518
519 test_data
520 .gpu_component_array
521 .set(&mut test_data.buffer, 1, entity_data_b_alt);
522 test_data.check(&[
523 (test_data.entity_a, test_data.entity_data_a),
524 (test_data.entity_b, entity_data_b_alt),
525 (test_data.entity_c, test_data.entity_data_c),
526 ]);
527 }
528
529 #[test]
532 fn remove_element_from_end_of_gpu_component_array() {
533 let mut test_data = TestData::new();
534 test_data.gpu_component_array.push(
535 &mut test_data.buffer,
536 test_data.entity_a,
537 test_data.entity_data_a,
538 );
539 test_data.gpu_component_array.push(
540 &mut test_data.buffer,
541 test_data.entity_b,
542 test_data.entity_data_b,
543 );
544 test_data.gpu_component_array.push(
545 &mut test_data.buffer,
546 test_data.entity_c,
547 test_data.entity_data_c,
548 );
549
550 let maybe_displaced_element = test_data
551 .gpu_component_array
552 .remove(&mut test_data.buffer, test_data.entity_c);
553 assert_eq!(maybe_displaced_element, None);
554
555 test_data.check(&[
556 (test_data.entity_a, test_data.entity_data_a),
557 (test_data.entity_b, test_data.entity_data_b),
558 ]);
559 }
560
561 #[test]
564 fn remove_element_from_start_of_gpu_component_array() {
565 let mut test_data = TestData::new();
566 test_data.gpu_component_array.push(
567 &mut test_data.buffer,
568 test_data.entity_a,
569 test_data.entity_data_a,
570 );
571 test_data.gpu_component_array.push(
572 &mut test_data.buffer,
573 test_data.entity_b,
574 test_data.entity_data_b,
575 );
576 test_data.gpu_component_array.push(
577 &mut test_data.buffer,
578 test_data.entity_c,
579 test_data.entity_data_c,
580 );
581
582 let maybe_displaced_element = test_data
583 .gpu_component_array
584 .remove(&mut test_data.buffer, test_data.entity_a);
585 assert_eq!(maybe_displaced_element, Some((test_data.entity_c, 0)));
586
587 test_data.check(&[
588 (test_data.entity_c, test_data.entity_data_c),
589 (test_data.entity_b, test_data.entity_data_b),
590 ]);
591 }
592}