Skip to main content

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