bevy_yoleck/
editor.rs

1use std::any::TypeId;
2use std::sync::Arc;
3
4use bevy::ecs::system::SystemState;
5use bevy::platform::collections::{HashMap, HashSet};
6use bevy::prelude::*;
7use bevy::state::state::FreelyMutableState;
8use bevy_egui::egui;
9
10use crate::entity_management::{YoleckEntryHeader, YoleckRawEntry};
11use crate::exclusive_systems::{
12    YoleckActiveExclusiveSystem, YoleckEntityCreationExclusiveSystems,
13    YoleckExclusiveSystemDirective, YoleckExclusiveSystemsQueue,
14};
15use crate::knobs::YoleckKnobsCache;
16use crate::prelude::{YoleckComponent, YoleckUi};
17use crate::{
18    BoxedArc, YoleckBelongsToLevel, YoleckEditMarker, YoleckEditSystems,
19    YoleckEntityConstructionSpecs, YoleckInternalSchedule, YoleckManaged, YoleckState,
20};
21
22/// Whether or not the Yoleck editor is active.
23#[derive(States, Default, Debug, PartialEq, Eq, Hash, Clone, Copy)]
24pub enum YoleckEditorState {
25    /// Editor mode. The editor is active and can be used to edit entities.
26    #[default]
27    EditorActive,
28    /// Game mode. Either the actual game or playtest from the editor mode.
29    GameActive,
30}
31
32/// Sync the game's state back and forth when the level editor enters and exits playtest mode.
33///
34/// Add this as a plugin. When using it, there is no need to initialize the state with `add_state`
35/// because `YoleckSyncWithEditorState` will initialize it and set its initial value to
36/// `when_editor`. This means that the state's default value should be it's initial value for
37/// non-editor mode (which is not necessarily `when_game`, because the game may start in a menu
38/// state or a loading state)
39///
40/// ```no_run
41/// # use bevy::prelude::*;
42/// # use bevy_yoleck::prelude::*;
43/// # use bevy_yoleck::bevy_egui::EguiPlugin;
44/// #[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)]
45/// enum GameState {
46///     #[default]
47///     Loading,
48///     Game,
49///     Editor,
50/// }
51///
52/// # let mut app = App::new();
53/// # let executable_started_in_editor_mode = true;
54/// if executable_started_in_editor_mode {
55///     // These two plugins are needed for editor mode:
56///     app.add_plugins(EguiPlugin::default());
57///     app.add_plugins(YoleckPluginForEditor);
58///     app.add_plugins(YoleckSyncWithEditorState {
59///         when_editor: GameState::Editor,
60///         when_game: GameState::Game,
61///     });
62/// } else {
63///     // This plugin is needed for game mode:
64///     app.add_plugins(YoleckPluginForGame);
65///
66///     app.init_state::<GameState>();
67/// }
68pub struct YoleckSyncWithEditorState<T>
69where
70    T: 'static
71        + States
72        + FreelyMutableState
73        + Sync
74        + Send
75        + std::fmt::Debug
76        + Clone
77        + std::cmp::Eq
78        + std::hash::Hash,
79{
80    pub when_editor: T,
81    pub when_game: T,
82}
83
84impl<T> Plugin for YoleckSyncWithEditorState<T>
85where
86    T: 'static
87        + States
88        + FreelyMutableState
89        + Sync
90        + Send
91        + std::fmt::Debug
92        + Clone
93        + std::cmp::Eq
94        + std::hash::Hash,
95{
96    fn build(&self, app: &mut App) {
97        app.insert_state(self.when_editor.clone());
98        let when_editor = self.when_editor.clone();
99        let when_game = self.when_game.clone();
100        app.add_systems(
101            Update,
102            move |editor_state: Res<State<YoleckEditorState>>,
103                  mut game_state: ResMut<NextState<T>>| {
104                game_state.set(match editor_state.get() {
105                    YoleckEditorState::EditorActive => when_editor.clone(),
106                    YoleckEditorState::GameActive => when_game.clone(),
107                });
108            },
109        );
110    }
111}
112
113/// Events emitted by the Yoleck editor.
114///
115/// Modules that provide editing overlays over the viewport (like [vpeol](crate::vpeol)) can
116/// use these events to update their status to match with the editor.
117#[derive(Debug, Message)]
118pub enum YoleckEditorEvent {
119    EntitySelected(Entity),
120    EntityDeselected(Entity),
121    EditedEntityPopulated(Entity),
122}
123
124enum YoleckDirectiveInner {
125    SetSelected(Option<Entity>),
126    ChangeSelectedStatus {
127        entity: Entity,
128        force_to: Option<bool>,
129    },
130    PassToEntity(Entity, TypeId, BoxedArc),
131    SpawnEntity {
132        level: Entity,
133        type_name: String,
134        data: serde_json::Value,
135        select_created_entity: bool,
136        #[allow(clippy::type_complexity)]
137        modify_exclusive_systems:
138            Option<Box<dyn Sync + Send + Fn(&mut YoleckExclusiveSystemsQueue)>>,
139    },
140}
141
142/// Event that can be sent to control Yoleck's editor.
143#[derive(Message)]
144pub struct YoleckDirective(YoleckDirectiveInner);
145
146impl YoleckDirective {
147    /// Pass data from an external system (usually a [ViewPort Editing OverLay](crate::vpeol)) to an entity.
148    ///
149    /// This data can be received using the [`YoleckPassedData`] resource. If the data is
150    /// passed to a knob, it can also be received using the knob handle's
151    /// [`get_passed_data`](crate::knobs::YoleckKnobHandle::get_passed_data) method.
152    pub fn pass_to_entity<T: 'static + Send + Sync>(entity: Entity, data: T) -> Self {
153        Self(YoleckDirectiveInner::PassToEntity(
154            entity,
155            TypeId::of::<T>(),
156            Arc::new(data),
157        ))
158    }
159
160    /// Set the entity selected in the Yoleck editor.
161    pub fn set_selected(entity: Option<Entity>) -> Self {
162        Self(YoleckDirectiveInner::SetSelected(entity))
163    }
164
165    /// Set the entity selected in the Yoleck editor.
166    pub fn toggle_selected(entity: Entity) -> Self {
167        Self(YoleckDirectiveInner::ChangeSelectedStatus {
168            entity,
169            force_to: None,
170        })
171    }
172
173    /// Spawn a new entity with pre-populated data.
174    ///
175    /// ```no_run
176    /// # use serde::{Deserialize, Serialize};
177    /// # use bevy::prelude::*;
178    /// # use bevy_yoleck::prelude::*;
179    /// # use bevy_yoleck::YoleckDirective;
180    /// # use bevy_yoleck::vpeol_2d::Vpeol2dPosition;
181    /// # #[derive(Default, Clone, PartialEq, Serialize, Deserialize, Component, YoleckComponent)]
182    /// # struct Example;
183    /// fn duplicate_example(
184    ///     mut ui: ResMut<YoleckUi>,
185    ///     mut edit: YoleckEdit<(&YoleckBelongsToLevel, &Vpeol2dPosition), With<Example>>,
186    ///     mut writer: MessageWriter<YoleckDirective>,
187    /// ) {
188    ///     let Ok((belongs_to_level, position)) = edit.single() else { return };
189    ///     if ui.button("Duplicate").clicked() {
190    ///         writer.write(
191    ///             YoleckDirective::spawn_entity(
192    ///                 belongs_to_level.level,
193    ///                 "Example",
194    ///                 // Automatically select the newly created entity:
195    ///                 true,
196    ///             )
197    ///             // Create the new example entity 100 units below the current one:
198    ///             .with(Vpeol2dPosition(position.0 - 100.0 * Vec2::Y))
199    ///             .into(),
200    ///         );
201    ///     }
202    /// }
203    /// ```
204    pub fn spawn_entity(
205        level: Entity,
206        type_name: impl ToString,
207        select_created_entity: bool,
208    ) -> SpawnEntityBuilder {
209        SpawnEntityBuilder {
210            level,
211            type_name: type_name.to_string(),
212            select_created_entity,
213            data: Default::default(),
214            modify_exclusive_systems: None,
215        }
216    }
217}
218
219pub struct SpawnEntityBuilder {
220    level: Entity,
221    type_name: String,
222    select_created_entity: bool,
223    data: HashMap<&'static str, serde_json::Value>,
224    #[allow(clippy::type_complexity)]
225    modify_exclusive_systems: Option<Box<dyn Sync + Send + Fn(&mut YoleckExclusiveSystemsQueue)>>,
226}
227
228impl SpawnEntityBuilder {
229    /// Override a component of the spawned entity.
230    pub fn with<T: YoleckComponent>(mut self, component: T) -> Self {
231        self.data.insert(
232            T::KEY,
233            serde_json::to_value(component).expect("should always work"),
234        );
235        self
236    }
237
238    /// Change the exclusive systems that will be running the entity is spawned.
239    pub fn modify_exclusive_systems(
240        mut self,
241        dlg: impl 'static + Sync + Send + Fn(&mut YoleckExclusiveSystemsQueue),
242    ) -> Self {
243        self.modify_exclusive_systems = Some(Box::new(dlg));
244        self
245    }
246}
247
248impl From<SpawnEntityBuilder> for YoleckDirective {
249    fn from(value: SpawnEntityBuilder) -> Self {
250        YoleckDirective(YoleckDirectiveInner::SpawnEntity {
251            level: value.level,
252            type_name: value.type_name,
253            data: serde_json::to_value(value.data).expect("should always work"),
254            select_created_entity: value.select_created_entity,
255            modify_exclusive_systems: value.modify_exclusive_systems,
256        })
257    }
258}
259
260#[derive(Resource)]
261pub struct YoleckPassedData(pub(crate) HashMap<Entity, HashMap<TypeId, BoxedArc>>);
262
263impl YoleckPassedData {
264    /// Get data sent to an entity from external systems (usually from (usually a [ViewPort Editing
265    /// OverLay](crate::vpeol))
266    ///
267    /// The data is sent using [a directive event](crate::YoleckDirective::pass_to_entity).
268    ///
269    /// ```no_run
270    /// # use bevy::prelude::*;
271    /// # use bevy_yoleck::prelude::*;;
272    /// # #[derive(Component)]
273    /// # struct Example {
274    /// #     message: String,
275    /// # }
276    /// fn edit_example(
277    ///     mut edit: YoleckEdit<(Entity, &mut Example)>,
278    ///     passed_data: Res<YoleckPassedData>,
279    /// ) {
280    ///     let Ok((entity, mut example)) = edit.single_mut() else { return };
281    ///     if let Some(message) = passed_data.get::<String>(entity) {
282    ///         example.message = message.clone();
283    ///     }
284    /// }
285    /// ```
286    pub fn get<T: 'static>(&self, entity: Entity) -> Option<&T> {
287        Some(
288            self.0
289                .get(&entity)?
290                .get(&TypeId::of::<T>())?
291                .downcast_ref()
292                .expect("Passed data TypeId must be correct"),
293        )
294    }
295}
296
297fn format_caption(entity: Entity, yoleck_managed: &YoleckManaged) -> String {
298    if yoleck_managed.name.is_empty() {
299        format!("{} {:?}", yoleck_managed.type_name, entity)
300    } else {
301        format!(
302            "{} ({} {:?})",
303            yoleck_managed.name, yoleck_managed.type_name, entity
304        )
305    }
306}
307
308/// The UI part for creating new entities. See [`YoleckEditorSections`](crate::YoleckEditorSections).
309pub fn new_entity_section(world: &mut World) -> impl FnMut(&mut World, &mut egui::Ui) -> Result {
310    let mut system_state = SystemState::<(
311        Res<YoleckEntityConstructionSpecs>,
312        Res<YoleckState>,
313        Res<State<YoleckEditorState>>,
314        MessageWriter<YoleckDirective>,
315        Option<Res<YoleckActiveExclusiveSystem>>,
316    )>::new(world);
317
318    move |world, ui| {
319        let (construction_specs, yoleck, editor_state, mut writer, active_exclusive_system) =
320            system_state.get_mut(world);
321        if active_exclusive_system.is_some() {
322            return Ok(());
323        }
324
325        if !matches!(editor_state.get(), YoleckEditorState::EditorActive) {
326            return Ok(());
327        }
328
329        let button_response = ui.button("Add New Entity");
330
331        egui::Popup::menu(&button_response).show(|ui| {
332            for entity_type in construction_specs.entity_types.iter() {
333                if ui.button(&entity_type.name).clicked() {
334                    writer.write(YoleckDirective(YoleckDirectiveInner::SpawnEntity {
335                        level: yoleck.level_being_edited,
336                        type_name: entity_type.name.clone(),
337                        data: serde_json::Value::Object(Default::default()),
338                        select_created_entity: true,
339                        modify_exclusive_systems: None,
340                    }));
341                }
342            }
343        });
344
345        system_state.apply(world);
346        Ok(())
347    }
348}
349
350/// The UI part for selecting entities. See [`YoleckEditorSections`](crate::YoleckEditorSections).
351pub fn entity_selection_section(
352    world: &mut World,
353) -> impl FnMut(&mut World, &mut egui::Ui) -> Result {
354    let mut filter_custom_name = String::new();
355    let mut filter_types = HashSet::<String>::new();
356
357    let mut system_state = SystemState::<(
358        Res<YoleckEntityConstructionSpecs>,
359        Query<(Entity, &YoleckManaged, Option<&YoleckEditMarker>)>,
360        Res<State<YoleckEditorState>>,
361        MessageWriter<YoleckDirective>,
362        Option<Res<YoleckActiveExclusiveSystem>>,
363    )>::new(world);
364
365    move |world, ui| {
366        let (
367            construction_specs,
368            yoleck_managed_query,
369            editor_state,
370            mut writer,
371            active_exclusive_system,
372        ) = system_state.get_mut(world);
373        if active_exclusive_system.is_some() {
374            return Ok(());
375        }
376
377        if !matches!(editor_state.get(), YoleckEditorState::EditorActive) {
378            return Ok(());
379        }
380
381        egui::CollapsingHeader::new("Select").show(ui, |ui| {
382            egui::CollapsingHeader::new("Filter").show(ui, |ui| {
383                ui.horizontal(|ui| {
384                    ui.label("By Name:");
385                    ui.text_edit_singleline(&mut filter_custom_name);
386                });
387                for entity_type in construction_specs.entity_types.iter() {
388                    let mut should_show = filter_types.contains(&entity_type.name);
389                    if ui.checkbox(&mut should_show, &entity_type.name).changed() {
390                        if should_show {
391                            filter_types.insert(entity_type.name.clone());
392                        } else {
393                            filter_types.remove(&entity_type.name);
394                        }
395                    }
396                }
397            });
398            for (entity, yoleck_managed, edit_marker) in yoleck_managed_query.iter() {
399                if !filter_types.is_empty() && !filter_types.contains(&yoleck_managed.type_name) {
400                    continue;
401                }
402                if !yoleck_managed.name.contains(filter_custom_name.as_str()) {
403                    continue;
404                }
405                let is_selected = edit_marker.is_some();
406                if ui
407                    .selectable_label(is_selected, format_caption(entity, yoleck_managed))
408                    .clicked()
409                {
410                    if ui.input(|input| input.modifiers.shift) {
411                        writer.write(YoleckDirective::toggle_selected(entity));
412                    } else if is_selected {
413                        writer.write(YoleckDirective::set_selected(None));
414                    } else {
415                        writer.write(YoleckDirective::set_selected(Some(entity)));
416                    }
417                }
418            }
419        });
420        Ok(())
421    }
422}
423
424/// The UI part for editing entities. See [`YoleckEditorSections`](crate::YoleckEditorSections).
425pub fn entity_editing_section(
426    world: &mut World,
427) -> impl FnMut(&mut World, &mut egui::Ui) -> Result {
428    let mut system_state = SystemState::<(
429        ResMut<YoleckState>,
430        Query<(Entity, &mut YoleckManaged), With<YoleckEditMarker>>,
431        Query<Entity, With<YoleckEditMarker>>,
432        MessageReader<YoleckDirective>,
433        Commands,
434        Res<State<YoleckEditorState>>,
435        MessageWriter<YoleckEditorEvent>,
436        ResMut<YoleckKnobsCache>,
437        Option<Res<YoleckActiveExclusiveSystem>>,
438        ResMut<YoleckExclusiveSystemsQueue>,
439        Res<YoleckEntityCreationExclusiveSystems>,
440    )>::new(world);
441
442    let mut previously_edited_entity: Option<Entity> = None;
443    let mut new_entity_created_this_frame = false;
444
445    move |world, ui| {
446        let mut passed_data = YoleckPassedData(Default::default());
447        {
448            let (
449                mut yoleck,
450                mut yoleck_managed_query,
451                yoleck_edited_query,
452                mut directives_reader,
453                mut commands,
454                editor_state,
455                mut writer,
456                mut knobs_cache,
457                active_exclusive_system,
458                mut exclusive_systems_queue,
459                entity_creation_exclusive_systems,
460            ) = system_state.get_mut(world);
461
462            if !matches!(editor_state.get(), YoleckEditorState::EditorActive) {
463                return Ok(());
464            }
465
466            let mut data_passed_to_entities: HashMap<Entity, HashMap<TypeId, BoxedArc>> =
467                Default::default();
468            for directive in directives_reader.read() {
469                match &directive.0 {
470                    YoleckDirectiveInner::PassToEntity(entity, type_id, data) => {
471                        if false {
472                            data_passed_to_entities
473                                .entry(*entity)
474                                .or_default()
475                                .insert(*type_id, data.clone());
476                        }
477                        passed_data
478                            .0
479                            .entry(*entity)
480                            .or_default()
481                            .insert(*type_id, data.clone());
482                    }
483                    YoleckDirectiveInner::SetSelected(entity) => {
484                        if active_exclusive_system.is_some() {
485                            // TODO: pass the selection command to the exclusive system?
486                            continue;
487                        }
488                        if let Some(entity) = entity {
489                            let mut already_selected = false;
490                            for entity_to_deselect in yoleck_edited_query.iter() {
491                                if entity_to_deselect == *entity {
492                                    already_selected = true;
493                                } else {
494                                    commands
495                                        .entity(entity_to_deselect)
496                                        .remove::<YoleckEditMarker>();
497                                    writer.write(YoleckEditorEvent::EntityDeselected(
498                                        entity_to_deselect,
499                                    ));
500                                }
501                            }
502                            if !already_selected {
503                                commands.entity(*entity).insert(YoleckEditMarker);
504                                writer.write(YoleckEditorEvent::EntitySelected(*entity));
505                            }
506                        } else {
507                            for entity_to_deselect in yoleck_edited_query.iter() {
508                                commands
509                                    .entity(entity_to_deselect)
510                                    .remove::<YoleckEditMarker>();
511                                writer
512                                    .write(YoleckEditorEvent::EntityDeselected(entity_to_deselect));
513                            }
514                        }
515                    }
516                    YoleckDirectiveInner::ChangeSelectedStatus { entity, force_to } => {
517                        if active_exclusive_system.is_some() {
518                            // TODO: pass the selection command to the exclusive system?
519                            continue;
520                        }
521                        match (force_to, yoleck_edited_query.contains(*entity)) {
522                            (Some(true), true) | (Some(false), false) => {
523                                // Nothing to do
524                            }
525                            (None, false) | (Some(true), false) => {
526                                // Add to selection
527                                commands.entity(*entity).insert(YoleckEditMarker);
528                                writer.write(YoleckEditorEvent::EntitySelected(*entity));
529                            }
530                            (None, true) | (Some(false), true) => {
531                                // Remove from selection
532                                commands.entity(*entity).remove::<YoleckEditMarker>();
533                                writer.write(YoleckEditorEvent::EntityDeselected(*entity));
534                            }
535                        }
536                    }
537                    YoleckDirectiveInner::SpawnEntity {
538                        level,
539                        type_name,
540                        data,
541                        select_created_entity,
542                        modify_exclusive_systems: override_exclusive_systems,
543                    } => {
544                        if active_exclusive_system.is_some() {
545                            continue;
546                        }
547                        let mut cmd = commands.spawn((
548                            YoleckRawEntry {
549                                header: YoleckEntryHeader {
550                                    type_name: type_name.clone(),
551                                    name: "".to_owned(),
552                                    uuid: None,
553                                },
554                                data: data.clone(),
555                            },
556                            YoleckBelongsToLevel { level: *level },
557                        ));
558                        if *select_created_entity {
559                            writer.write(YoleckEditorEvent::EntitySelected(cmd.id()));
560                            cmd.insert(YoleckEditMarker);
561                            for entity_to_deselect in yoleck_edited_query.iter() {
562                                commands
563                                    .entity(entity_to_deselect)
564                                    .remove::<YoleckEditMarker>();
565                                writer
566                                    .write(YoleckEditorEvent::EntityDeselected(entity_to_deselect));
567                            }
568                            *exclusive_systems_queue =
569                                entity_creation_exclusive_systems.create_queue();
570                            if let Some(override_exclusive_systems) = override_exclusive_systems {
571                                override_exclusive_systems(exclusive_systems_queue.as_mut());
572                            }
573                            new_entity_created_this_frame = true;
574                        }
575                        yoleck.level_needs_saving = true;
576                    }
577                }
578            }
579
580            let entity_being_edited;
581            if let Ok((entity, mut yoleck_managed)) = yoleck_managed_query.single_mut() {
582                entity_being_edited = Some(entity);
583                ui.horizontal(|ui| {
584                    ui.heading(format!(
585                        "Editing {}",
586                        format_caption(entity, &yoleck_managed)
587                    ));
588                    if ui.button("Delete").clicked() {
589                        commands.entity(entity).despawn();
590                        writer.write(YoleckEditorEvent::EntityDeselected(entity));
591                        yoleck.level_needs_saving = true;
592                    }
593                });
594                ui.horizontal(|ui| {
595                    ui.label("Custom Name:");
596                    ui.text_edit_singleline(&mut yoleck_managed.name);
597                });
598            } else {
599                entity_being_edited = None;
600            }
601
602            if previously_edited_entity != entity_being_edited {
603                previously_edited_entity = entity_being_edited;
604                for knob_entity in knobs_cache.drain() {
605                    commands.entity(knob_entity).despawn();
606                }
607            } else {
608                knobs_cache.clean_untouched(|knob_entity| {
609                    commands.entity(knob_entity).despawn();
610                });
611            }
612        }
613        system_state.apply(world);
614
615        let frame = egui::Frame::new();
616        let mut prepared = frame.begin(ui);
617        let content_ui = std::mem::replace(
618            &mut prepared.content_ui,
619            ui.new_child(egui::UiBuilder {
620                max_rect: Some(ui.max_rect()),
621                layout: Some(*ui.layout()), // Is this necessary?
622                ..Default::default()
623            }),
624        );
625        world.insert_resource(YoleckUi(content_ui));
626        world.insert_resource(passed_data);
627
628        enum ActiveExclusiveSystemStatus {
629            DidNotRun,
630            StillRunningSame,
631            JustFinishedRunning,
632        }
633
634        let behavior_for_exclusive_system = if let Some(mut active_exclusive_system) =
635            world.remove_resource::<YoleckActiveExclusiveSystem>()
636        {
637            let result = active_exclusive_system
638                .0
639                .run((), world)
640                .map_err(|e| match e {
641                    bevy::ecs::system::RunSystemError::Skipped(e) => e.into(),
642                    bevy::ecs::system::RunSystemError::Failed(e) => e,
643                })?;
644            match result {
645                YoleckExclusiveSystemDirective::Listening => {
646                    world.insert_resource(active_exclusive_system);
647                    ActiveExclusiveSystemStatus::StillRunningSame
648                }
649                YoleckExclusiveSystemDirective::Finished => {
650                    ActiveExclusiveSystemStatus::JustFinishedRunning
651                }
652            }
653        } else {
654            ActiveExclusiveSystemStatus::DidNotRun
655        };
656
657        let should_run_regular_systems = match behavior_for_exclusive_system {
658            ActiveExclusiveSystemStatus::DidNotRun => loop {
659                let Some(mut new_exclusive_system) = world
660                    .resource_mut::<YoleckExclusiveSystemsQueue>()
661                    .pop_front()
662                else {
663                    break true;
664                };
665                new_exclusive_system.initialize(world);
666                let first_run_result =
667                    new_exclusive_system.run((), world).map_err(|e| match e {
668                        bevy::ecs::system::RunSystemError::Skipped(e) => e.into(),
669                        bevy::ecs::system::RunSystemError::Failed(e) => e,
670                    })?;
671                if new_entity_created_this_frame
672                    || matches!(first_run_result, YoleckExclusiveSystemDirective::Listening)
673                {
674                    world.insert_resource(YoleckActiveExclusiveSystem(new_exclusive_system));
675                    break false;
676                }
677            },
678            ActiveExclusiveSystemStatus::StillRunningSame => false,
679            ActiveExclusiveSystemStatus::JustFinishedRunning => false,
680        };
681
682        if should_run_regular_systems {
683            world.resource_scope(|world, mut yoleck_edit_systems: Mut<YoleckEditSystems>| {
684                yoleck_edit_systems.run_systems(world);
685            });
686        }
687        let YoleckUi(content_ui) = world
688            .remove_resource()
689            .expect("The YoleckUi resource was put in the world by this very function");
690        world.remove_resource::<YoleckPassedData>();
691        prepared.content_ui = content_ui;
692        prepared.end(ui);
693
694        // Some systems may have edited the entries, so we need to update them
695        world.run_schedule(YoleckInternalSchedule::UpdateManagedDataFromComponents);
696        Ok(())
697    }
698}