bevy_yoleck/
lib.rs

1//! # Your Own Level Editor Creation Kit
2//!
3//! Yoleck is a crate for having a game built with the Bevy game engine act as its own level
4//! editor.
5//!
6//! Yoleck uses Plain Old Rust Structs to store the data, and uses Serde to store them in files.
7//! The user code defines _populate systems_ for creating Bevy entities (populating their
8//! components) from these structs and _edit systems_ to edit these structs with egui.
9//!
10//! The synchronization between the structs and the files is bidirectional, and so is the
11//! synchronization between the structs and the egui widgets, but the synchronization from the
12//! structs to the entities is unidirectional - changes in the entities are not reflected in the
13//! structs:
14//!
15//! ```none
16//! ┌────────┐  Populate   ┏━━━━━━━━━┓   Edit      ┌───────┐
17//! │Bevy    │  Systems    ┃Yoleck   ┃   Systems   │egui   │
18//! │Entities│◄────────────┃Component┃◄═══════════►│Widgets│
19//! └────────┘             ┃Structs  ┃             └───────┘
20//!                        ┗━━━━━━━━━┛
21//!                            ▲
22//!                            ║
23//!                            ║ Serde
24//!                            ║
25//!                            ▼
26//!                          ┌─────┐
27//!                          │.yol │
28//!                          │Files│
29//!                          └─────┘
30//! ```
31//!
32//! To support integrate Yoleck, a game needs to:
33//!
34//! * Define the component structs, and make sure they implement:
35//!   ```text
36//!   #[derive(Default, Clone, PartialEq, Component, Serialize, Deserialize, YoleckComponent)]
37//!   ```
38//! * For each entity type that can be created in the level editor, use
39//!   [`add_yoleck_entity_type`](YoleckExtForApp::add_yoleck_entity_type) to add a
40//!   [`YoleckEntityType`]. Use [`YoleckEntityType::with`] to register the
41//!   [`YoleckComponent`](crate::specs_registration::YoleckComponent)s for that entity type.
42//! * Register edit systems with
43//!   [`add_yoleck_edit_system`](YoleckExtForApp::add_yoleck_edit_system).
44//! * Register populate systems on [`YoleckSchedule::Populate`]
45//! * If the application starts in editor mode:
46//!   * Add the `EguiPlugin` plugin.
47//!   * Add the [`YoleckPluginForEditor`] plugin.
48//!   * Use [`YoleckSyncWithEditorState`](crate::editor::YoleckSyncWithEditorState) to synchronize
49//!     the game's state with the [`YoleckEditorState`] (optional but highly recommended)
50//! * If the application starts in game mode:
51//!   * Add the [`YoleckPluginForGame`] plugin.
52//!   * Use the [`YoleckLevelIndex`] asset to determine the list of available levels (optional)
53//!   * Spawn an entity with the [`YoleckLoadLevel`](entity_management::YoleckLoadLevel) component
54//!     to load the level. Note that the level can be unloaded by despawning that entity or by
55//!     removing the [`YoleckKeepLevel`] component that will automatically be added to it.
56//!
57//! To support picking and moving entities in the viewport with the mouse, check out the
58//! [`vpeol_2d`] and [`vpeol_3d`] modules. After adding the appropriate feature flag
59//! (`vpeol_2d`/`vpeol_3d`), import their types from
60//! [`bevy_yoleck::vpeol::prelude::*`](crate::vpeol::prelude).
61//!
62//! # Example
63//!
64//! ```no_run
65//! use bevy::prelude::*;
66//! use bevy_yoleck::bevy_egui::EguiPlugin;
67//! use bevy_yoleck::prelude::*;
68//! use serde::{Deserialize, Serialize};
69//! # use bevy_yoleck::egui;
70//!
71//! fn main() {
72//!     let is_editor = std::env::args().any(|arg| arg == "--editor");
73//!
74//!     let mut app = App::new();
75//!     app.add_plugins(DefaultPlugins);
76//!     if is_editor {
77//!         // Doesn't matter in this example, but a proper game would have systems that can work
78//!         // on the entity in `GameState::Game`, so while the level is edited we want to be in
79//!         // `GameState::Editor` - which can be treated as a pause state. When the editor wants
80//!         // to playtest the level we want to move to `GameState::Game` so that they can play it.
81//!         app.add_plugins(EguiPlugin::default());
82//!         app.add_plugins(YoleckSyncWithEditorState {
83//!             when_editor: GameState::Editor,
84//!             when_game: GameState::Game,
85//!         });
86//!         app.add_plugins(YoleckPluginForEditor);
87//!     } else {
88//!         app.add_plugins(YoleckPluginForGame);
89//!         app.init_state::<GameState>();
90//!         // In editor mode Yoleck takes care of level loading. In game mode the game needs to
91//!         // tell yoleck which levels to load and when.
92//!         app.add_systems(Update, load_first_level.run_if(in_state(GameState::Loading)));
93//!     }
94//!     app.add_systems(Startup, setup_camera);
95//!
96//!     app.add_yoleck_entity_type({
97//!         YoleckEntityType::new("Rectangle")
98//!             .with::<Rectangle>()
99//!     });
100//!     app.add_yoleck_edit_system(edit_rectangle);
101//!     app.add_systems(YoleckSchedule::Populate, populate_rectangle);
102//!
103//!     app.run();
104//! }
105//!
106//! #[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)]
107//! enum GameState {
108//!     #[default]
109//!     Loading,
110//!     Game,
111//!     Editor,
112//! }
113//!
114//! fn setup_camera(mut commands: Commands) {
115//!     commands.spawn(Camera2d::default());
116//! }
117//!
118//! #[derive(Clone, PartialEq, Serialize, Deserialize, Component, YoleckComponent)]
119//! struct Rectangle {
120//!     width: f32,
121//!     height: f32,
122//! }
123//!
124//! impl Default for Rectangle {
125//!     fn default() -> Self {
126//!         Self {
127//!             width: 50.0,
128//!             height: 50.0,
129//!         }
130//!     }
131//! }
132//!
133//! fn populate_rectangle(mut populate: YoleckPopulate<&Rectangle>) {
134//!     populate.populate(|_ctx, mut cmd, rectangle| {
135//!         cmd.insert(Sprite {
136//!             color: bevy::color::palettes::css::RED.into(),
137//!             custom_size: Some(Vec2::new(rectangle.width, rectangle.height)),
138//!             ..Default::default()
139//!         });
140//!     });
141//! }
142//!
143//! fn edit_rectangle(mut ui: ResMut<YoleckUi>, mut edit: YoleckEdit<&mut Rectangle>) {
144//!     let Ok(mut rectangle) = edit.single_mut() else { return };
145//!     ui.add(egui::Slider::new(&mut rectangle.width, 50.0..=500.0).prefix("Width: "));
146//!     ui.add(egui::Slider::new(&mut rectangle.height, 50.0..=500.0).prefix("Height: "));
147//! }
148//!
149//! fn load_first_level(
150//!     mut level_index_handle: Local<Option<Handle<YoleckLevelIndex>>>,
151//!     asset_server: Res<AssetServer>,
152//!     level_index_assets: Res<Assets<YoleckLevelIndex>>,
153//!     mut commands: Commands,
154//!     mut game_state: ResMut<NextState<GameState>>,
155//! ) {
156//!     // Keep the handle in local resource, so that Bevy will not unload the level index asset
157//!     // between frames.
158//!     let level_index_handle = level_index_handle
159//!         .get_or_insert_with(|| asset_server.load("levels/index.yoli"))
160//!         .clone();
161//!     let Some(level_index) = level_index_assets.get(&level_index_handle) else {
162//!         // During the first invocation of this system, the level index asset is not going to be
163//!         // loaded just yet. Since this system is going to run on every frame during the Loading
164//!         // state, it just has to keep trying until it starts in a frame where it is loaded.
165//!         return;
166//!     };
167//!     // A proper game would have a proper level progression system, but here we are just
168//!     // taking the first level and loading it.
169//!     let level_handle: Handle<YoleckRawLevel> =
170//!         asset_server.load(&format!("levels/{}", level_index[0].filename));
171//!     commands.spawn(YoleckLoadLevel(level_handle));
172//!     game_state.set(GameState::Game);
173//! }
174//! ```
175
176pub mod auto_edit;
177mod console;
178mod editing;
179mod editor;
180mod editor_panels;
181mod editor_window;
182mod entity_management;
183pub mod entity_ref;
184mod entity_upgrading;
185mod entity_uuid;
186mod errors;
187pub mod exclusive_systems;
188pub mod knobs;
189mod level_files_manager;
190pub mod level_files_upgrading;
191mod level_index;
192mod picking_helpers;
193mod populating;
194mod specs_registration;
195mod util;
196#[cfg(feature = "vpeol")]
197pub mod vpeol;
198#[cfg(feature = "vpeol_2d")]
199pub mod vpeol_2d;
200#[cfg(feature = "vpeol_3d")]
201pub mod vpeol_3d;
202
203use std::any::{Any, TypeId};
204use std::path::Path;
205use std::sync::Arc;
206
207use bevy::ecs::schedule::ScheduleLabel;
208use bevy::ecs::system::{EntityCommands, SystemId};
209use bevy::platform::collections::HashMap;
210use bevy::prelude::*;
211use bevy_egui::EguiPrimaryContextPass;
212
213pub mod prelude {
214    pub use crate::auto_edit::{YoleckAutoEdit, YoleckAutoEditExt};
215    pub use crate::editing::{YoleckEdit, YoleckUi};
216    pub use crate::editor::{YoleckEditorState, YoleckPassedData, YoleckSyncWithEditorState};
217    pub use crate::entity_management::{YoleckKeepLevel, YoleckLoadLevel, YoleckRawLevel};
218    pub use crate::entity_ref::{YoleckEntityRef, YoleckEntityRefAccessor};
219    pub use crate::entity_upgrading::YoleckEntityUpgradingPlugin;
220    pub use crate::entity_uuid::{YoleckEntityUuid, YoleckUuidRegistry};
221    pub use crate::knobs::YoleckKnobs;
222    pub use crate::level_index::{YoleckLevelIndex, YoleckLevelIndexEntry};
223    pub use crate::populating::{YoleckMarking, YoleckPopulate};
224    pub use crate::specs_registration::{YoleckComponent, YoleckEntityType};
225    pub use crate::{
226        YoleckBelongsToLevel, YoleckExtForApp, YoleckLevelInEditor, YoleckLevelInPlaytest,
227        YoleckLevelJustLoaded, YoleckPluginForEditor, YoleckPluginForGame, YoleckSchedule,
228    };
229    pub use bevy_yoleck_macros::{YoleckAutoEdit, YoleckComponent};
230}
231
232pub use self::console::{YoleckConsoleLogHistory, YoleckConsoleState, console_layer_factory};
233pub use self::editing::YoleckEditMarker;
234pub use self::editor::YoleckDirective;
235pub use self::editor::YoleckEditorEvent;
236use self::editor::YoleckEditorState;
237pub use self::editor_panels::{
238    YoleckEditorBottomPanelSections, YoleckEditorBottomPanelTab, YoleckEditorLeftPanelSections,
239    YoleckEditorRightPanelSections, YoleckEditorTopPanelSections, YoleckPanelUi,
240};
241pub use self::editor_window::YoleckEditorViewportRect;
242pub use self::picking_helpers::*;
243
244use self::entity_management::{EntitiesToPopulate, YoleckRawLevel};
245use self::entity_upgrading::YoleckEntityUpgrading;
246use self::exclusive_systems::YoleckExclusiveSystemsPlugin;
247use self::knobs::YoleckKnobsCache;
248pub use self::level_files_manager::YoleckEditorLevelsDirectoryPath;
249pub use self::level_index::YoleckEditableLevels;
250use self::level_index::YoleckLevelIndex;
251pub use self::populating::{YoleckPopulateContext, YoleckSystemMarker};
252use self::prelude::{YoleckKeepLevel, YoleckUuidRegistry};
253use self::specs_registration::{YoleckComponentHandler, YoleckEntityType};
254use self::util::EditSpecificResources;
255pub use bevy_egui;
256pub use bevy_egui::egui;
257
258struct YoleckPluginBase;
259pub struct YoleckPluginForGame;
260pub struct YoleckPluginForEditor;
261
262#[derive(Debug, Clone, PartialEq, Eq, Hash, SystemSet)]
263enum YoleckSystems {
264    ProcessRawEntities,
265    RunPopulateSchedule,
266}
267
268#[derive(Debug, Clone, PartialEq, Eq, Hash, SystemSet)]
269pub(crate) struct YoleckRunEditSystems;
270
271impl Plugin for YoleckPluginBase {
272    fn build(&self, app: &mut App) {
273        app.init_resource::<YoleckEntityConstructionSpecs>();
274        app.insert_resource(YoleckUuidRegistry(Default::default()));
275        app.register_asset_loader(entity_management::YoleckLevelAssetLoader);
276        app.init_asset::<YoleckRawLevel>();
277        app.register_asset_loader(level_index::YoleckLevelIndexLoader);
278        app.init_asset::<YoleckLevelIndex>();
279
280        app.configure_sets(
281            Update,
282            (
283                YoleckSystems::ProcessRawEntities,
284                YoleckSystems::RunPopulateSchedule,
285            )
286                .chain(),
287        );
288
289        app.add_systems(
290            Update,
291            (
292                entity_management::yoleck_process_raw_entries,
293                ApplyDeferred,
294                (
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                    ),
302                    entity_management::yoleck_remove_just_loaded_marker_from_levels,
303                    ApplyDeferred,
304                )
305                    .chain()
306                    .run_if(
307                        |freshly_loaded_level_entities: Query<(), With<YoleckLevelJustLoaded>>| {
308                            !freshly_loaded_level_entities.is_empty()
309                        },
310                    ),
311            )
312                .chain()
313                .in_set(YoleckSystems::ProcessRawEntities),
314        );
315        app.insert_resource(EntitiesToPopulate(Default::default()));
316        app.add_systems(
317            Update,
318            (
319                entity_management::yoleck_prepare_populate_schedule,
320                entity_management::yoleck_run_populate_schedule.run_if(
321                    |entities_to_populate: Res<EntitiesToPopulate>| {
322                        !entities_to_populate.0.is_empty()
323                    },
324                ),
325            )
326                .chain()
327                .in_set(YoleckSystems::RunPopulateSchedule),
328        );
329        app.add_systems(
330            Update,
331            ((
332                entity_management::process_unloading_command,
333                entity_management::process_loading_command,
334                ApplyDeferred,
335            )
336                .chain()
337                .before(YoleckSystems::ProcessRawEntities),),
338        );
339        app.add_schedule(Schedule::new(YoleckSchedule::Populate));
340        app.add_schedule(Schedule::new(YoleckInternalSchedule::PostLoadResolutions));
341        app.add_schedule(Schedule::new(YoleckSchedule::LevelLoaded));
342        app.add_schedule(Schedule::new(YoleckSchedule::OverrideCommonComponents));
343    }
344}
345
346impl Plugin for YoleckPluginForGame {
347    fn build(&self, app: &mut App) {
348        app.init_state::<YoleckEditorState>();
349        app.add_systems(
350            Startup,
351            |mut state: ResMut<NextState<YoleckEditorState>>| {
352                state.set(YoleckEditorState::GameActive);
353            },
354        );
355        app.add_plugins(YoleckPluginBase);
356    }
357}
358
359impl Plugin for YoleckPluginForEditor {
360    fn build(&self, app: &mut App) {
361        app.init_state::<YoleckEditorState>();
362        app.add_message::<YoleckEditorEvent>();
363        app.add_plugins(YoleckPluginBase);
364        app.add_plugins(YoleckExclusiveSystemsPlugin);
365        app.init_resource::<YoleckEditSystems>();
366        app.insert_resource(YoleckKnobsCache::default());
367        let level_being_edited = app
368            .world_mut()
369            .spawn((YoleckLevelInEditor, YoleckKeepLevel))
370            .id();
371        app.insert_resource(YoleckState {
372            level_being_edited,
373            level_needs_saving: false,
374        });
375        app.insert_resource(YoleckEditorLevelsDirectoryPath(
376            Path::new(".").join("assets").join("levels"),
377        ));
378        app.init_resource::<YoleckEditorLeftPanelSections>();
379        app.init_resource::<YoleckEditorRightPanelSections>();
380        app.init_resource::<YoleckEditorTopPanelSections>();
381        app.init_resource::<YoleckEditorBottomPanelSections>();
382        app.init_resource::<YoleckEditorViewportRect>();
383        app.init_resource::<YoleckConsoleState>();
384        app.init_resource::<YoleckConsoleLogHistory>();
385        app.init_resource::<YoleckPlaytestLevel>();
386        app.insert_resource(EditSpecificResources::new().with(YoleckEditableLevels {
387            levels: Default::default(),
388        }));
389        app.add_message::<YoleckDirective>();
390        app.configure_sets(
391            Update,
392            YoleckRunEditSystems.after(YoleckSystems::ProcessRawEntities),
393        );
394        app.add_systems(
395            EguiPrimaryContextPass,
396            editor_window::yoleck_editor_window.in_set(YoleckRunEditSystems),
397        );
398
399        app.add_schedule(Schedule::new(
400            YoleckInternalSchedule::UpdateManagedDataFromComponents,
401        ));
402    }
403}
404
405pub trait YoleckExtForApp {
406    /// Add a type of entity that can be edited in Yoleck's level editor.
407    ///
408    /// ```no_run
409    /// # use bevy::prelude::*;
410    /// # use bevy_yoleck::prelude::*;
411    /// # use serde::{Deserialize, Serialize};
412    /// # #[derive(Default, Clone, PartialEq, Serialize, Deserialize, Component, YoleckComponent)]
413    /// # struct Component1;
414    /// # type Component2 = Component1;
415    /// # type Component3 = Component1;
416    /// # let mut app = App::new();
417    /// app.add_yoleck_entity_type({
418    ///     YoleckEntityType::new("MyEntityType")
419    ///         .with::<Component1>()
420    ///         .with::<Component2>()
421    ///         .with::<Component3>()
422    /// });
423    /// ```
424    fn add_yoleck_entity_type(&mut self, entity_type: YoleckEntityType);
425
426    /// Add a system for editing Yoleck components in the level editor.
427    ///
428    /// ```no_run
429    /// # use bevy::prelude::*;
430    /// # use bevy_yoleck::prelude::*;
431    /// # use serde::{Deserialize, Serialize};
432    /// # #[derive(Default, Clone, PartialEq, Serialize, Deserialize, Component, YoleckComponent)]
433    /// # struct Component1;
434    /// # let mut app = App::new();
435    ///
436    /// app.add_yoleck_edit_system(edit_component1);
437    ///
438    /// fn edit_component1(mut ui: ResMut<YoleckUi>, mut edit: YoleckEdit<&mut Component1>) {
439    ///     let Ok(component1) = edit.single_mut() else { return };
440    ///     // Edit `component1` with the `ui`
441    /// }
442    /// ```
443    ///
444    /// See [`YoleckEdit`](crate::editing::YoleckEdit).
445    fn add_yoleck_edit_system<P>(&mut self, system: impl 'static + IntoSystem<(), (), P>);
446
447    /// Register a function that upgrades entities from a previous version of the app format.
448    ///
449    /// This should only be called _after_ adding
450    /// [`YoleckEntityUpgradingPlugin`](crate::entity_upgrading::YoleckEntityUpgradingPlugin). See
451    /// that plugin's docs for more info.
452    fn add_yoleck_entity_upgrade(
453        &mut self,
454        to_version: usize,
455        upgrade_dlg: impl 'static
456        + Send
457        + Sync
458        + Fn(&str, &mut serde_json::Map<String, serde_json::Value>),
459    );
460
461    /// Register a function that upgrades entities of a specific type from a previous version of
462    /// the app format.
463    fn add_yoleck_entity_upgrade_for(
464        &mut self,
465        to_version: usize,
466        for_type_name: impl ToString,
467        upgrade_dlg: impl 'static + Send + Sync + Fn(&mut serde_json::Map<String, serde_json::Value>),
468    ) {
469        let for_type_name = for_type_name.to_string();
470        self.add_yoleck_entity_upgrade(to_version, move |type_name, data| {
471            if type_name == for_type_name {
472                upgrade_dlg(data);
473            }
474        });
475    }
476}
477
478impl YoleckExtForApp for App {
479    fn add_yoleck_entity_type(&mut self, entity_type: YoleckEntityType) {
480        let construction_specs = self
481            .world_mut()
482            .get_resource_or_insert_with(YoleckEntityConstructionSpecs::default);
483
484        let mut component_type_ids = Vec::with_capacity(entity_type.components.len());
485        let mut component_handlers_to_register = Vec::new();
486        for handler in entity_type.components.into_iter() {
487            component_type_ids.push(handler.component_type());
488            if !construction_specs
489                .component_handlers
490                .contains_key(&handler.component_type())
491            {
492                component_handlers_to_register.push(handler);
493            }
494        }
495
496        for handler in component_handlers_to_register.iter() {
497            handler.build_in_bevy_app(self);
498        }
499
500        let new_entry = YoleckEntityTypeInfo {
501            name: entity_type.name.clone(),
502            components: component_type_ids,
503            on_init: entity_type.on_init,
504            has_uuid: entity_type.has_uuid,
505        };
506
507        let mut construction_specs = self
508            .world_mut()
509            .get_resource_mut::<YoleckEntityConstructionSpecs>()
510            .expect("YoleckEntityConstructionSpecs was inserted earlier in this function");
511
512        let new_index = construction_specs.entity_types.len();
513        construction_specs
514            .entity_types_index
515            .insert(entity_type.name, new_index);
516        construction_specs.entity_types.push(new_entry);
517        for handler in component_handlers_to_register {
518            // Can handlers can register systems? If so, this needs to be broken into two phases...
519            construction_specs
520                .component_handlers
521                .insert(handler.component_type(), handler);
522        }
523    }
524
525    fn add_yoleck_edit_system<P>(&mut self, system: impl 'static + IntoSystem<(), (), P>) {
526        let system_id = self.world_mut().register_system(system);
527        let mut edit_systems = self
528            .world_mut()
529            .get_resource_or_insert_with(YoleckEditSystems::default);
530        edit_systems.edit_systems.push(system_id);
531    }
532
533    fn add_yoleck_entity_upgrade(
534        &mut self,
535        to_version: usize,
536        upgrade_dlg: impl 'static
537        + Send
538        + Sync
539        + Fn(&str, &mut serde_json::Map<String, serde_json::Value>),
540    ) {
541        let mut entity_upgrading = self.world_mut().get_resource_mut::<YoleckEntityUpgrading>()
542            .expect("add_yoleck_entity_upgrade can only be called after the YoleckEntityUpgrading plugin was added");
543        if entity_upgrading.app_format_version < to_version {
544            panic!(
545                "Cannot create an upgrade system to version {} when YoleckEntityUpgrading set the version to {}",
546                to_version, entity_upgrading.app_format_version
547            );
548        }
549        entity_upgrading
550            .upgrade_functions
551            .entry(to_version)
552            .or_default()
553            .push(Box::new(upgrade_dlg));
554    }
555}
556
557type BoxedArc = Arc<dyn Send + Sync + Any>;
558type BoxedAny = Box<dyn Send + Sync + Any>;
559
560/// A component that describes how Yoleck manages an entity under its control.
561#[derive(Component)]
562pub struct YoleckManaged {
563    /// A name to display near the entity in the entities list.
564    ///
565    /// This is for level editors' convenience only - it will not be used in the games.
566    pub name: String,
567
568    /// The type of the Yoleck entity, as registered with
569    /// [`add_yoleck_entity_type`](YoleckExtForApp::add_yoleck_entity_type).
570    ///
571    /// This defines the Yoleck components that can be edited for the entity.
572    pub type_name: String,
573
574    lifecycle_status: YoleckEntityLifecycleStatus,
575
576    pub(crate) components_data: HashMap<TypeId, BoxedAny>,
577}
578
579/// A marker for entities that belongs to the Yoleck level and should be despawned with it.
580///
581/// Yoleck already adds this automatically to entities created from the editor. The game itself
582/// should add this to entities created during gameplay, like bullets or spawned enemeis, so that
583/// they'll be despawned when a playtest is finished or restarted.
584///
585/// When removing a [`YoleckKeepLevel`] from entity (or removing the entire entity), Yoleck will
586/// automatically despawn all the entities that have this component and point to that level.
587///
588/// There is no need to add this to child entities of entities that already has this marker,
589/// because Bevy will already despawn them when despawning their parent.
590#[derive(Component, Debug, Clone)]
591pub struct YoleckBelongsToLevel {
592    /// The entity which was used with [`YoleckLoadLevel`](entity_management::YoleckLoadLevel) to
593    /// load the level that this entity belongs to.
594    pub level: Entity,
595}
596
597pub enum YoleckEntityLifecycleStatus {
598    Synchronized,
599    JustCreated,
600    JustChanged,
601}
602
603#[derive(Default, Resource)]
604struct YoleckEditSystems {
605    edit_systems: Vec<SystemId>,
606}
607
608impl YoleckEditSystems {
609    pub(crate) fn run_systems(&mut self, world: &mut World) {
610        for system_id in self.edit_systems.iter() {
611            world
612                .run_system(*system_id)
613                .expect("edit systems handled by Yoleck - system should been properly handled");
614        }
615    }
616}
617
618pub(crate) struct YoleckEntityTypeInfo {
619    pub name: String,
620    pub components: Vec<TypeId>,
621    #[allow(clippy::type_complexity)]
622    pub(crate) on_init:
623        Vec<Box<dyn 'static + Sync + Send + Fn(YoleckEditorState, &mut EntityCommands)>>,
624    pub has_uuid: bool,
625}
626
627#[derive(Default, Resource)]
628pub(crate) struct YoleckEntityConstructionSpecs {
629    pub entity_types: Vec<YoleckEntityTypeInfo>,
630    pub entity_types_index: HashMap<String, usize>,
631    pub component_handlers: HashMap<TypeId, Box<dyn YoleckComponentHandler>>,
632}
633
634impl YoleckEntityConstructionSpecs {
635    pub fn get_entity_type_info(&self, entity_type: &str) -> Option<&YoleckEntityTypeInfo> {
636        Some(&self.entity_types[*self.entity_types_index.get(entity_type)?])
637    }
638}
639
640/// Fields of the Yoleck editor.
641#[derive(Resource)]
642pub(crate) struct YoleckState {
643    level_being_edited: Entity,
644    level_needs_saving: bool,
645}
646
647/// The level currently being playtested, if any.
648#[derive(Default, Resource)]
649pub struct YoleckPlaytestLevel(pub Option<YoleckRawLevel>);
650
651#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash)]
652pub(crate) enum YoleckInternalSchedule {
653    UpdateManagedDataFromComponents,
654    /// Before [`LevelLoaded`][YoleckSchedule::LevelLoaded] to resolve things like entity
655    /// references.
656    PostLoadResolutions,
657}
658
659/// Schedules for user code to do the actual entity/level population after Yoleck spawns the level
660/// "skeleton".
661#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash)]
662pub enum YoleckSchedule {
663    /// This is where user defined populate systems should reside.
664    ///
665    /// Note that populate systems, rather than directly trying to query the entities to be
666    /// populated, should use [`YoleckPopulate`](crate::prelude::YoleckPopulate):
667    ///
668    /// ```no_run
669    /// # use bevy::prelude::*;
670    /// # use bevy_yoleck::prelude::*;
671    /// # use serde::{Deserialize, Serialize};
672    /// # #[derive(Default, Clone, PartialEq, Serialize, Deserialize, Component, YoleckComponent)]
673    /// # struct Component1;
674    /// # let mut app = App::new();
675    ///
676    /// app.add_systems(YoleckSchedule::Populate, populate_component1);
677    ///
678    /// fn populate_component1(mut populate: YoleckPopulate<&Component1>) {
679    ///     populate.populate(|_ctx, mut cmd, component1| {
680    ///         // Add Bevy components derived from `component1` to `cmd`.
681    ///     });
682    /// }
683    /// ```
684    Populate,
685    /// Right after all the level entities are loaded, but before any populate systems manage to
686    /// run.
687    LevelLoaded,
688    /// Since many bundles add their own transform and visibility components, systems that override
689    /// them explicitly need to go here.
690    OverrideCommonComponents,
691}
692
693/// Automatically added to level entities that are being edited in the level editor.
694#[derive(Component)]
695pub struct YoleckLevelInEditor;
696
697/// Automatically added to level entities that are being play-tested in the level editor.
698///
699/// Note that this only gets added to the levels that are launched from the editor UI. If game
700/// systems load new levels during the play-test, this component will not be added to them.
701#[derive(Component)]
702pub struct YoleckLevelInPlaytest;
703
704/// During the [`YoleckSchedule::LevelLoaded`] schedule, this component marks the level entities
705/// that were just loaded and triggered that schedule.
706///
707/// Note that this component will be removed after that schedule finishes running - it should not
708/// be relied on in systems outside that schedule.
709#[derive(Component)]
710pub struct YoleckLevelJustLoaded;