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