Skip to content

Serialize Entity generation #2795

Closed
Closed

Description

@Sydius

What problem does this solve or what need does it fill?

This would allow for safe storage of Entity in things like save files without risk of collision for old, invalid Entity IDs. An example of where this could occur is if one component stores an Entity that, for whatever reason, becomes invalid, and a new Entity is later spawned with the same ID but a different generation. If this component is serialized and then deserialized, it will now be pointing to the new Entity and not to the invalid one that was originally serialized. This is useful in conjunction with get_or_spawn() when reading back in save files. A similar principle would apply for networked games.

What solution would you like?

Serialize the generation.

What alternative(s) have you considered?

Using to_bits but this would require customizing serialization of any component storing an Entity and seems to violate the documentation for this method.

Activity

  1. added
    C-FeatureA new feature, making something new possible
    S-Needs-TriageThis issue needs to be labelled
    on Sep 9, 2021
  2. bjorn3 commented on Sep 9, 2021

    @bjorn3
    Contributor

    The generation is only unique within a single run of the game. If you restart it, the generation starts at the beginning again, so saving the generation won't help much for save games.

  3. Nilirad commented on Sep 9, 2021

    @Nilirad
    Contributor

    Entity is an opaque handle type, therefore it's value is not supposed to be known from client code. Serializing and deserializing Entity across different runs will probably result in UB.

    I think scenes are better suited for state saves.

  4. Sydius commented on Sep 9, 2021

    @Sydius
    Author

    The generation is only unique within a single run of the game. If you restart it, the generation starts at the beginning again, so saving the generation won't help much for save games.

    Ah, right, I suppose it can't know about all the Entitys that are not saved, even if it only ever incremented those respawned with get_or_spawn. So, even if the generation was serialized, it would only help prevent collisions as of when the game state was loaded and not after additional entities are spawned. I'm not sure if there's a sensible way to address that, that I can think of.

    Serializing and deserializing Entity across different runs will probably result in UB.

    Even if care is given to ensure no dangling Entitys exist in any component? If so, I suppose the only recourse I have is to save everything then, upon reloading, do a fix-up pass to remap the entities to their new IDs after ditching get_or_spawn() in favor of spawn(). Note that Serialize and Deserialize is implemented for Entity, but I suppose there could be uses for that within a single run.

    I think scenes are better suited for state saves.

    In my case, I'm using bevy_ecs on its own, so I don't have scenes to work with (at least, I don't think I do).

    In any case, perhaps this feature request isn't viable and I'll have to do a bunch of work to work around this somehow.

  5. cart commented on Sep 10, 2021

    @cart
    Member

    Theres basically no valid use case for get_or_spawn outside of "sharing entities between worlds with a scheme that prevents id collisions" (as the doc comment warns about). This isn't something that scenes can or should do. If you need a unique permanent ID, i recommend creating a new component for it / maintaining your own ids. As others have said, entity IDs should be treated as opaque runtime state.

    get_or_spawn is used to enable the "extract entity pattern" in the new renderer, but outside of that context it is niche enough that we might even want to find a way to hide it to prevent people from trying to use it.

    Some history: Entity ids used to be "globally unique" to enable cases like this (and other things like shared-id space networking). We generated a new random id for each entity. The problem was that small random ids have collision risk and large random ids are expensive to generate. Additionally random ids are more expensive because you need to hash them instead of using an array lookup. Doing "globally unique" ids ended up being way too expensive. And by making them big enough to avoid collision risk, we also made them too big to be good "networked entity ids". It was a mess. By treating entity ids as opaque runtime state, everything can be fast and collision free by default. And if you need a stable id, you can always create one yourself as a component and choose the id scheme that works best for your specific scenario.

  6. Sydius commented on Sep 10, 2021

    @Sydius
    Author

    Yeah, that all makes sense. It's a hard problem. The irony is that I started out my game without Bevy ECS and it was when I started making my own Entity that I reached for Bevy, since I was so impressed with the rest of the API. I missed that it was missing these properties.

    The problem with rolling my own ID as a component is that I now have the problem of efficiently querying for the thing with the correct ID. I may be doing a whole bunch of bad practices here, perhaps, but what I do is have, as an example, NPCs with an Entity pointing to the map they're on, and objects with an Entity of their container (and vice-versa). To use my own ID efficiently, I'd have to recreate the guts of the Entity tables and replicate much of it.

    I may just abandon serializing components directly and instead make a serializable version that replaces these with my own IDs, then when loading everything back in, replace them in the other direction with the result of spawn(). I suppose I could also just fork Bevy ECS and hack it up so that I can dump/restore the state of the tables across runs, but then I won't ever be able to upgrade. All the options seem like a bit of a PITA.

    At any rate, I certainly won't be offended if you want to close this as won't-fix; I'm convinced it's not viable. Perhaps, instead, beef up the docs around Entity and especially around how the serialization/deserialization is to be used (since it's implemented)? Or maybe have some kind of best practices guide for coordinating across client/server or game runs Entity relationships?

    Thanks much for all the responses.

  7. minecrawler commented on Sep 10, 2021

    @minecrawler

    I think the issue is known and there is a RFC for entity relationships. Maybe you can help get an adequate solution into bevy by sharing your thoughts/architecture/experiences/implementation there 🙂

  8. added
    A-ScenesComposing and serializing ECS objects
    C-UsabilityA targeted quality-of-life change that makes Bevy easier to use
    S-WontfixThis issue is the result of a deliberate design decision, and will not be fixed
    and removed
    C-FeatureA new feature, making something new possible
    S-Needs-TriageThis issue needs to be labelled
    on Sep 14, 2021
  9. alice-i-cecile commented on Sep 14, 2021

    @alice-i-cecile
    Member

    Closing this out; I think Cart's solution given above is the best we've got for now. More docs and experience around these complex areas are absolutely needed; I suspect this broad area is going to continue to come up repeatedly.

  10. cart commented on Sep 16, 2021

    @cart
    Member

    Yeah I think closing this is the right call.

    To use my own ID efficiently, I'd have to recreate the guts of the Entity tables and replicate much of it.

    In fairness, if we reverted to the old "hashed UUID impl", you'd still have the "slower" lookup, it would just be internal. By doing this externally with a userspace HashMap<UUID, Entity>, its just a single extra array lookup for the internal Entity->EntityLocation lookup, which in the grand scheme of things isn't huge. You could keep the mapping up to date with a system that looks for new changed Uuid components and updates the map.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ScenesComposing and serializing ECS objectsC-UsabilityA targeted quality-of-life change that makes Bevy easier to useS-WontfixThis issue is the result of a deliberate design decision, and will not be fixed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions