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

Use FromReflect when extracting entities in dynamic scenes #15174

Merged
merged 9 commits into from
Sep 15, 2024

Conversation

yrns
Copy link
Contributor

@yrns yrns commented Sep 12, 2024

Objective

Fix #10284.

Solution

When DynamicSceneBuilder extracts entities, they are cloned via PartialReflect::clone_value, making them into dynamic versions of the original components. This loses any custom ReflectSerialize type data. Dynamic scenes are deserialized with the original types, not the dynamic versions, and so any component with a custom serialize may fail. In this case Rect and Vec2. The dynamic version includes the field names 'x' and 'y' but the Serialize impl doesn't, hence the "expect float" error.

The solution here: Instead of using clone_value to clone the components, FromReflect clones and retains the original information needed to serialize with any custom Serialize impls. I think using something like reflect_clone from (#13432) might make this more efficient.

I also did the same when deserializing dynamic scenes to appease some of the round-trip tests which use ReflectPartialEq, which requires the types be the same and not a unique/proxy pair. I'm not sure it's otherwise necessary. Maybe this would also be more efficient when spawning dynamic scenes with reflect_clone instead of FromReflect again?

An alternative solution would be to fall back to the dynamic version when deserializing DynamicScenes if the custom version fails. I think that's possible. Or maybe simply always deserializing via the dynamic route for dynamic scenes?

Testing

This example is similar to the original test case in #10284:

#![allow(missing_docs)]

use bevy::{prelude::*, scene::SceneInstanceReady};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, (save, load).chain())
        .observe(check)
        .run();
}

static SAVEGAME_SAVE_PATH: &str = "savegame.scn.ron";

fn save(world: &mut World) {
    let entity = world.spawn(OrthographicProjection::default()).id();

    let scene = DynamicSceneBuilder::from_world(world)
        .extract_entity(entity)
        .build();

    if let Some(registry) = world.get_resource::<AppTypeRegistry>() {
        let registry = registry.read();
        let serialized_scene = scene.serialize(&registry).unwrap();
        // println!("{}", serialized_scene);
        std::fs::write(format!("assets/{SAVEGAME_SAVE_PATH}"), serialized_scene).unwrap();
    }

    world.entity_mut(entity).despawn_recursive();
}

fn load(mut commands: Commands, asset_server: Res<AssetServer>) {
    commands.spawn(DynamicSceneBundle {
        scene: asset_server.load(SAVEGAME_SAVE_PATH),
        ..default()
    });
}

fn check(_trigger: Trigger<SceneInstanceReady>, query: Query<&OrthographicProjection>) {
    dbg!(query.single());
}

Migration Guide

The DynamicScene format is changed to use custom serialize impls so old scene files will need updating:

Old:

(
  resources: {},
  entities: {
    4294967299: (
      components: {
        "bevy_render::camera::projection::OrthographicProjection": (
          near: 0.0,
          far: 1000.0,
          viewport_origin: (
            x: 0.5,
            y: 0.5,
          ),
          scaling_mode: WindowSize(1.0),
          scale: 1.0,
          area: (
            min: (
              x: -1.0,
              y: -1.0,
            ),
            max: (
              x: 1.0,
              y: 1.0,
            ),
          ),
        ),
      },
    ),
  },
)

New:

(
  resources: {},
  entities: {
    4294967299: (
      components: {
        "bevy_render::camera::projection::OrthographicProjection": (
          near: 0.0,
          far: 1000.0,
          viewport_origin: (0.5, 0.5),
          scaling_mode: WindowSize(1.0),
          scale: 1.0,
          area: (
            min: (-1.0, -1.0),
            max: (1.0, 1.0),
          ),
        ),
      },
    ),
  },
)

@alice-i-cecile alice-i-cecile added A-Reflection Runtime information about types A-Scenes Serialized ECS data stored on the disk labels Sep 13, 2024
@alice-i-cecile alice-i-cecile added S-Needs-Review Needs reviewer attention (from anyone!) to move forward C-Bug An unexpected or incorrect behavior labels Sep 13, 2024
Copy link
Member

@MrGVSV MrGVSV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

This is actually something I originally wanted to add as part of the FromReflect ergonomics RFC/implementation (for the reflection deserializer, not necessarily the one for bevy_scene), so hopefully we can solve this problem at the root as well. I'm also hoping #13432 can help here eventually too.

crates/bevy_scene/src/dynamic_scene_builder.rs Outdated Show resolved Hide resolved
crates/bevy_scene/src/dynamic_scene_builder.rs Outdated Show resolved Hide resolved
@alice-i-cecile alice-i-cecile added the M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide label Sep 14, 2024
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you provide an example before and after scene for the migration guide? We don't have great tools here yet, but we should be able to do better.

@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Sep 14, 2024
@alice-i-cecile
Copy link
Member

FYI, clippy is failing: https://github.com/bevyengine/bevy/actions/runs/10863643853/job/30148024354?pr=15174 I'll merge this once that's passing though.

@yrns
Copy link
Contributor Author

yrns commented Sep 14, 2024

Could you provide an example before and after scene for the migration guide? We don't have great tools here yet, but we should be able to do better.

Updated. Interesting that viewport_origin (a Vec2) implements Serialize, but still serializes with reflect because it does not impl ReflectSerialize. Whereas Rect does. Should ReflectSerialize (and ReflectDeserialize) be implemented for all foreign Reflect types that also impl Serialize and Deserialize?

@alice-i-cecile
Copy link
Member

Should ReflectSerialize (and ReflectDeserialize) be implemented for all foreign Reflect types that also impl Serialize and Deserialize?

I believe so yes.

@yrns
Copy link
Contributor Author

yrns commented Sep 14, 2024

Without a rigorous way to check every type, I just did a text scan and found only the glam types were missing. The bug is not as bad as I thought because, as far as I can tell, only the glam types have a differing serialization format (they serialize as sequences not maps). If the format is the same for reflect/native serialization the bug should not occur. I updated the migration guide which includes viewport_origin looking right.

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Sep 15, 2024
@alice-i-cecile
Copy link
Member

Great, thank you!

Merged via the queue into bevyengine:main with commit 2ea51fc Sep 15, 2024
26 checks passed
coreh added a commit to coreh/bevy that referenced this pull request Sep 20, 2024
@yrns yrns deleted the op-ser branch September 21, 2024 19:17
github-merge-queue bot pushed a commit that referenced this pull request Oct 9, 2024
# Objective

Fixes #15726

The extraction logic for components makes use of `FromReflect` to try
and ensure we have a concrete type for serialization. However, we did
not do the same for resources.

The reason we're seeing this for the glam types is that #15174 also made
a change to rely on the glam type's `Serialize` and `Deserialize` impls,
which I don't think should have been merged (I'll put up a PR addressing
this specifically soon).

## Solution

Use `FromReflect` on extracted resources.

## Testing

You can test locally by running:

```
cargo test --package bevy_scene
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Reflection Runtime information about types A-Scenes Serialized ECS data stored on the disk C-Bug An unexpected or incorrect behavior M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cannot Deserialize Camera2dBundle because of the OrthographicProjection component
3 participants