-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Create types to store serializable data about the ECS schedule and provide tools for extracting this data. #22520
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
4c0eec6
Create an initial setup for dumping out schedule data into a serializ…
andriyDev 0e355d3
Create a plugin that can be added to automatically dump all schedules.
andriyDev 13cadcc
Create an example to dump out all the schedule data.
andriyDev 40209a5
Move build warnings into a more extensible `ScheduleBuildMetadata` st…
andriyDev 594bac5
Make build passes record which edges they add and return that in the …
andriyDev 6fbc10d
Send the schedule build metadata as an event instead of storing it.
andriyDev e35a02d
Add the edges added by build passes to the output schedule data.
andriyDev 452ab3c
Merge "main" branch into dump-schedules.
andriyDev ce6d00c
Improve the docs.
andriyDev 930f80b
Explain the default location for app_data in the example.
andriyDev 9040f18
Use an enum to indicate world conflict instead of an empty Vec.
andriyDev 9ba71e2
Remove deprecated method call.
andriyDev fab36e0
Remove unhelpful helper.
andriyDev 32716f4
Change the severity of errors to Warning.
andriyDev f3563b6
Explain why we don't record removed edges.
andriyDev f88e34e
Use a newtype for hierarchy keys.
andriyDev 68ca5f9
Add a test that the plugin code actually extracts the schedule data.
andriyDev 1a68b21
Move the .gitignore to the root.
andriyDev 543af4f
Propagate the schedule_data feature flag all the way up instead of us…
andriyDev 426b0bd
Update feature list.
andriyDev a7b5295
Appease CI false positive.
andriyDev 4787615
Simple suggestions from code review
alice-i-cecile a49718b
cargo fmt
alice-i-cecile File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| //! Tools for extracting schedule data from an app, and interpreting that data for use with | ||
| //! visualization tools (for example). | ||
|
|
||
| pub mod plugin; | ||
| pub mod serde; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| //! Convenience plugin for automatically performing serialization of schedules on boot. | ||
| use std::{fs::File, io::Write, path::PathBuf}; | ||
|
|
||
| use bevy_app::{App, Main, Plugin}; | ||
| use bevy_ecs::{ | ||
| error::{BevyError, ResultSeverityExt, Severity}, | ||
| intern::Interned, | ||
| resource::Resource, | ||
| schedule::{ | ||
| common_conditions::run_once, IntoScheduleConfigs, ScheduleLabel, Schedules, SystemSet, | ||
| }, | ||
| world::World, | ||
| }; | ||
| use bevy_platform::collections::HashMap; | ||
| use ron::ser::PrettyConfig; | ||
|
|
||
| use crate::schedule_data::serde::AppData; | ||
|
|
||
| /// A plugin to automatically collect and write all schedule data on boot to a file that can later | ||
| /// be parsed. | ||
| /// | ||
| /// By default, the schedule data is written to `<current working directory>/app_data.ron`. This can | ||
| /// be configured to a different path using [`SerializeSchedulesFilePath`]. | ||
| pub struct SerializeSchedulesPlugin { | ||
| /// The schedule into which the systems for collecting/writing the schedule data are added. | ||
| /// | ||
| /// This schedule **will not** have its schedule data collected, as well as any "parent" | ||
| /// schedules. In order to run a schedule, Bevy removes it from the world, meaning if this | ||
| /// system is added to schedule [`Update`](bevy_app::Update), that schedule and also [`Main`] | ||
| /// will not be included in the [`AppData`]. The default is the [`Main`] schedule since usually | ||
| /// there is only one system ([`Main::run_main`]), so there's very little data to collect. | ||
| /// | ||
| /// Avoid changing this field. This is intended for power-users who might not use the [`Main`] | ||
| /// schedule at all. It may also be worth considering just calling [`AppData::from_schedules`] | ||
| /// manually to ensure a particular schedule is present. | ||
| /// | ||
| /// Usually, this will be set using [`Self::in_schedule`]. | ||
| pub schedule: Interned<dyn ScheduleLabel>, | ||
alice-i-cecile marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| impl Default for SerializeSchedulesPlugin { | ||
| fn default() -> Self { | ||
| Self { | ||
| schedule: Main.intern(), | ||
alice-i-cecile marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| } | ||
|
|
||
| impl SerializeSchedulesPlugin { | ||
| /// Creates an instance of [`Self`] that inserts into the specified schedule. | ||
| pub fn in_schedule(label: impl ScheduleLabel) -> Self { | ||
| Self { | ||
| schedule: label.intern(), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl Plugin for SerializeSchedulesPlugin { | ||
| fn build(&self, app: &mut App) { | ||
| app.init_resource::<SerializeSchedulesFilePath>() | ||
| .add_systems( | ||
| self.schedule, | ||
| collect_system_data | ||
| .run_if(run_once) | ||
| .in_set(SerializeSchedulesSystems) | ||
| // While we may not be in the `Main` schedule at all, the default is that, so we | ||
| // should make this work properly in the default case. | ||
| .before(Main::run_main), | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /// A system set for allowing users to configure scheduling properties of systems in | ||
| /// [`SerializeSchedulesPlugin`]. | ||
| #[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone)] | ||
| pub struct SerializeSchedulesSystems; | ||
|
|
||
| /// The file path where schedules will be written to after collected by | ||
| /// [`SerializeSchedulesPlugin`]. | ||
| #[derive(Resource)] | ||
| pub struct SerializeSchedulesFilePath(pub PathBuf); | ||
|
|
||
| impl Default for SerializeSchedulesFilePath { | ||
| fn default() -> Self { | ||
| Self("app_data.ron".into()) | ||
| } | ||
| } | ||
|
|
||
| /// The inner part of [`collect_system_data`] that returns the [`AppData`] so we can write tests | ||
| /// without needing to write to disk. | ||
| fn collect_system_data_inner(world: &mut World) -> Result<AppData, BevyError> { | ||
| let schedules = world.resource::<Schedules>(); | ||
| let labels = schedules | ||
| .iter() | ||
| .map(|schedule| schedule.1.label()) | ||
| .collect::<Vec<_>>(); | ||
| let mut label_to_build_metadata = HashMap::new(); | ||
|
|
||
| for label in labels { | ||
| let mut schedules = world.resource_mut::<Schedules>(); | ||
| let mut schedule = schedules.remove(label).unwrap(); | ||
| let Some(build_metadata) = schedule.initialize(world)? else { | ||
| return Err( | ||
| "The schedule has already been built, so we can't collect its system data".into(), | ||
| ); | ||
| }; | ||
alice-i-cecile marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| label_to_build_metadata.insert(label, build_metadata); | ||
|
|
||
| let mut schedules = world.resource_mut::<Schedules>(); | ||
| schedules.insert(schedule); | ||
| } | ||
|
|
||
| let schedules = world.resource::<Schedules>(); | ||
| Ok(AppData::from_schedules( | ||
| schedules, | ||
| world.components(), | ||
| &label_to_build_metadata, | ||
| )?) | ||
| } | ||
|
|
||
| /// A system that collects all the schedule data and writes it to [`SerializeSchedulesFilePath`]. | ||
| fn collect_system_data(world: &mut World) -> Result<(), BevyError> { | ||
| let app_data = collect_system_data_inner(world).with_severity(Severity::Warning)?; | ||
| let file_path = world | ||
| .get_resource::<SerializeSchedulesFilePath>() | ||
| .ok_or("Missing SerializeSchedulesFilePath resource") | ||
| .with_severity(Severity::Warning)?; | ||
| let mut file = File::create(&file_path.0).with_severity(Severity::Warning)?; | ||
| // Use \n unconditionally so that Windows formatting is predictable. | ||
| let serialized = ron::ser::to_string_pretty(&app_data, PrettyConfig::default().new_line("\n"))?; | ||
| file.write_all(serialized.as_bytes()) | ||
| .with_severity(Severity::Warning)?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use bevy_app::{App, PostUpdate, Update}; | ||
|
|
||
| use crate::schedule_data::{ | ||
| plugin::collect_system_data_inner, | ||
| serde::tests::{remove_module_paths, simple_system, sort_app_data}, | ||
| }; | ||
|
|
||
| #[test] | ||
| fn collects_all_schedules() { | ||
| // Start with an empty app so only our stuff gets added. | ||
| let mut app = App::empty(); | ||
|
|
||
| fn a() {} | ||
| fn b() {} | ||
| fn c() {} | ||
| app.add_systems(Update, (a, b)); | ||
| app.add_systems(PostUpdate, c); | ||
|
|
||
| // Normally users would use the plugin, but to avoid writing to disk in a test, we just call | ||
| // the inner part of the system directly. | ||
| let mut app_data = collect_system_data_inner(app.world_mut()).unwrap(); | ||
| remove_module_paths(&mut app_data); | ||
| sort_app_data(&mut app_data); | ||
|
|
||
| assert_eq!(app_data.schedules.len(), 2); | ||
| let post_update = &app_data.schedules[0]; | ||
| assert_eq!(post_update.name, "PostUpdate"); | ||
| assert_eq!(post_update.systems, [simple_system("c")]); | ||
| let update = &app_data.schedules[1]; | ||
| assert_eq!(update.name, "Update"); | ||
| assert_eq!(update.systems, [simple_system("a"), simple_system("b")]); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.