Skip to content

Commit 0a76b57

Browse files
committed
Automatically resolve entities from UUIDs
Also fix bug when doing so caused the level to be marked as unsaved
1 parent b2d451a commit 0a76b57

File tree

7 files changed

+100
-40
lines changed

7 files changed

+100
-40
lines changed

examples/example2d.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ fn main() {
143143
// Common systems
144144
// ========================================================================
145145

146-
app.add_systems(Update, (resolve_laser_pointers, draw_laser_pointers));
146+
app.add_systems(Update, draw_laser_pointers);
147147
app.add_systems(
148148
Update,
149149
(control_player, eat_fruits).run_if(in_state(YoleckEditorState::GameActive)),
@@ -574,15 +574,6 @@ fn populate_triangle(
574574
// LaserPointer (shared)
575575
// ============================================================================
576576

577-
fn resolve_laser_pointers(
578-
mut query: Query<&mut TextLaserPointer>,
579-
uuid_registry: Res<YoleckUuidRegistry>,
580-
) {
581-
for mut laser_pointer in query.iter_mut() {
582-
let _ = laser_pointer.target.resolve(&uuid_registry);
583-
}
584-
}
585-
586577
fn draw_laser_pointers(
587578
query: Query<(&TextLaserPointer, &GlobalTransform)>,
588579
targets_query: Query<&GlobalTransform>,

examples/example3d.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ fn main() {
7474
});
7575
app.add_yoleck_auto_edit::<LaserPointer>();
7676
app.add_systems(YoleckSchedule::Populate, populate_simple_sphere);
77-
app.add_systems(Update, (resolve_laser_pointers, draw_laser_pointers));
77+
app.add_systems(Update, draw_laser_pointers);
7878

7979
app.add_systems(
8080
Update,
@@ -278,15 +278,6 @@ fn populate_simple_sphere(
278278
});
279279
}
280280

281-
fn resolve_laser_pointers(
282-
mut query: Query<&mut LaserPointer>,
283-
uuid_registry: Res<YoleckUuidRegistry>,
284-
) {
285-
for mut laser_pointer in query.iter_mut() {
286-
let _ = laser_pointer.target.resolve(&uuid_registry);
287-
}
288-
}
289-
290281
fn draw_laser_pointers(
291282
query: Query<(&LaserPointer, &GlobalTransform)>,
292283
targets_query: Query<&GlobalTransform>,

macros/src/lib.rs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -228,22 +228,20 @@ fn impl_yoleck_auto_edit_derive(input: DeriveInput) -> Result<TokenStream, Error
228228
let name = &input.ident;
229229
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
230230

231-
let fields = match &input.data {
232-
Data::Struct(data) => match &data.fields {
233-
Fields::Named(fields) => &fields.named,
234-
_ => {
235-
return Err(Error::new_spanned(
236-
&input,
237-
"YoleckAutoEdit only supports structs with named fields",
238-
));
239-
}
240-
},
241-
_ => {
231+
let fields = if let Data::Struct(data) = &input.data {
232+
if let Fields::Named(fields) = &data.fields {
233+
&fields.named
234+
} else {
242235
return Err(Error::new_spanned(
243236
&input,
244-
"YoleckAutoEdit only supports structs",
237+
"YoleckAutoEdit only supports structs with named fields",
245238
));
246239
}
240+
} else {
241+
return Err(Error::new_spanned(
242+
&input,
243+
"YoleckAutoEdit only supports structs",
244+
));
247245
};
248246

249247
let mut field_uis = Vec::new();
@@ -256,9 +254,16 @@ fn impl_yoleck_auto_edit_derive(input: DeriveInput) -> Result<TokenStream, Error
256254
}
257255

258256
let mut entity_ref_fields = Vec::new();
257+
let mut entity_ref_field_names = Vec::new();
259258
for field in fields {
260259
if let Some(info) = parse_entity_ref_attrs(field)? {
261260
entity_ref_fields.push(info);
261+
entity_ref_field_names.push(
262+
field
263+
.ident
264+
.as_ref()
265+
.expect("fields are taken from a named struct variant"),
266+
);
262267
}
263268
}
264269

@@ -322,6 +327,12 @@ fn impl_yoleck_auto_edit_derive(input: DeriveInput) -> Result<TokenStream, Error
322327
fn get_entity_ref_mut(&mut self, field_name: &str) -> &mut bevy_yoleck::entity_ref::YoleckEntityRef {
323328
#get_entity_ref_mut_body
324329
}
330+
331+
fn resolve_entity_refs(&mut self, registry: &bevy_yoleck::prelude::YoleckUuidRegistry) {
332+
#(
333+
let _ = self.#entity_ref_field_names.resolve(registry);
334+
)*
335+
}
325336
}
326337
};
327338

src/auto_edit.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
use bevy::prelude::*;
22
use bevy_egui::egui;
33

4+
use crate::YoleckInternalSchedule;
5+
use crate::entity_ref::resolve_entity_refs;
6+
47
#[cfg(feature = "vpeol")]
58
use crate::entity_ref::validate_entity_ref_requirements_for;
69

710
#[cfg(feature = "vpeol")]
811
use crate::entity_ref::YoleckEntityRef;
12+
#[cfg(feature = "vpeol")]
13+
use crate::prelude::YoleckUuidRegistry;
914

1015
#[cfg(feature = "vpeol")]
1116
use std::collections::HashMap;
@@ -635,6 +640,7 @@ pub fn auto_edit_system<T: YoleckComponent + YoleckAutoEdit + YoleckEntityRefAcc
635640
mut ui: ResMut<YoleckUi>,
636641
mut edit: YoleckEdit<&mut T>,
637642
entities_query: Query<(&YoleckEntityUuid, &YoleckManaged)>,
643+
registry: Res<YoleckUuidRegistry>,
638644
) {
639645
let Ok(mut component) = edit.single_mut() else {
640646
return;
@@ -668,6 +674,8 @@ pub fn auto_edit_system<T: YoleckComponent + YoleckAutoEdit + YoleckEntityRefAcc
668674
ui.separator();
669675
T::auto_edit(&mut component, ui);
670676
});
677+
678+
component.resolve_entity_refs(registry.as_ref());
671679
}
672680

673681
#[cfg(not(feature = "vpeol"))]
@@ -712,6 +720,10 @@ impl YoleckAutoEditExt for App {
712720
&mut self,
713721
) {
714722
self.add_yoleck_edit_system(auto_edit_system::<T>);
723+
self.add_systems(
724+
YoleckInternalSchedule::PostLoadResolutions,
725+
resolve_entity_refs::<T>,
726+
);
715727

716728
let construction_specs = self
717729
.world_mut()
@@ -723,7 +735,15 @@ impl YoleckAutoEditExt for App {
723735
}
724736

725737
#[cfg(not(feature = "vpeol"))]
726-
fn add_yoleck_auto_edit<T: YoleckComponent + YoleckAutoEdit>(&mut self) {
738+
fn add_yoleck_auto_edit<T: YoleckComponent + YoleckAutoEdit + YoleckEntityRefAccessor>(
739+
&mut self,
740+
) {
741+
use crate::YoleckInternalSchedule;
742+
727743
self.add_yoleck_edit_system(auto_edit_system::<T>);
744+
self.add_systems(
745+
YoleckInternalSchedule::PostLoadResolutions,
746+
resolve_entity_refs::<T>,
747+
);
728748
}
729749
}

src/entity_management.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::populating::PopulateReason;
1616
use crate::prelude::{YoleckEntityUuid, YoleckUuidRegistry};
1717
use crate::{
1818
YoleckBelongsToLevel, YoleckEntityConstructionSpecs, YoleckEntityLifecycleStatus,
19-
YoleckLevelJustLoaded, YoleckManaged, YoleckSchedule, YoleckState,
19+
YoleckInternalSchedule, YoleckLevelJustLoaded, YoleckManaged, YoleckSchedule, YoleckState,
2020
};
2121

2222
/// Used by Yoleck to determine how to handle the entity.
@@ -185,6 +185,10 @@ pub(crate) fn process_loading_command(
185185
}
186186
}
187187

188+
pub(crate) fn yoleck_run_post_load_resolutions_schedule(world: &mut World) {
189+
world.run_schedule(YoleckInternalSchedule::PostLoadResolutions);
190+
}
191+
188192
pub(crate) fn yoleck_run_level_loaded_schedule(world: &mut World) {
189193
world.run_schedule(YoleckSchedule::LevelLoaded);
190194
}

src/entity_ref.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
use std::any::TypeId;
2+
3+
use bevy::ecs::component::Mutable;
14
use bevy::prelude::*;
25
use serde::{Deserialize, Serialize};
36
use uuid::Uuid;
47

8+
use crate::YoleckManaged;
59
use crate::entity_uuid::YoleckUuidRegistry;
610
use crate::errors::YoleckEntityRefCannotBeResolved;
711

@@ -58,6 +62,18 @@ pub struct YoleckEntityRef {
5862
resolved: Option<Entity>,
5963
}
6064

65+
// impl std::hash::Hash for YoleckEntityRef {
66+
// fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
67+
// self.uuid.hash(state);
68+
// }
69+
// }
70+
71+
// impl PartialEq for YoleckEntityRef {
72+
// fn eq(&self, other: &Self) -> bool {
73+
// self.uuid == other.uuid
74+
// }
75+
// }
76+
6177
impl YoleckEntityRef {
6278
pub fn new() -> Self {
6379
Self {
@@ -118,6 +134,8 @@ impl YoleckEntityRef {
118134
pub trait YoleckEntityRefAccessor: Sized + Send + Sync + 'static {
119135
fn entity_ref_fields() -> &'static [(&'static str, Option<&'static str>)];
120136
fn get_entity_ref_mut(&mut self, field_name: &str) -> &mut YoleckEntityRef;
137+
// TODO: make this more versatile
138+
fn resolve_entity_refs(&mut self, registry: &YoleckUuidRegistry);
121139
}
122140

123141
#[cfg(feature = "vpeol")]
@@ -139,3 +157,19 @@ pub(crate) fn validate_entity_ref_requirements_for<T: YoleckEntityRefAccessor>(
139157
}
140158
}
141159
}
160+
161+
pub fn resolve_entity_refs<
162+
T: 'static + Component<Mutability = Mutable> + YoleckEntityRefAccessor,
163+
>(
164+
mut query: Query<(&mut T, &mut YoleckManaged)>,
165+
registry: Res<YoleckUuidRegistry>,
166+
) {
167+
for (mut component, mut managed) in query.iter_mut() {
168+
component.resolve_entity_refs(registry.as_ref());
169+
if let Some(data) = managed.components_data.get_mut(&TypeId::of::<T>())
170+
&& let Some(data) = data.downcast_mut::<T>()
171+
{
172+
data.resolve_entity_refs(registry.as_ref());
173+
}
174+
}
175+
}

src/lib.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -292,16 +292,21 @@ impl Plugin for YoleckPluginBase {
292292
entity_management::yoleck_process_raw_entries,
293293
ApplyDeferred,
294294
(
295-
entity_management::yoleck_run_level_loaded_schedule,
295+
entity_management::yoleck_run_post_load_resolutions_schedule,
296+
entity_management::yoleck_run_level_loaded_schedule.run_if(
297+
|freshly_loaded_level_entities: Query<
298+
(),
299+
(With<YoleckLevelJustLoaded>, Without<YoleckLevelInEditor>),
300+
>| { !freshly_loaded_level_entities.is_empty() },
301+
),
296302
entity_management::yoleck_remove_just_loaded_marker_from_levels,
297303
ApplyDeferred,
298304
)
299305
.chain()
300306
.run_if(
301-
|freshly_loaded_level_entities: Query<
302-
(),
303-
(With<YoleckLevelJustLoaded>, Without<YoleckLevelInEditor>),
304-
>| { !freshly_loaded_level_entities.is_empty() },
307+
|freshly_loaded_level_entities: Query<(), With<YoleckLevelJustLoaded>>| {
308+
!freshly_loaded_level_entities.is_empty()
309+
},
305310
),
306311
)
307312
.chain()
@@ -332,6 +337,7 @@ impl Plugin for YoleckPluginBase {
332337
.before(YoleckSystems::ProcessRawEntities),),
333338
);
334339
app.add_schedule(Schedule::new(YoleckSchedule::Populate));
340+
app.add_schedule(Schedule::new(YoleckInternalSchedule::PostLoadResolutions));
335341
app.add_schedule(Schedule::new(YoleckSchedule::LevelLoaded));
336342
app.add_schedule(Schedule::new(YoleckSchedule::OverrideCommonComponents));
337343
}
@@ -567,7 +573,7 @@ pub struct YoleckManaged {
567573

568574
lifecycle_status: YoleckEntityLifecycleStatus,
569575

570-
components_data: HashMap<TypeId, BoxedAny>,
576+
pub(crate) components_data: HashMap<TypeId, BoxedAny>,
571577
}
572578

573579
/// A marker for entities that belongs to the Yoleck level and should be despawned with it.
@@ -645,6 +651,9 @@ pub struct YoleckPlaytestLevel(pub Option<YoleckRawLevel>);
645651
#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash)]
646652
pub(crate) enum YoleckInternalSchedule {
647653
UpdateManagedDataFromComponents,
654+
/// Before [`LevelLoaded`][YoleckSchedule::LevelLoaded] to resolve things like entity
655+
/// references.
656+
PostLoadResolutions,
648657
}
649658

650659
/// Schedules for user code to do the actual entity/level population after Yoleck spawns the level

0 commit comments

Comments
 (0)