bevy_yoleck/
entity_ref.rs

1use std::any::TypeId;
2
3use bevy::ecs::component::Mutable;
4use bevy::prelude::*;
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7
8use crate::YoleckManaged;
9use crate::entity_uuid::YoleckUuidRegistry;
10use crate::errors::YoleckEntityRefCannotBeResolved;
11
12/// A reference to another Yoleck entity, stored by UUID for persistence.
13///
14/// This allows one entity to reference another entity in a way that survives saving and loading.
15/// The reference is stored as a UUID in the level file, which gets resolved to an actual `Entity`
16/// at runtime.
17///
18/// # Requirements
19///
20/// **Important:** Only entities with `.with_uuid()` can be referenced. When defining entity types
21/// that should be referenceable, make sure to add `.with_uuid()` to the entity type:
22///
23/// ```no_run
24/// # use bevy::prelude::*;
25/// # use bevy_yoleck::prelude::*;
26/// # let mut app = App::new();
27/// app.add_yoleck_entity_type({
28///     YoleckEntityType::new("Planet")
29///         .with_uuid()  // Required for entity references!
30///         // ... other configuration
31/// #       ;YoleckEntityType::new("Planet")
32/// });
33/// ```
34///
35/// # Editor Features
36///
37/// In the editor, entity references can be set using:
38/// - Dropdown menu to select from available entities
39/// - Drag and drop from the entity list (only entities with UUID can be dragged)
40/// - Viewport click selection using the 🎯 button
41///
42/// # Usage
43///
44/// Add a `YoleckEntityRef` field to your component with the `entity_ref` attribute to filter by
45/// entity type:
46///
47/// ```no_run
48/// # use bevy::prelude::*;
49/// # use bevy_yoleck::prelude::*;
50/// # use serde::{Deserialize, Serialize};
51/// #[derive(Component, YoleckComponent, YoleckAutoEdit, Serialize, Deserialize, Clone, PartialEq, Default)]
52/// struct LaserPointer {
53///     #[yoleck(entity_ref = "Planet")]
54///     target: YoleckEntityRef,
55/// }
56/// ```
57#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default, Debug)]
58pub struct YoleckEntityRef {
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    uuid: Option<Uuid>,
61    #[serde(skip)]
62    resolved: Option<Entity>,
63}
64
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
77impl YoleckEntityRef {
78    pub fn new() -> Self {
79        Self {
80            uuid: None,
81            resolved: None,
82        }
83    }
84
85    pub fn from_uuid(uuid: Uuid) -> Self {
86        Self {
87            uuid: Some(uuid),
88            resolved: None,
89        }
90    }
91
92    pub fn is_some(&self) -> bool {
93        self.uuid.is_some()
94    }
95
96    pub fn is_none(&self) -> bool {
97        self.uuid.is_none()
98    }
99
100    pub fn entity(&self) -> Option<Entity> {
101        self.resolved
102    }
103
104    pub fn uuid(&self) -> Option<Uuid> {
105        self.uuid
106    }
107
108    pub fn clear(&mut self) {
109        self.uuid = None;
110        self.resolved = None;
111    }
112
113    pub fn set(&mut self, uuid: Uuid) {
114        self.uuid = Some(uuid);
115        self.resolved = None;
116    }
117
118    pub fn resolve(
119        &mut self,
120        registry: &YoleckUuidRegistry,
121    ) -> Result<(), YoleckEntityRefCannotBeResolved> {
122        if let Some(uuid) = self.uuid {
123            self.resolved = registry.get(uuid);
124            if self.resolved.is_none() {
125                return Err(YoleckEntityRefCannotBeResolved { uuid });
126            }
127        } else {
128            self.resolved = None;
129        }
130        Ok(())
131    }
132}
133
134pub trait YoleckEntityRefAccessor: Sized + Send + Sync + 'static {
135    fn entity_ref_fields() -> &'static [(&'static str, Option<&'static str>)];
136    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);
139}
140
141pub(crate) fn validate_entity_ref_requirements_for<T: YoleckEntityRefAccessor>(
142    construction_specs: &crate::YoleckEntityConstructionSpecs,
143) {
144    for (field_name, filter) in T::entity_ref_fields() {
145        if let Some(required_entity_type) = filter
146            && let Some(entity_type_info) =
147                construction_specs.get_entity_type_info(required_entity_type)
148            && !entity_type_info.has_uuid
149        {
150            error!(
151                "Component '{}' field '{}' requires entity type '{}' to have UUID.",
152                std::any::type_name::<T>(),
153                field_name,
154                required_entity_type
155            );
156        }
157    }
158}
159
160pub fn resolve_entity_refs<
161    T: 'static + Component<Mutability = Mutable> + YoleckEntityRefAccessor,
162>(
163    mut query: Query<(&mut T, &mut YoleckManaged)>,
164    registry: Res<YoleckUuidRegistry>,
165) {
166    for (mut component, mut managed) in query.iter_mut() {
167        component.resolve_entity_refs(registry.as_ref());
168        if let Some(data) = managed.components_data.get_mut(&TypeId::of::<T>())
169            && let Some(data) = data.downcast_mut::<T>()
170        {
171            data.resolve_entity_refs(registry.as_ref());
172        }
173    }
174}