Skip to content

Commit f581846

Browse files
authored
Merge branch 'main' into 22485_observer_gizmos
2 parents c4407d6 + d836e14 commit f581846

File tree

16 files changed

+2171
-23
lines changed

16 files changed

+2171
-23
lines changed

Cargo.toml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4557,6 +4557,54 @@ description = "A scene showcasing light gizmos"
45574557
category = "Gizmos"
45584558
wasm = true
45594559

4560+
[[example]]
4561+
name = "2d_text_gizmos"
4562+
path = "examples/gizmos/2d_text_gizmos.rs"
4563+
# Causes an ICE on docs.rs
4564+
doc-scrape-examples = false
4565+
4566+
[package.metadata.example.2d_text_gizmos]
4567+
name = "Text Gizmos 2d"
4568+
description = "A scene showcasing 2d text gizmos"
4569+
category = "Gizmos"
4570+
wasm = true
4571+
4572+
[[example]]
4573+
name = "anchored_text_gizmos"
4574+
path = "examples/gizmos/anchored_text_gizmos.rs"
4575+
# Causes an ICE on docs.rs
4576+
doc-scrape-examples = false
4577+
4578+
[package.metadata.example.anchored_text_gizmos]
4579+
name = "Anchored Text Gizmos"
4580+
description = "Demonstrates anchored text gizmos"
4581+
category = "Gizmos"
4582+
wasm = true
4583+
4584+
[[example]]
4585+
name = "3d_text_gizmos"
4586+
path = "examples/gizmos/3d_text_gizmos.rs"
4587+
# Causes an ICE on docs.rs
4588+
doc-scrape-examples = false
4589+
4590+
[package.metadata.example.3d_text_gizmos]
4591+
name = "Text Gizmos 3d"
4592+
description = "A scene showcasing 3d text gizmos"
4593+
category = "Gizmos"
4594+
wasm = true
4595+
4596+
[[example]]
4597+
name = "text_gizmos_font"
4598+
path = "examples/gizmos/text_gizmos_font.rs"
4599+
# Causes an ICE on docs.rs
4600+
doc-scrape-examples = false
4601+
4602+
[package.metadata.example.text_gizmos_font]
4603+
name = "Text Gizmos Font"
4604+
description = "Example displaying the font used by text gizmos"
4605+
category = "Gizmos"
4606+
wasm = true
4607+
45604608
[[example]]
45614609
name = "custom_gltf_vertex_attribute"
45624610
path = "examples/gltf/custom_gltf_vertex_attribute.rs"

crates/bevy_dev_tools/src/easy_screenshot.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,21 @@ pub enum RecordScreen {
153153
Stop,
154154
}
155155

156+
#[cfg(feature = "screenrecording")]
157+
/// The [`Update`] systems that the [`EasyScreenRecordPlugin`] runs
158+
/// to start and stop recording on user command and
159+
/// to send frames to the thread that manages video file creation.
160+
/// These systems manipulate [`virtual`](bevy_time::Virtual)
161+
/// [`time`](bevy_time::Time) in order to capture frames for video.
162+
///
163+
/// If any application [`Update`] systems have behavior that depend
164+
/// on virtual time and must be recorded, ensure that these systems run
165+
/// [`after(EasyScreenRecordSystems)`](bevy_ecs::schedule::IntoScheduleConfigs::after).
166+
/// The application may run slower on screen during recording,
167+
/// but the video playback will be at normal speed.
168+
#[derive(SystemSet, Debug, Clone, Copy, PartialEq, Eq, Hash)]
169+
pub struct EasyScreenRecordSystems;
170+
156171
#[cfg(feature = "screenrecording")]
157172
impl Plugin for EasyScreenRecordPlugin {
158173
#[cfg_attr(
@@ -199,7 +214,6 @@ impl Plugin for EasyScreenRecordPlugin {
199214
setup = Some(Setup::preset(preset, tune, false, true).high());
200215
}
201216
RecordCommand::Stop => {
202-
info!("stopping recording");
203217
if let Some(encoder) = encoder.take() {
204218
let mut flush = encoder.flush();
205219
let mut file = file.take().unwrap();
@@ -208,6 +222,7 @@ impl Plugin for EasyScreenRecordPlugin {
208222
file.write_all(data.entirety()).unwrap();
209223
}
210224
}
225+
info!("finished processing video");
211226
}
212227
RecordCommand::Frame(image) => {
213228
if let Some(setup) = setup.take() {
@@ -291,6 +306,7 @@ impl Plugin for EasyScreenRecordPlugin {
291306
tx.send(RecordCommand::Stop).unwrap();
292307
*recording = false;
293308
virtual_time.unpause();
309+
info!("stopped recording. still processing video");
294310
}
295311
_ => {}
296312
}
@@ -310,7 +326,8 @@ impl Plugin for EasyScreenRecordPlugin {
310326
}
311327
},
312328
)
313-
.chain(),
329+
.chain()
330+
.in_set(EasyScreenRecordSystems),
314331
);
315332
}
316333
}

crates/bevy_ecs/src/system/query.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2730,6 +2730,8 @@ impl<'w, 'q, Q: QueryData, F: QueryFilter> From<&'q mut Query<'w, '_, Q, F>>
27302730
///
27312731
/// Use [`Option<Single<D, F>>`] instead if zero or one matching entities can exist.
27322732
///
2733+
/// Note that [`Single`] is not used as a search optimization. It is used as a validation with slight overhead compared to [`Query`].
2734+
///
27332735
/// See [`Query`] for more details.
27342736
///
27352737
/// [System parameter]: crate::system::SystemParam

crates/bevy_ecs/src/world/mod.rs

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ use bevy_ptr::{move_as_ptr, MovingPtr, OwningPtr, Ptr};
7474
use bevy_utils::prelude::DebugName;
7575
use core::{any::TypeId, fmt, mem::ManuallyDrop};
7676
use log::warn;
77-
use unsafe_world_cell::UnsafeWorldCell;
77+
use unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell};
7878

7979
/// Stores and exposes operations on [entities](Entity), [components](Component), resources,
8080
/// and their associated metadata.
@@ -984,6 +984,31 @@ impl World {
984984
unsafe { entities.fetch_mut(cell) }
985985
}
986986

987+
/// Returns an [`Entity`] iterator of current entities.
988+
///
989+
/// This is useful in contexts where you only have immutable access to the [`World`].
990+
/// If you have mutable access to the [`World`], use
991+
/// [`query()::<EntityRef>().iter(&world)`](World::query()) instead.
992+
#[inline]
993+
pub fn iter_entities(&self) -> impl Iterator<Item = EntityRef<'_>> + '_ {
994+
self.archetypes.iter().flat_map(|archetype| {
995+
archetype
996+
.entities_with_location()
997+
.map(|(entity, location)| {
998+
// SAFETY: entity exists and location accurately specifies the archetype where the entity is stored.
999+
let cell = UnsafeEntityCell::new(
1000+
self.as_unsafe_world_cell_readonly(),
1001+
entity,
1002+
location,
1003+
self.last_change_tick,
1004+
self.read_change_tick(),
1005+
);
1006+
// SAFETY: `&self` gives read access to the entire world.
1007+
unsafe { EntityRef::new(cell) }
1008+
})
1009+
})
1010+
}
1011+
9871012
/// Simultaneously provides access to entity data and a command queue, which
9881013
/// will be applied when the world is next flushed.
9891014
///
@@ -3852,7 +3877,7 @@ mod tests {
38523877
vec::Vec,
38533878
};
38543879
use bevy_ecs_macros::Component;
3855-
use bevy_platform::collections::HashSet;
3880+
use bevy_platform::collections::{HashMap, HashSet};
38563881
use bevy_utils::prelude::DebugName;
38573882
use core::{
38583883
any::TypeId,
@@ -4266,6 +4291,75 @@ mod tests {
42664291
);
42674292
}
42684293

4294+
#[test]
4295+
fn iterate_entities() {
4296+
let mut world = World::new();
4297+
let mut entity_counters = <HashMap<_, _>>::default();
4298+
4299+
let iterate_and_count_entities = |world: &World, entity_counters: &mut HashMap<_, _>| {
4300+
entity_counters.clear();
4301+
for entity in world.iter_entities() {
4302+
let counter = entity_counters.entry(entity.id()).or_insert(0);
4303+
*counter += 1;
4304+
}
4305+
};
4306+
4307+
// Adding one entity and validating iteration
4308+
let ent0 = world.spawn((Foo, Bar, Baz)).id();
4309+
4310+
iterate_and_count_entities(&world, &mut entity_counters);
4311+
assert_eq!(entity_counters[&ent0], 1);
4312+
assert_eq!(entity_counters.len(), 1);
4313+
4314+
// Spawning three more entities and then validating iteration
4315+
let ent1 = world.spawn((Foo, Bar)).id();
4316+
let ent2 = world.spawn((Bar, Baz)).id();
4317+
let ent3 = world.spawn((Foo, Baz)).id();
4318+
4319+
iterate_and_count_entities(&world, &mut entity_counters);
4320+
4321+
assert_eq!(entity_counters[&ent0], 1);
4322+
assert_eq!(entity_counters[&ent1], 1);
4323+
assert_eq!(entity_counters[&ent2], 1);
4324+
assert_eq!(entity_counters[&ent3], 1);
4325+
assert_eq!(entity_counters.len(), 4);
4326+
4327+
// Despawning first entity and then validating the iteration
4328+
assert!(world.despawn(ent0));
4329+
4330+
iterate_and_count_entities(&world, &mut entity_counters);
4331+
4332+
assert_eq!(entity_counters[&ent1], 1);
4333+
assert_eq!(entity_counters[&ent2], 1);
4334+
assert_eq!(entity_counters[&ent3], 1);
4335+
assert_eq!(entity_counters.len(), 3);
4336+
4337+
// Spawning three more entities, despawning three and then validating the iteration
4338+
let ent4 = world.spawn(Foo).id();
4339+
let ent5 = world.spawn(Bar).id();
4340+
let ent6 = world.spawn(Baz).id();
4341+
4342+
assert!(world.despawn(ent2));
4343+
assert!(world.despawn(ent3));
4344+
assert!(world.despawn(ent4));
4345+
4346+
iterate_and_count_entities(&world, &mut entity_counters);
4347+
4348+
assert_eq!(entity_counters[&ent1], 1);
4349+
assert_eq!(entity_counters[&ent5], 1);
4350+
assert_eq!(entity_counters[&ent6], 1);
4351+
assert_eq!(entity_counters.len(), 3);
4352+
4353+
// Despawning remaining entities and then validating the iteration
4354+
assert!(world.despawn(ent1));
4355+
assert!(world.despawn(ent5));
4356+
assert!(world.despawn(ent6));
4357+
4358+
iterate_and_count_entities(&world, &mut entity_counters);
4359+
4360+
assert_eq!(entity_counters.len(), 0);
4361+
}
4362+
42694363
#[test]
42704364
fn spawn_empty_bundle() {
42714365
let mut world = World::new();

crates/bevy_gizmos/src/config.rs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,31 @@ impl GizmoConfigStore {
110110
}
111111

112112
/// Returns [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T`
113+
///
114+
/// # Panics
115+
/// If the config does not exist for [`GizmoConfigGroup`] `T`
116+
///
117+
/// For a non-panicking version, see [`get_config`].
118+
///
119+
/// [`get_config`]: Self::get_config
113120
pub fn config<T: GizmoConfigGroup>(&self) -> (&GizmoConfig, &T) {
114-
let Some((config, ext)) = self.get_config_dyn(&TypeId::of::<T>()) else {
121+
let Some(configs) = self.get_config() else {
115122
panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?", T::type_path());
116123
};
124+
configs
125+
}
126+
127+
/// Returns Some([`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T` if they exist,
128+
/// else None.
129+
///
130+
/// If the configs will always be present, use [`config`].
131+
///
132+
/// [`config`]: Self::config
133+
pub fn get_config<T: GizmoConfigGroup>(&self) -> Option<(&GizmoConfig, &T)> {
134+
let (config, ext) = self.get_config_dyn(&TypeId::of::<T>())?;
117135
// hash map invariant guarantees that &dyn Reflect is of correct type T
118136
let ext = ext.as_any().downcast_ref().unwrap();
119-
(config, ext)
137+
Some((config, ext))
120138
}
121139

122140
/// Returns mutable [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`TypeId`] of a [`GizmoConfigGroup`]
@@ -129,13 +147,31 @@ impl GizmoConfigStore {
129147
}
130148

131149
/// Returns mutable [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T`
150+
///
151+
/// # Panics
152+
/// If the config does not exist for [`GizmoConfigGroup`] `T`
153+
///
154+
/// For a non-panicking version, see [`get_config_mut`].
155+
///
156+
/// [`get_config_mut`]: Self::get_config_mut
132157
pub fn config_mut<T: GizmoConfigGroup>(&mut self) -> (&mut GizmoConfig, &mut T) {
133-
let Some((config, ext)) = self.get_config_mut_dyn(&TypeId::of::<T>()) else {
158+
let Some(configs) = self.get_config_mut() else {
134159
panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?", T::type_path());
135160
};
161+
configs
162+
}
163+
164+
/// Returns mutable Some([`GizmoConfig`] and [`GizmoConfigGroup`]) associated with [`GizmoConfigGroup`] `T` if they exist,
165+
/// else None
166+
///
167+
/// If the configs will always be present, use [`config_mut`].
168+
///
169+
/// [`config_mut`]: Self::config_mut
170+
pub fn get_config_mut<T: GizmoConfigGroup>(&mut self) -> Option<(&mut GizmoConfig, &mut T)> {
171+
let (config, ext) = self.get_config_mut_dyn(&TypeId::of::<T>())?;
136172
// hash map invariant guarantees that &dyn Reflect is of correct type T
137173
let ext = ext.as_any_mut().downcast_mut().unwrap();
138-
(config, ext)
174+
Some((config, ext))
139175
}
140176

141177
/// Returns an iterator over all [`GizmoConfig`]s.

crates/bevy_gizmos/src/gizmos.rs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,28 @@ where
234234
system_meta: &SystemMeta,
235235
world: UnsafeWorldCell,
236236
) -> Result<(), SystemParamValidationError> {
237-
// SAFETY: Delegated to existing `SystemParam` implementations.
237+
// SAFETY: Delegated to existing `SystemParam` implementation.
238238
unsafe {
239-
GizmosState::<Config, Clear>::validate_param(&mut state.state, system_meta, world)
239+
GizmosState::<Config, Clear>::validate_param(&mut state.state, system_meta, world)?;
240+
}
241+
242+
// SAFETY: Delegated to existing `SystemParam` implementation.
243+
let (_, f1) = unsafe {
244+
GizmosState::<Config, Clear>::get_param(
245+
&mut state.state,
246+
system_meta,
247+
world,
248+
world.change_tick(),
249+
)
250+
};
251+
// This if-block is to accommodate an Option<Gizmos> SystemParam.
252+
// The user may decide not to initialize a gizmo group, so its config will not exist.
253+
if f1.get_config::<Config>().is_none() {
254+
Err(SystemParamValidationError::invalid::<Self>(
255+
format!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?",
256+
Config::type_path())))
257+
} else {
258+
Ok(())
240259
}
241260
}
242261

@@ -352,11 +371,19 @@ where
352371
Clear: 'static + Send + Sync,
353372
{
354373
fn queue(&mut self, _system_meta: &SystemMeta, mut world: DeferredWorld) {
355-
let mut storage = world.resource_mut::<GizmoStorage<Config, Clear>>();
356-
storage.list_positions.append(&mut self.list_positions);
357-
storage.list_colors.append(&mut self.list_colors);
358-
storage.strip_positions.append(&mut self.strip_positions);
359-
storage.strip_colors.append(&mut self.strip_colors);
374+
if let Some(mut storage) = world.get_resource_mut::<GizmoStorage<Config, Clear>>() {
375+
storage.list_positions.append(&mut self.list_positions);
376+
storage.list_colors.append(&mut self.list_colors);
377+
storage.strip_positions.append(&mut self.strip_positions);
378+
storage.strip_colors.append(&mut self.strip_colors);
379+
} else {
380+
// Prevent the buffer from growing indefinitely if GizmoStorage
381+
// for the config group has not been initialized
382+
self.list_positions.clear();
383+
self.list_colors.clear();
384+
self.strip_positions.clear();
385+
self.strip_colors.clear();
386+
}
360387
}
361388
}
362389

crates/bevy_gizmos/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ pub mod grid;
3636
pub mod primitives;
3737
pub mod retained;
3838
pub mod rounded_box;
39+
mod simplex_stroke_font;
40+
pub mod stroke_text;
3941

4042
#[cfg(feature = "bevy_mesh")]
4143
pub mod skinned_mesh_bounds;

0 commit comments

Comments
 (0)