1use core::{
2 any::{type_name, Any, TypeId},
3 marker::PhantomData,
4};
5
6use bevy_app::{Plugin, PreUpdate};
7use bevy_diagnostic::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic};
8use bevy_ecs::{resource::Resource, system::Res};
9use bevy_platform::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
10use bevy_render::{Extract, ExtractSchedule, RenderApp};
11
12use crate::{Material, MaterialBindGroupAllocators};
13
14pub struct MaterialAllocatorDiagnosticPlugin<M: Material> {
15 suffix: &'static str,
16 _phantom: PhantomData<M>,
17}
18
19impl<M: Material> MaterialAllocatorDiagnosticPlugin<M> {
20 pub fn new(suffix: &'static str) -> Self {
21 Self {
22 suffix,
23 _phantom: PhantomData,
24 }
25 }
26}
27
28impl<M: Material> Default for MaterialAllocatorDiagnosticPlugin<M> {
29 fn default() -> Self {
30 Self {
31 suffix: " materials",
32 _phantom: PhantomData,
33 }
34 }
35}
36
37impl<M: Material> MaterialAllocatorDiagnosticPlugin<M> {
38 pub fn slabs_diagnostic_path() -> DiagnosticPath {
40 DiagnosticPath::from_components(["material_allocator_slabs", type_name::<M>()])
41 }
42 pub fn slabs_size_diagnostic_path() -> DiagnosticPath {
44 DiagnosticPath::from_components(["material_allocator_slabs_size", type_name::<M>()])
45 }
46 pub fn allocations_diagnostic_path() -> DiagnosticPath {
48 DiagnosticPath::from_components(["material_allocator_allocations", type_name::<M>()])
49 }
50}
51
52impl<M: Material> Plugin for MaterialAllocatorDiagnosticPlugin<M> {
53 fn build(&self, app: &mut bevy_app::App) {
54 app.register_diagnostic(
55 Diagnostic::new(Self::slabs_diagnostic_path()).with_suffix(" slabs"),
56 )
57 .register_diagnostic(
58 Diagnostic::new(Self::slabs_size_diagnostic_path()).with_suffix(" bytes"),
59 )
60 .register_diagnostic(
61 Diagnostic::new(Self::allocations_diagnostic_path()).with_suffix(self.suffix),
62 )
63 .init_resource::<MaterialAllocatorMeasurements<M>>()
64 .add_systems(PreUpdate, add_material_allocator_measurement::<M>);
65
66 if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
67 render_app.add_systems(ExtractSchedule, measure_allocator::<M>);
68 }
69 }
70}
71
72#[derive(Debug, Resource)]
73struct MaterialAllocatorMeasurements<M: Material> {
74 slabs: AtomicUsize,
75 slabs_size: AtomicUsize,
76 allocations: AtomicU64,
77 _phantom: PhantomData<M>,
78}
79
80impl<M: Material> Default for MaterialAllocatorMeasurements<M> {
81 fn default() -> Self {
82 Self {
83 slabs: AtomicUsize::default(),
84 slabs_size: AtomicUsize::default(),
85 allocations: AtomicU64::default(),
86 _phantom: PhantomData,
87 }
88 }
89}
90
91fn add_material_allocator_measurement<M: Material>(
92 mut diagnostics: Diagnostics,
93 measurements: Res<MaterialAllocatorMeasurements<M>>,
94) {
95 diagnostics.add_measurement(
96 &MaterialAllocatorDiagnosticPlugin::<M>::slabs_diagnostic_path(),
97 || measurements.slabs.load(Ordering::Relaxed) as f64,
98 );
99 diagnostics.add_measurement(
100 &MaterialAllocatorDiagnosticPlugin::<M>::slabs_size_diagnostic_path(),
101 || measurements.slabs_size.load(Ordering::Relaxed) as f64,
102 );
103 diagnostics.add_measurement(
104 &MaterialAllocatorDiagnosticPlugin::<M>::allocations_diagnostic_path(),
105 || measurements.allocations.load(Ordering::Relaxed) as f64,
106 );
107}
108
109fn measure_allocator<M: Material + Any>(
110 measurements: Extract<Res<MaterialAllocatorMeasurements<M>>>,
111 allocators: Res<MaterialBindGroupAllocators>,
112) {
113 if let Some(allocator) = allocators.get(&TypeId::of::<M>()) {
114 measurements
115 .slabs
116 .store(allocator.slab_count(), Ordering::Relaxed);
117 measurements
118 .slabs_size
119 .store(allocator.slabs_size(), Ordering::Relaxed);
120 measurements
121 .allocations
122 .store(allocator.allocations(), Ordering::Relaxed);
123 }
124}