hephae_ui/
measure.rs

1//! Defines UI leaf node measurers.
2
3use std::{
4    any::type_name,
5    mem::MaybeUninit,
6    ops::Index,
7    panic::{AssertUnwindSafe, catch_unwind, resume_unwind},
8};
9
10use bevy_ecs::{
11    component::{ComponentId, ComponentIdFor},
12    prelude::*,
13    query::{QueryItem, ReadOnlyQueryData},
14    storage::SparseSet,
15    system::{
16        ReadOnlySystemParam, SystemParamItem, SystemState,
17        lifetimeless::{Read, SQuery},
18    },
19    world::unsafe_world_cell::UnsafeWorldCell,
20};
21use bevy_math::prelude::*;
22
23/// Content-size measurer component.
24///
25/// # Note
26///
27/// When something might cause this measurer to output a different size, e.g. when a text span is
28/// modified, users should take care to call
29/// [`UiCaches::invalidate`](crate::node::UiCaches::invalidate) on its entity so the UI tree will be
30/// recomputed.
31pub trait Measure: Component {
32    /// The parameter required for measuring.
33    type Param: ReadOnlySystemParam;
34    /// Necessary neighbor components required for measuring. Failing to fetch these will make the
35    /// measure output as zero on both axes.
36    type Item: ReadOnlyQueryData;
37
38    /// Measures the UI leaf node.
39    fn measure(
40        &self,
41        param: &SystemParamItem<Self::Param>,
42        item: QueryItem<Self::Item>,
43        known_size: (Option<f32>, Option<f32>),
44        available_space: (AvailableSpace, AvailableSpace),
45    ) -> Vec2;
46}
47
48pub(crate) fn on_measure_inserted<T: Measure>(
49    trigger: Trigger<OnInsert, T>,
50    mut commands: Commands,
51    measurements: Res<Measurements>,
52    id: ComponentIdFor<T>,
53) {
54    let e = trigger.entity();
55    commands.entity(e).insert(ContentSize(
56        measurements
57            .get(id.get())
58            .unwrap_or_else(|| panic!("`{}` not registered", type_name::<T>())),
59    ));
60}
61
62/// Type-erased container of [`Measure`]s. Note that this component is automatically registered as
63/// required by [`Measure`] when configured.
64#[derive(Component, Copy, Clone)]
65pub struct ContentSize(MeasureId);
66impl ContentSize {
67    /// Gets the measure ID for use with [`Measurements`].
68    #[inline]
69    pub const fn get(self) -> MeasureId {
70        self.0
71    }
72}
73
74impl Default for ContentSize {
75    #[inline]
76    fn default() -> Self {
77        Self(MeasureId::INVALID)
78    }
79}
80
81/// Opaque ID of a [`Measure`] in an entity.
82#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
83pub struct MeasureId(usize);
84impl MeasureId {
85    /// Invalid measure ID. Content size will be zero.
86    pub const INVALID: Self = Self(usize::MAX);
87}
88
89/// The amount of space available to a node in a given axis.
90#[derive(Copy, Clone, PartialEq, PartialOrd)]
91pub enum AvailableSpace {
92    /// The amount of space available is the specified number of pixels.
93    Definite(f32),
94    /// The amount of space available is indefinite and the node should be laid out under a
95    /// min-content constraint.
96    MinContent,
97    /// The amount of space available is indefinite and the node should be laid out under a
98    /// max-content constraint.
99    MaxContent,
100}
101
102impl From<taffy::AvailableSpace> for AvailableSpace {
103    #[inline]
104    fn from(value: taffy::AvailableSpace) -> Self {
105        match value {
106            taffy::AvailableSpace::Definite(value) => Self::Definite(value),
107            taffy::AvailableSpace::MinContent => Self::MinContent,
108            taffy::AvailableSpace::MaxContent => Self::MaxContent,
109        }
110    }
111}
112
113/// Stores data required for each [`Measure`]s globally.
114#[derive(Resource, Default)]
115pub struct Measurements {
116    ids: SparseSet<ComponentId, MeasureId>,
117    data: Vec<Box<dyn MeasureDyn>>,
118}
119
120impl Measurements {
121    /// Registers a [`Measure`], making it require a [`ContentSize`].
122    #[inline]
123    pub fn register<T: Measure>(&mut self, world: &mut World) -> MeasureId {
124        *self.ids.get_or_insert_with(world.register_component::<T>(), || {
125            self.data.push(Box::new(MeasureImpl::<T> {
126                state: SystemState::new(world),
127                fetch: MaybeUninit::uninit(),
128            }));
129
130            MeasureId(self.data.len() - 1)
131        })
132    }
133
134    /// Gets an ID for a [`Measure`] component.
135    #[inline]
136    pub fn get(&self, id: ComponentId) -> Option<MeasureId> {
137        self.ids.get(id).copied()
138    }
139
140    /// Gets all the [`Measure`] data, fetching their system params to the given `world`.
141    ///
142    /// # Safety
143    ///
144    /// - `world` must be able to access any component and resources immutably, except for
145    ///   components that are private and inaccessible to implementors of [`Measure`].
146    /// - The drop glue of the returned container must run before trying to access resources and
147    ///   components mutably, or making structural ECS changes.
148    #[inline]
149    pub unsafe fn get_measurers(&mut self, world: UnsafeWorldCell) -> impl Index<MeasureId, Output = dyn Measurer> {
150        struct Guard<'a> {
151            measurers: &'a mut [Box<dyn MeasureDyn>],
152        }
153
154        impl Index<MeasureId> for Guard<'_> {
155            type Output = dyn Measurer;
156
157            #[inline]
158            fn index(&self, index: MeasureId) -> &Self::Output {
159                // Safety: `init_fetch` has been called.
160                unsafe { self.measurers[index.0].as_measurer() }
161            }
162        }
163
164        impl Drop for Guard<'_> {
165            fn drop(&mut self) {
166                for measurer in &mut *self.measurers {
167                    unsafe { measurer.finish_fetch() }
168                }
169            }
170        }
171
172        for (i, data) in self.data.iter_mut().enumerate() {
173            if let Err(e) = catch_unwind(AssertUnwindSafe(|| unsafe { data.init_fetch(world) })) {
174                for i in 0..i {
175                    unsafe { self.data[i].finish_fetch() }
176                }
177
178                resume_unwind(e)
179            }
180        }
181
182        Guard {
183            measurers: &mut self.data,
184        }
185    }
186
187    /// Calls [`SystemState::apply`] for each [`Measure`] data.
188    pub fn apply_measurers(&mut self, world: &mut World) {
189        for data in &mut self.data {
190            data.apply(world)
191        }
192    }
193}
194
195/// Type returned by [`Measurements::get_measurers`].
196pub trait Measurer: 'static + Send + Sync {
197    /// Type-erased version of [`Measure::measure`].
198    fn measure(
199        &self,
200        known_size: (Option<f32>, Option<f32>),
201        available_space: (AvailableSpace, AvailableSpace),
202        entity: Entity,
203    ) -> Vec2;
204}
205
206/// # Safety
207///
208/// - `init_fetch` must fetch data that lives as long as `'w`.
209/// - `finish_fetch` must drop all data fetched by `init_fetch`.
210unsafe trait MeasureDyn: Measurer {
211    unsafe fn init_fetch<'w>(&'w mut self, world: UnsafeWorldCell<'w>);
212
213    unsafe fn finish_fetch(&mut self);
214
215    unsafe fn as_measurer(&self) -> &dyn Measurer;
216
217    fn apply(&mut self, world: &mut World);
218}
219
220impl<T: Measure> Measurer for MeasureImpl<T> {
221    #[inline]
222    fn measure<'w>(
223        &'w self,
224        known_size: (Option<f32>, Option<f32>),
225        available_space: (AvailableSpace, AvailableSpace),
226        entity: Entity,
227    ) -> Vec2 {
228        let (param, queue) = unsafe {
229            std::mem::transmute::<
230                &'w SystemParamItem<'static, 'static, (T::Param, SQuery<(Read<T>, T::Item)>)>,
231                &'w SystemParamItem<'w, 'w, (T::Param, SQuery<(Read<T>, T::Item)>)>,
232            >(self.fetch.assume_init_ref())
233        };
234
235        let Ok((measure, item)) = queue.get(entity) else {
236            return Vec2::ZERO;
237        };
238
239        measure.measure(param, item, known_size, available_space)
240    }
241}
242
243unsafe impl<T: Measure> MeasureDyn for MeasureImpl<T> {
244    #[inline]
245    unsafe fn init_fetch<'w>(&'w mut self, world: UnsafeWorldCell<'w>) {
246        self.state.update_archetypes_unsafe_world_cell(world);
247        unsafe {
248            self.fetch.as_mut_ptr().write(std::mem::transmute::<
249                SystemParamItem<'w, 'w, (T::Param, SQuery<(Read<T>, T::Item)>)>,
250                SystemParamItem<'static, 'static, (T::Param, SQuery<(Read<T>, T::Item)>)>,
251            >(self.state.get_unchecked_manual(world)))
252        }
253    }
254
255    #[inline]
256    unsafe fn finish_fetch(&mut self) {
257        unsafe { self.fetch.assume_init_drop() }
258    }
259
260    #[inline]
261    unsafe fn as_measurer(&self) -> &dyn Measurer {
262        self as &dyn Measurer
263    }
264
265    #[inline]
266    fn apply(&mut self, world: &mut World) {
267        self.state.apply(world)
268    }
269}
270
271struct MeasureImpl<T: Measure> {
272    state: SystemState<(T::Param, SQuery<(Read<T>, T::Item)>)>,
273    fetch: MaybeUninit<SystemParamItem<'static, 'static, (T::Param, SQuery<(Read<T>, T::Item)>)>>,
274}
275
276unsafe impl<T: Measure> Send for MeasureImpl<T> {}
277unsafe impl<T: Measure> Sync for MeasureImpl<T> {}