Use default serde impls for Entity (#6194)
# Objective
Currently for entities we serialize only `id`. But this is not very expected behavior. For example, in networking, when the server sends its state, it contains entities and components. On the client, I create new objects and map them (using `EntityMap`) to those received from the server (to know which one matches which). And if `generation` field is missing, this mapping can be broken. Example:
1. Server sends an entity `Entity{ id: 2, generation: 1}` with components.
2. Client puts the received entity in a map and create a new entity that maps to this received entity. The new entity have different `id` and `generation`. Let's call it `Entity{ id: 12, generation: 4}`.
3. Client sends a command for `Entity{ id: 12, generation: 4}`. To do so, it maps local entity to the one from server. But `generation` field is 0 because it was omitted for serialization on the server. So it maps to `Entity{ id: 2, generation: 0}`.
4. Server receives `Entity{ id: 2, generation: 0}` which is invalid.
In my game I worked around it by [writing custom serialization](https://github.com/dollisgame/dollis/blob/master/src/core/network/entity_serde.rs) and using `serde(with = "...")`. But it feels like a bad default to me.
Using `Entity` over a custom `NetworkId` also have the following advantages:
1. Re-use `MapEntities` trait to map `Entity`s in replicated components.
2. Instead of server `Entity <-> NetworkId ` and `Entity <-> NetworkId`, we map entities only on client.
3. No need to handling uniqueness. It's a rare case, but makes things simpler. For example, I don't need to query for a resource to create an unique ID.
Closes #6143.
## Solution
Use default serde impls. If anyone want to avoid wasting memory on `generation`, they can create a new type that holds `u32`. This is what Bevy do for [DynamicEntity](https://docs.rs/bevy/latest/bevy/scene/struct.DynamicEntity.html) to serialize scenes. And I don't see any use case to serialize an entity id expect this one.
---
## Changelog
### Changed
- Entity now serializes / deserializes `generation` field.
## Migration Guide
- Entity now fully serialized. If you want to serialze only `id`, as it was before, you can create a new type that wraps `u32`.
This commit is contained in:
parent
d8bf5f8224
commit
71f8b4a92f
@ -26,7 +26,7 @@ thread_local = "1.1.4"
|
|||||||
fixedbitset = "0.4"
|
fixedbitset = "0.4"
|
||||||
fxhash = "0.2"
|
fxhash = "0.2"
|
||||||
downcast-rs = "1.2"
|
downcast-rs = "1.2"
|
||||||
serde = "1"
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
|||||||
@ -31,12 +31,11 @@
|
|||||||
//! [`EntityMut::insert`]: crate::world::EntityMut::insert
|
//! [`EntityMut::insert`]: crate::world::EntityMut::insert
|
||||||
//! [`EntityMut::remove`]: crate::world::EntityMut::remove
|
//! [`EntityMut::remove`]: crate::world::EntityMut::remove
|
||||||
mod map_entities;
|
mod map_entities;
|
||||||
mod serde;
|
|
||||||
|
|
||||||
pub use self::serde::*;
|
|
||||||
pub use map_entities::*;
|
pub use map_entities::*;
|
||||||
|
|
||||||
use crate::{archetype::ArchetypeId, storage::SparseSetIndex};
|
use crate::{archetype::ArchetypeId, storage::SparseSetIndex};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{convert::TryFrom, fmt, mem, sync::atomic::Ordering};
|
use std::{convert::TryFrom, fmt, mem, sync::atomic::Ordering};
|
||||||
|
|
||||||
#[cfg(target_has_atomic = "64")]
|
#[cfg(target_has_atomic = "64")]
|
||||||
@ -104,7 +103,7 @@ type IdCursor = isize;
|
|||||||
/// [`EntityMut::id`]: crate::world::EntityMut::id
|
/// [`EntityMut::id`]: crate::world::EntityMut::id
|
||||||
/// [`EntityCommands`]: crate::system::EntityCommands
|
/// [`EntityCommands`]: crate::system::EntityCommands
|
||||||
/// [`Query::get`]: crate::system::Query::get
|
/// [`Query::get`]: crate::system::Query::get
|
||||||
#[derive(Clone, Copy, Hash, Eq, Ord, PartialEq, PartialOrd)]
|
#[derive(Clone, Copy, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
|
||||||
pub struct Entity {
|
pub struct Entity {
|
||||||
pub(crate) generation: u32,
|
pub(crate) generation: u32,
|
||||||
pub(crate) id: u32,
|
pub(crate) id: u32,
|
||||||
|
|||||||
@ -1,44 +0,0 @@
|
|||||||
use crate::entity::Entity;
|
|
||||||
use serde::{de::Visitor, Deserialize, Serialize, Serializer};
|
|
||||||
|
|
||||||
impl Serialize for Entity {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_u32(self.id())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Entity {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
deserializer.deserialize_u32(EntityVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EntityVisitor;
|
|
||||||
|
|
||||||
impl<'de> Visitor<'de> for EntityVisitor {
|
|
||||||
type Value = Entity;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
formatter.write_str("Entity")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
Ok(Entity::from_raw(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
Ok(Entity::from_raw(v as u32))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user