1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
use std::ops::RangeFrom;
use bevy::ecs::query::{QueryData, QueryFilter, WorldQuery};
use bevy::ecs::system::{EntityCommands, SystemParam};
use bevy::prelude::*;
use bevy::utils::HashMap;
use crate::entity_management::EntitiesToPopulate;
/// Wrapper for writing queries in populate systems.
#[derive(SystemParam)]
pub struct YoleckPopulate<'w, 's, Q: 'static + QueryData, F: 'static + QueryFilter = ()> {
entities_to_populate: Res<'w, EntitiesToPopulate>,
query: Query<'w, 's, Q, F>,
commands: Commands<'w, 's>,
}
impl<Q: 'static + QueryData, F: 'static + QueryFilter> YoleckPopulate<'_, '_, Q, F> {
/// Iterate over the entities that need populating in order to add/update components using
/// a Bevy command.
pub fn populate(
&mut self,
mut dlg: impl FnMut(YoleckPopulateContext, EntityCommands, <Q as WorldQuery>::Item<'_>),
) {
for (entity, populate_reason) in self.entities_to_populate.0.iter() {
if let Ok(data) = self.query.get_mut(*entity) {
let cmd = self.commands.entity(*entity);
let context = YoleckPopulateContext {
reason: *populate_reason,
};
dlg(context, cmd, data);
}
}
}
}
#[derive(Clone, Copy)]
pub(crate) enum PopulateReason {
EditorInit,
EditorUpdate,
RealGame,
}
/// A context for [`YoleckPopulate::populate`].
pub struct YoleckPopulateContext {
pub(crate) reason: PopulateReason,
}
impl YoleckPopulateContext {
/// `true` if the entity is created in editor mode, `false` if created in playtest or actual game.
pub fn is_in_editor(&self) -> bool {
match self.reason {
PopulateReason::EditorInit => true,
PopulateReason::EditorUpdate => true,
PopulateReason::RealGame => false,
}
}
/// `true` if this is this is the first time the entity is populated, `false` if the entity was
/// popultated before.
pub fn is_first_time(&self) -> bool {
match self.reason {
PopulateReason::EditorInit => true,
PopulateReason::EditorUpdate => false,
PopulateReason::RealGame => true,
}
}
}
/// See [`YoleckMarking`].
#[derive(Debug, Component, PartialEq, Eq, Clone, Copy)]
pub struct YoleckSystemMarker(usize);
#[derive(Resource)]
struct MarkerGenerator(RangeFrom<usize>);
impl FromWorld for YoleckSystemMarker {
fn from_world(world: &mut World) -> Self {
let mut marker = world.get_resource_or_insert_with(|| MarkerGenerator(1..));
YoleckSystemMarker(marker.0.next().unwrap())
}
}
/// Use to mark child entities created from a specific system.
///
/// Using `despawn_descendants` is dangerous because it can delete entities created by other
/// populate systems in the same frame. Instead, a populate system that wants to despawn its own
/// child entities should do something like this:
///
/// ```no_run
/// # use bevy::prelude::*;
/// # use bevy_yoleck::prelude::*;
/// # use serde::{Deserialize, Serialize};
/// # #[derive(Default, Clone, PartialEq, Serialize, Deserialize, Component, YoleckComponent)]
/// # struct MyComponent;
/// fn populate_system(mut populate: YoleckPopulate<&MyComponent>, marking: YoleckMarking) {
/// populate.populate(|_ctx, mut cmd, my_component| {
/// marking.despawn_marked(&mut cmd);
/// cmd.with_children(|commands| {
/// let mut child = commands.spawn(marking.marker());
/// child.insert((
/// // relevant Bevy components
/// ));
/// });
/// });
/// }
/// ```
#[derive(SystemParam)]
pub struct YoleckMarking<'w, 's> {
designated_marker: Local<'s, YoleckSystemMarker>,
children_query: Query<'w, 's, &'static Children>,
marked_query: Query<'w, 's, (&'static Parent, &'static YoleckSystemMarker)>,
}
impl YoleckMarking<'_, '_> {
/// Create a marker unique to this system.
pub fn marker(&self) -> YoleckSystemMarker {
*self.designated_marker
}
/// Despawn (with `despawn_recursive`) all the entities marked by the current system, that are
/// descendants of the entity edited by the supplied `cmd`.
pub fn despawn_marked(&self, cmd: &mut EntityCommands) {
let mut marked_children_map: HashMap<Entity, Vec<Entity>> = Default::default();
for child in self.children_query.iter_descendants(cmd.id()) {
let Ok((parent, marker)) = self.marked_query.get(child) else {
continue;
};
if *marker == *self.designated_marker {
marked_children_map
.entry(parent.get())
.or_default()
.push(child);
}
}
for (parent, children) in marked_children_map {
cmd.commands().entity(parent).remove_children(&children);
for child in children {
cmd.commands().entity(child).despawn_recursive();
}
}
}
}