Skip to content
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

Cannot Serialize Simple Bevy World Into a Scene #9060

Open
Peepo-Juice opened this issue Jul 6, 2023 · 7 comments
Open

Cannot Serialize Simple Bevy World Into a Scene #9060

Peepo-Juice opened this issue Jul 6, 2023 · 7 comments
Labels
A-Scenes Serialized ECS data stored on the disk C-Bug An unexpected or incorrect behavior

Comments

@Peepo-Juice
Copy link

Bevy version

0.10.1

What you did

  1. Created fresh bevy project
  2. Made this main.rs file:
use bevy::prelude::*;

fn main() {
    App::new()
        .add_startup_system(start_up)
        .add_system(serialize_scene_system)
        .add_plugins(DefaultPlugins)
        .run();
}

fn start_up(mut commands: Commands) {
    // comment out this line to fix issue
    commands.spawn(Camera2dBundle::default());
}

fn serialize_scene_system(world: &mut World) {
    let type_registry = world.resource::<AppTypeRegistry>();
    let scene = DynamicScene::from_world(world, type_registry);

    let serialized_scene = scene.serialize_ron(type_registry).unwrap();

    info!("{}", serialized_scene);
}
  1. Executed "cargo run"

What went wrong

I was expecting it to just serialize the world, as I have not really added any new code (i.e components/resources).
Instead, I got this error: thread 'main' panicked at 'called Result::unwrap() on an Err value: Message("no registration found for dynamic type with name bevy_math::rect::Rect")', src\main.rs:19:63

Additional information

It feels like something wasn't registered by bevy. If you comment out the line of code inside of start_up, which spawns the Camera bundle, the error disappears and program runs as expected.

My thoughts moving forward

I understand that in the examples, you usually create an empty world and throw in the things you want to save in the scene, or use the DynamicSceneBuilder. This may or may not be a common practice in the bevy community. But I think if you want to serialize the entire world, then you should be able to without bevy holding you back.

If we're in agreement, then we should make a unit test that ensures that a basic bevy setup always registers what is needed so that the entire World can be serialized. I would make a PR of this myself, and I would actually like to in order to have a contribution under my belt (perhaps the first of many), but I'm not familiar enough with Bevy to make a comprehensive unit test. So if someone could help me out with that, or if you just wanna leave suggestions here on what I should include in the unit test, I'd appreciate it!

@Peepo-Juice Peepo-Juice added C-Bug An unexpected or incorrect behavior S-Needs-Triage This issue needs to be labelled labels Jul 6, 2023
@Selene-Amanita Selene-Amanita added A-Scenes Serialized ECS data stored on the disk and removed S-Needs-Triage This issue needs to be labelled labels Jul 9, 2023
@Peepo-Juice
Copy link
Author

Peepo-Juice commented Jul 10, 2023

When I migrated the code to v0.11, a different error came up:

thread 'main' panicked at 'called `Result::unwrap()` on an 
`Err` value: Message("Type 'std::time::Instant' did not register 
ReflectSerialize")', src\main.rs:20:63

@Semihazah
Copy link

I apologize, but I'm having the same issue with ComputedVisibilityFlags.

@Architector4
Copy link
Contributor

Architector4 commented Jul 27, 2023

This reply is going to be a ramble, apologies for that, but...

Experiencing all the same stuff with Bevy 0.11.0 right now. Moreso, I tried excluding all components and resources that aren't truly serializable (the only way I found to do this was to actually just serialize each one, which is wildly inefficient, but it works and that's what I wanted lol), and there is still an issue.

Sample system for serializing and deserializing only serializable components and resources
use bevy::{
    ecs::entity::EntityMap,
    prelude::*,
    reflect::{serde::TypedReflectSerializer, TypeRegistryInternal},
    scene::serde::SceneDeserializer,
};

use serde::de::DeserializeSeed;

fn serializable(reflect: &dyn Reflect, registry: &TypeRegistryInternal) -> bool {
    let serializer = TypedReflectSerializer::new(reflect, registry);
    ron::to_string(&serializer).is_ok()
}

pub fn rescene(world: &mut World) {
    let input = world.get_resource::<Input<KeyCode>>().unwrap();
    if input.just_pressed(KeyCode::C) {
        let mut scene = DynamicScene::from_world(world);
        let registry = world.get_resource::<AppTypeRegistry>().unwrap().read();

        scene
            .resources
            .retain(|res| serializable(res.as_ref(), &registry));

        scene.entities.retain_mut(|ent| {
            ent.components
                .retain(|x| serializable(x.as_ref(), &registry));
            !ent.components.is_empty()
        });

        drop(registry);
        let registry = world.get_resource_mut::<AppTypeRegistry>().unwrap();
        let serialized = scene.serialize_ron(&registry.0).unwrap();

        //std::fs::write("/tmp/test", &serialized).ok();

        let registry = world.get_resource::<AppTypeRegistry>().unwrap().read();

        let deserializer = SceneDeserializer {
            type_registry: &registry,
        };

        let mut ronde = ron::de::Deserializer::from_bytes(serialized.as_bytes()).unwrap();
        // panics here
        let deserialized = deserializer.deserialize(&mut ronde).unwrap();

        //drop(registry);

        //deserialized
        //    .write_to_world(world, &mut EntityMap::default())
        //    .unwrap();
    }
}

This panics on actually deserializing the scene I got from serializing everything that could be. In particular:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Message("no registration found for type `core::option::Option<bevy_window::window::WindowTheme>`")', src/serialize_mess.rs:45:65

This seems to be the window_theme field on Window component. The fact that just serializing everything serializes window entities suggests that maybe serializing the entire world might not be the best path forward. The original poster of this issue would likely have been hit with this issue too later on when trying to deserialize their scene without a camera.

Funnily enough, excluding that via string comparisons in serializable function I made up causes it to then crash on Camera3dDepthTextureUsage, and excluding that on HashMap<Entity, Cascade>. It will likely keep crashing on various random stuff if I keep doing this.


...It doesn't seem like serializing the entire world and every single thing is very practical. It could probably be a cool feature to cause deserializing spawn a new window on the user's desktop, but usually that's not what you'd want.

This does raise a question of how does one de/serialize "meaningful" parts of a world. Serializing Transform but not GlobalTransform because the later is automatically computed anyways. Camera3d's transform and settings, but not depth texture metadata and other technical gooblydook. Not the window itself, maybe.

Thinking about it, this sounds like a very subjective thing, as different parts of different components can be meaningless to some users but very important to other. Someone might actually care about the exact computed matrix in a GlobalTransform, for example. I guess deciding on this should be user's responsibility, and DynamicSceneBuilder type does allow for granular filtering like this.

You could make a marker component, slap it on every meaningful entity and then before exporting make a query and use it as a filter in DynamicSceneBuilder. If that's not enough filtering, there's always the path of directly editing DynamicScene itself, as it's just a struct with vectors of resources and entities, latter of which are vectors of components.

For unserializable components themselves, like Camera2d, it does sound like you'd want to do some more work: store just the meaningful information itself, maybe as a separate component, and then on/after deserializing convert that component into a real new Camera2d. Considering that scenes are assets, this sounds like something related to asset processing, which would get a revamp in Bevy 0.12. Maybe better ways around this stuff would appear at that time?

Though, the caveat of "this is likely meaningless to serialize" should probably be mentioned in docs of DynamicScene::from_world(…).

@MatthewEppelsheimer
Copy link

I understand that in the examples, you usually create an empty world and throw in the things you want to save in the scene, or use the DynamicSceneBuilder. [...] But I think if you want to serialize the entire world, then you should be able to without bevy holding you back. [...] we should make a unit test that ensures that a basic bevy setup always registers what is needed so that the entire World can be serialized.

Completely agreed! I'm new to Bevy (and Rust for that matter) so I can't be much help beyond this small contribution:

I can reproduce in Bevy 0.11.2 with the following main.rs updated from the original poster to remove some errors and deprecation warnings:

use bevy::prelude::*;

fn main() {
    App::new()
        .add_systems(Startup, start_up)
        .add_systems(Update, serialize_scene_system)
        .add_plugins(DefaultPlugins)
        .run();
}

fn start_up(mut commands: Commands) {
    // comment out this line to fix issue
    commands.spawn(Camera2dBundle::default());
}

fn serialize_scene_system(world: &mut World) {
    let type_registry = world.resource::<AppTypeRegistry>();
    let scene = DynamicScene::from_world(world);

    let serialized_scene = scene.serialize_ron(type_registry).unwrap();

    info!("{}", serialized_scene);
}

The error:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Message("Type 'std::time::Instant' did not register ReflectSerialize")', src/main.rs:20:63

@pop
Copy link

pop commented Sep 1, 2023

Related to this issue, there I noticed that PlaybackSettings do not serialize/deserialize. There are no errors, but that component is not included in DynamicScene exports.

I think the root of the issue is that a number of components and resources do not reflect out of the box, resulting in inconsistent and surprising behavior. Reflection is still fairly new (mentioned a lot in the latest release notes) and stabilizing -- so rough edges like this are to be expected.

My workaround (for those blocked by this issue) is to use the DynamicSceneBuilder allow, allow_resource and deny/deny_resource methods to exclude these panic-causing bugs w/ Instant and ComputedVisibility. Then I have a system which detects added entities with correlated components and "re-hydrates" said entities to include the correct components.

For example, here is a system that exports a scene with none of the resources and a bunch of entities, sans the ComputedVisibility component:

fn export(
    root: Query<Entity, With<LevelRoot>>,
    children:Query<&Children>,
    world: &World,
) {
    let app_type_registry = world.resource::<AppTypeRegistry>().clone();
    let mut builder = DynamicSceneBuilder::from_world(world.clone());

    builder.deny_all_resources();

    builder.allow_all();
    builder.deny::<ComputedVisibility>();

    // Here we are extracting a `LevelRoot` node and it's children, for example sake.
    // Extract the level root
    builder.extract_entity(root.single());

    // Extract all level root descendants
    builder.extract_entities(
        children.iter_descendants(root.single())
    );

    // Build the scene
    let scene = builder.build();

    let scene_ron = scene
        .serialize_ron(&app_type_registry)
        .expect("Serialize scene");
}

And here is a system that re-hydrates entities missing the ComputedVisibility component:

fn rehydrate(
    events: Query<Entity, (Added<Visibility>, Without<ComputedVisibility>)>,
    mut commands: Commands,
) {
    for entity in events.iter() {
        commands
            .entity(entity)
            .insert(ComputedVisibility::default());
    }
}

This is not an ideal UX and I would much rather have all out-of-the-box resources and components reflect by default.

@Zeenobit
Copy link
Contributor

Zeenobit commented Sep 19, 2023

Just to chime in, I've made Moonshine Save as a generic solution to exactly the problems @pop described.

@james7132
Copy link
Member

Note that this should be significantly easier with #4154 resolved and the currently experimental #12332. The latter should resolve almost all cases where registration is currently missing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Scenes Serialized ECS data stored on the disk C-Bug An unexpected or incorrect behavior
Projects
None yet
Development

No branches or pull requests

8 participants