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 {
105 self.has_uuid = true;
106 self
107 }
108}
109
110pub(crate) trait YoleckComponentHandler: 'static + Sync + Send {
111 fn component_type(&self) -> TypeId;
112 fn key(&self) -> &'static str;
113 fn init_in_entity(
114 &self,
115 data: Option<serde_json::Value>,
116 cmd: &mut EntityCommands,
117 components_data: &mut HashMap<TypeId, BoxedAny>,
118 );
119 fn build_in_bevy_app(&self, app: &mut App);
120 fn serialize(&self, component: &dyn Any) -> serde_json::Value;
121}
122
123#[derive(Default)]
124struct YoleckComponentHandlerImpl<T: YoleckComponent> {
125 _phantom_data: PhantomData<T>,
126}
127
128impl<T: YoleckComponent> YoleckComponentHandler for YoleckComponentHandlerImpl<T> {
129 fn component_type(&self) -> TypeId {
130 TypeId::of::<T>()
131 }
132
133 fn key(&self) -> &'static str {
134 T::KEY
135 }
136
137 fn init_in_entity(
138 &self,
139 data: Option<serde_json::Value>,
140 cmd: &mut EntityCommands,
141 components_data: &mut HashMap<TypeId, BoxedAny>,
142 ) {
143 let component: T = if let Some(data) = data {
144 match serde_json::from_value(data) {
145 Ok(component) => component,
146 Err(err) => {
147 error!("Cannot load {:?}: {:?}", T::KEY, err);
148 return;
149 }
150 }
151 } else {
152 Default::default()
153 };
154 components_data.insert(self.component_type(), Box::new(component.clone()));
155 cmd.insert(component);
156 }
157
158 fn build_in_bevy_app(&self, app: &mut App) {
159 if let Some(schedule) =
160 app.get_schedule_mut(YoleckInternalSchedule::UpdateManagedDataFromComponents)
161 {
162 schedule.add_systems(Self::update_data_from_components);
163 }
164 }
165
166 fn serialize(&self, component: &dyn Any) -> serde_json::Value {
167 let concrete = component
168 .downcast_ref::<T>()
169 .expect("Serialize must be called with the correct type");
170 serde_json::to_value(concrete).expect("Component must always be serializable")
171 }
172}
173
174impl<T: YoleckComponent> YoleckComponentHandlerImpl<T> {
175 fn update_data_from_components(mut query: Query<(&mut YoleckManaged, &mut T)>) {
176 for (mut yoleck_managed, component) in query.iter_mut() {
177 let yoleck_managed = yoleck_managed.as_mut();
178 match yoleck_managed.components_data.entry(TypeId::of::<T>()) {
179 bevy::platform::collections::hash_map::Entry::Vacant(entry) => {
180 yoleck_managed.lifecycle_status = YoleckEntityLifecycleStatus::JustChanged;
181 entry.insert(Box::<T>::new(component.clone()));
182 }
183 bevy::platform::collections::hash_map::Entry::Occupied(mut entry) => {
184 let existing: &mut T = entry
185 .get_mut()
186 .downcast_mut()
187 .expect("Component data is of wrong type");
188 if existing != component.as_ref() {
189 yoleck_managed.lifecycle_status = YoleckEntityLifecycleStatus::JustChanged;
190 *existing = component.clone();
191 }
192 }
193 }
194 }
195 }
196}