bevy_yoleck/
specs_registration.rs1use std::any::{Any, TypeId};
2use std::marker::PhantomData;
3
4use bevy::ecs::component::Mutable;
5use bevy::ecs::system::EntityCommands;
6use bevy::platform::collections::HashMap;
7use bevy::prelude::*;
8use serde::{Deserialize, Serialize};
9
10use crate::prelude::YoleckEditorState;
11use crate::{BoxedAny, YoleckEntityLifecycleStatus, YoleckInternalSchedule, YoleckManaged};
12
13pub trait YoleckComponent:
19 Default + Clone + PartialEq + Component<Mutability = Mutable> + Serialize + for<'a> Deserialize<'a>
20{
21 const KEY: &'static str;
22}
23
24pub struct YoleckEntityType {
31 pub name: String,
33 pub(crate) components: Vec<Box<dyn YoleckComponentHandler>>,
34 #[allow(clippy::type_complexity)]
35 pub(crate) on_init:
36 Vec<Box<dyn 'static + Sync + Send + Fn(YoleckEditorState, &mut EntityCommands)>>,
37 pub has_uuid: bool,
38}
39
40impl YoleckEntityType {
41 pub fn new(name: impl ToString) -> Self {
42 Self {
43 name: name.to_string(),
44 components: Default::default(),
45 on_init: Default::default(),
46 has_uuid: false,
47 }
48 }
49
50 pub fn with<T: YoleckComponent>(mut self) -> Self {
52 self.components
53 .push(Box::<YoleckComponentHandlerImpl<T>>::default());
54 self
55 }
56
57 pub fn insert_on_init<T: Bundle>(
61 mut self,
62 bundle_maker: impl 'static + Sync + Send + Fn() -> T,
63 ) -> Self {
64 self.on_init.push(Box::new(move |_, cmd| {
65 cmd.insert(bundle_maker());
66 }));
67 self
68 }
69
70 pub fn insert_on_init_during_editor<T: Bundle>(
73 mut self,
74 bundle_maker: impl 'static + Sync + Send + Fn() -> T,
75 ) -> Self {
76 self.on_init.push(Box::new(move |editor_state, cmd| {
77 if matches!(editor_state, YoleckEditorState::EditorActive) {
78 cmd.insert(bundle_maker());
79 }
80 }));
81 self
82 }
83
84 pub fn insert_on_init_during_game<T: Bundle>(
87 mut self,
88 bundle_maker: impl 'static + Sync + Send + Fn() -> T,
89 ) -> Self {
90 self.on_init.push(Box::new(move |editor_state, cmd| {
91 if matches!(editor_state, YoleckEditorState::GameActive) {
92 cmd.insert(bundle_maker());
93 }
94 }));
95 self
96 }
97
98 pub fn with_uuid(mut self) -> Self {
127 self.has_uuid = true;
128 self
129 }
130}
131
132pub(crate) trait YoleckComponentHandler: 'static + Sync + Send {
133 fn component_type(&self) -> TypeId;
134 fn key(&self) -> &'static str;
135 fn init_in_entity(
136 &self,
137 data: Option<serde_json::Value>,
138 cmd: &mut EntityCommands,
139 components_data: &mut HashMap<TypeId, BoxedAny>,
140 );
141 fn build_in_bevy_app(&self, app: &mut App);
142 fn serialize(&self, component: &dyn Any) -> serde_json::Value;
143}
144
145#[derive(Default)]
146struct YoleckComponentHandlerImpl<T: YoleckComponent> {
147 _phantom_data: PhantomData<T>,
148}
149
150impl<T: YoleckComponent> YoleckComponentHandler for YoleckComponentHandlerImpl<T> {
151 fn component_type(&self) -> TypeId {
152 TypeId::of::<T>()
153 }
154
155 fn key(&self) -> &'static str {
156 T::KEY
157 }
158
159 fn init_in_entity(
160 &self,
161 data: Option<serde_json::Value>,
162 cmd: &mut EntityCommands,
163 components_data: &mut HashMap<TypeId, BoxedAny>,
164 ) {
165 let component: T = if let Some(data) = data {
166 match serde_json::from_value(data) {
167 Ok(component) => component,
168 Err(err) => {
169 error!("Cannot load {:?}: {:?}", T::KEY, err);
170 return;
171 }
172 }
173 } else {
174 Default::default()
175 };
176 components_data.insert(self.component_type(), Box::new(component.clone()));
177 cmd.insert(component);
178 }
179
180 fn build_in_bevy_app(&self, app: &mut App) {
181 if let Some(schedule) =
182 app.get_schedule_mut(YoleckInternalSchedule::UpdateManagedDataFromComponents)
183 {
184 schedule.add_systems(Self::update_data_from_components);
185 }
186 }
187
188 fn serialize(&self, component: &dyn Any) -> serde_json::Value {
189 let concrete = component
190 .downcast_ref::<T>()
191 .expect("Serialize must be called with the correct type");
192 serde_json::to_value(concrete).expect("Component must always be serializable")
193 }
194}
195
196impl<T: YoleckComponent> YoleckComponentHandlerImpl<T> {
197 fn update_data_from_components(mut query: Query<(&mut YoleckManaged, &mut T)>) {
198 for (mut yoleck_managed, component) in query.iter_mut() {
199 let yoleck_managed = yoleck_managed.as_mut();
200 match yoleck_managed.components_data.entry(TypeId::of::<T>()) {
201 bevy::platform::collections::hash_map::Entry::Vacant(entry) => {
202 yoleck_managed.lifecycle_status = YoleckEntityLifecycleStatus::JustChanged;
203 entry.insert(Box::<T>::new(component.clone()));
204 }
205 bevy::platform::collections::hash_map::Entry::Occupied(mut entry) => {
206 let existing: &mut T = entry
207 .get_mut()
208 .downcast_mut()
209 .expect("Component data is of wrong type");
210 if existing != component.as_ref() {
211 yoleck_managed.lifecycle_status = YoleckEntityLifecycleStatus::JustChanged;
212 *existing = component.clone();
213 }
214 }
215 }
216 }
217 }
218}