Skip to main content

world_serialization/
world_serialization.rs

1//! This example demonstrates how to load world data from files and then dynamically
2//! apply that data to entities in your Bevy `World`. This includes spawning new
3//! entities and applying updates to existing ones. World serialization in Bevy
4//! serializes and deserializes `Components` and `Resources` so that you can easily
5//! store, load, and manipulate data outside of a purely code-driven context.
6//!
7//! This example also shows how to do the following:
8//! * Register your custom types for reflection, which allows them to be serialized,
9//!   deserialized, and manipulated dynamically.
10//! * Skip serialization of fields you don't want stored in your world files (like
11//!   runtime values that should always be computed dynamically).
12//! * Save a new world to disk to show how it can be updated compared to the original
13//!   world file (and how that updated world file might then be used later on).
14//!
15//! The example proceeds by creating components and resources, registering their types,
16//! loading a world from a file, logging when changes are detected, and finally saving
17//! a new world file to disk. This is useful for anyone wanting to see how to integrate
18//! file-based ECS workflows into their Bevy projects.
19//!
20//! # Note on working with files
21//!
22//! The saving behavior uses the standard filesystem APIs, which are blocking, so it
23//! utilizes a thread pool (`IoTaskPool`) to avoid stalling the main thread. This
24//! won't work on WASM because WASM typically doesn't have direct filesystem access.
25//!
26
27use bevy::{asset::LoadState, prelude::*, tasks::IoTaskPool};
28
29use core::time::Duration;
30use std::{fs::File, io::Write};
31
32/// The entry point of our Bevy app.
33///
34/// Sets up default plugins, registers all necessary component/resource types
35/// for serialization/reflection, and runs the various systems in the correct schedule.
36fn main() {
37    App::new()
38        .add_plugins(DefaultPlugins)
39        .add_systems(
40            Startup,
41            (save_world_system, load_world_system, infotext_system),
42        )
43        .add_systems(Update, (log_system, panic_on_fail))
44        .run();
45}
46
47/// # Components, Resources, and Reflection
48///
49/// Below are some simple examples of how to define your own Bevy `Component` types
50/// and `Resource` types so that they can be properly reflected, serialized, and
51/// deserialized. The `#[derive(Reflect)]` macro enables Bevy's reflection features,
52/// and we add component-specific reflection by using `#[reflect(Component)]`.
53/// We also illustrate how to skip serializing fields and how `FromWorld` can help
54/// create runtime-initialized data.
55///
56/// A sample component that is fully serializable.
57///
58/// This component has public `x` and `y` fields that will be included in
59/// the world files. Notice how it derives `Default`, `Reflect`, and declares
60/// itself as a reflected component with `#[reflect(Component)]`.
61#[derive(Component, Reflect, Default)]
62#[reflect(Component)] // this tells the reflect derive to also reflect component behaviors
63struct ComponentA {
64    /// An example `f32` field
65    pub x: f32,
66    /// Another example `f32` field
67    pub y: f32,
68}
69
70/// A sample component that includes both serializable and non-serializable fields.
71///
72/// This is useful for skipping serialization of runtime data or fields you
73/// don't want written to world files.
74#[derive(Component, Reflect)]
75#[reflect(Component)]
76struct ComponentB {
77    /// A string field that will be serialized.
78    pub value: String,
79    /// A `Duration` field that should never be serialized to the world file, so we skip it.
80    #[reflect(skip_serializing)]
81    pub _time_since_startup: Duration,
82}
83
84/// This implements `FromWorld` for `ComponentB`, letting us initialize runtime fields
85/// by accessing the current ECS resources. In this case, we acquire the `Time` resource
86/// and store the current elapsed time.
87impl FromWorld for ComponentB {
88    fn from_world(world: &mut World) -> Self {
89        let time = world.resource::<Time>();
90        ComponentB {
91            _time_since_startup: time.elapsed(),
92            value: "Default Value".to_string(),
93        }
94    }
95}
96
97/// A simple resource that also derives `Reflect`, allowing it to be stored in world files.
98///
99/// Just like a component, you can skip serializing fields or implement `FromWorld` if needed.
100#[derive(Resource, Reflect, Default)]
101#[reflect(Resource)]
102struct ResourceA {
103    /// This resource tracks a `score` value.
104    pub score: u32,
105}
106
107/// # World File Paths
108///
109/// `WORLD_FILE_PATH` points to the original world file that we'll be loading.
110/// `NEW_WORLD_FILE_PATH` points to the new world file that we'll be creating
111/// (and demonstrating how to serialize to disk).
112///
113/// The initial world file will be loaded below and not change when the world is saved.
114const WORLD_FILE_PATH: &str = "serialized_worlds/load_scene_example.scn.ron";
115
116/// The new, updated world data will be saved here so that you can see the changes.
117const NEW_WORLD_FILE_PATH: &str = "serialized_worlds/load_scene_example-new.scn.ron";
118
119/// Loads a world from an asset file and spawns it in the current world.
120///
121/// Spawning a `DynamicWorldRoot` creates a new parent entity, which then spawns new
122/// instances of the world's entities as its children. If you modify the
123/// `WORLD_FILE_PATH` file, or if you enable file watching, you can see
124/// changes reflected immediately.
125fn load_world_system(mut commands: Commands, asset_server: Res<AssetServer>) {
126    commands.spawn(DynamicWorldRoot(asset_server.load(WORLD_FILE_PATH)));
127    commands.spawn((
128        Camera3d::default(),
129        Transform::from_xyz(1.0, 1.0, 1.0).looking_at(Vec3::new(0.0, 0.25, 0.0), Vec3::Y),
130    ));
131    commands.spawn((
132        DirectionalLight::default(),
133        Transform::default().looking_to(Vec3::new(0.0, -1.0, -1.0), Vec3::Y),
134    ));
135}
136
137/// Logs changes made to `ComponentA` entities, and also checks whether `ResourceA`
138/// has been recently added.
139///
140/// Any time a `ComponentA` is modified, that change will appear here. This system
141/// demonstrates how you might detect and handle world updates at runtime.
142fn log_system(
143    query: Query<(Entity, &ComponentA), Changed<ComponentA>>,
144    res: Option<Res<ResourceA>>,
145) {
146    for (entity, component_a) in &query {
147        info!("  Entity({})", entity.index());
148        info!(
149            "    ComponentA: {{ x: {} y: {} }}\n",
150            component_a.x, component_a.y
151        );
152    }
153    if let Some(res) = res
154        && res.is_added()
155    {
156        info!("  New ResourceA: {{ score: {} }}\n", res.score);
157    }
158}
159
160/// Demonstrates how to create a new world from scratch, populate it with data,
161/// and then serialize it to a file. The new file is written to `NEW_WORLD_FILE_PATH`.
162///
163/// This system creates a fresh world, duplicates the type registry so that our
164/// custom component types are recognized, spawns some sample entities and resources,
165/// and then serializes the resulting dynamic world.
166fn save_world_system(world: &mut World) {
167    let asset_server = world.resource::<AssetServer>().clone();
168    // The `TypeRegistry` resource contains information about all registered types (including components).
169    // This is used to construct worlds, so we'll want to ensure that we use the registry from the
170    // main world. To do this, we can simply clone the `AppTypeRegistry` resource.
171    let type_registry = world.resource::<AppTypeRegistry>().clone();
172
173    // Any ECS World can be serialized.
174    // For demonstration purposes, we'll create a new one.
175    let mut scene_world = World::new();
176
177    let mut component_b = ComponentB::from_world(world);
178    component_b.value = "hello".to_string();
179    scene_world.spawn((
180        component_b,
181        ComponentA { x: 1.0, y: 2.0 },
182        Transform::IDENTITY,
183        Name::new("joe"),
184        WorldAssetRoot(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0")),
185    ));
186    scene_world.spawn(ComponentA { x: 3.0, y: 4.0 });
187    scene_world.insert_resource(ResourceA { score: 1 });
188
189    // With our sample world ready to go, we can now create a DynamicWorld from it.
190    // For simplicity, we will create our scene using DynamicWorld directly, but if
191    // you need more control, you can use DynamicWorldBuilder.
192    let dynamic_world = DynamicWorld::from_world_with(&scene_world, &type_registry.read());
193
194    // Dynamic Worlds can be serialized like this:
195    let type_registry = world.resource::<AppTypeRegistry>();
196    let type_registry = type_registry.read();
197    let serialized_world = dynamic_world.serialize(&type_registry).unwrap();
198
199    // Shows the serialized world in the console
200    info!("{}", serialized_world);
201
202    // Writing the world to a new file. Using a task to avoid calling the filesystem APIs in a system
203    // as they are blocking.
204    //
205    // This can't work in Wasm as there is no filesystem access.
206    #[cfg(not(target_arch = "wasm32"))]
207    IoTaskPool::get()
208        .spawn(async move {
209            // Write the world RON data to file
210            File::create(format!("assets/{NEW_WORLD_FILE_PATH}"))
211                .and_then(|mut file| file.write(serialized_world.as_bytes()))
212                .expect("Error while writing world to file");
213        })
214        .detach();
215}
216
217/// Spawns a simple 2D camera and some text indicating that the user should
218/// check the console output for world loading/saving messages.
219///
220/// This system is only necessary for the info message in the UI.
221fn infotext_system(mut commands: Commands) {
222    commands.spawn((
223        Text::new("Check the console output for more!"),
224        TextFont {
225            font_size: FontSize::Px(42.0),
226            ..default()
227        },
228        Node {
229            align_self: AlignSelf::FlexEnd,
230            ..default()
231        },
232    ));
233}
234
235/// To help with Bevy's automated testing, we want the example to close with an appropriate if the
236/// world asset fails to load. This is most likely not something you want in your own app.
237fn panic_on_fail(world_roots: Query<&DynamicWorldRoot>, asset_server: Res<AssetServer>) {
238    for world_root in &world_roots {
239        if let Some(LoadState::Failed(err)) = asset_server.get_load_state(&world_root.0) {
240            panic!("Failed to load world. {err}");
241        }
242    }
243}