-
Notifications
You must be signed in to change notification settings - Fork 220
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
Add an ability to save and load entities. #275
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
use std::fmt::{self, Display, Formatter}; | ||
use std::marker::PhantomData; | ||
|
||
use serde::de::{self, Deserialize, DeserializeSeed, Deserializer, SeqAccess, Visitor}; | ||
|
||
use {Entities, FetchMut, WriteStorage}; | ||
|
||
use saveload::{Components, EntityData, Storages}; | ||
use saveload::marker::{Marker, MarkerAllocator}; | ||
|
||
|
||
/// Wrapper for `Entity` and tuple of `WriteStorage`s that implements `serde::Deserialize` | ||
struct DeserializeEntity<'a, 'b: 'a, M: Marker, E, T: Components<M::Identifier, E>> { | ||
entities: &'a Entities<'b>, | ||
storages: &'a mut <T as Storages<'b>>::WriteStorages, | ||
markers: &'a mut WriteStorage<'b, M>, | ||
allocator: &'a mut FetchMut<'b, M::Allocator>, | ||
pd: PhantomData<(E, T)>, | ||
} | ||
|
||
impl<'de, 'a, 'b: 'a, M, E, T> DeserializeSeed<'de> for DeserializeEntity<'a, 'b, M, E, T> | ||
where | ||
M: Marker, | ||
E: Display, | ||
T: Components<M::Identifier, E>, | ||
{ | ||
type Value = (); | ||
fn deserialize<D>(self, deserializer: D) -> Result<(), D::Error> | ||
where | ||
D: Deserializer<'de>, | ||
{ | ||
let DeserializeEntity { | ||
entities, | ||
storages, | ||
markers, | ||
allocator, | ||
.. | ||
} = self; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: why do you need to deconstruct There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Otherwise whole |
||
let data = EntityData::<M, E, T>::deserialize(deserializer)?; | ||
let entity = allocator.get_marked(data.marker.id(), entities, markers); | ||
markers | ||
.get_mut(entity) | ||
.ok_or("Allocator is broken") | ||
.map_err(de::Error::custom)? | ||
.update(data.marker); | ||
let ids = |marker: M::Identifier| Some(allocator.get_marked(marker, entities, markers)); | ||
match T::load(entity, data.components, storages, ids) { | ||
Ok(()) => Ok(()), | ||
Err(err) => Err(de::Error::custom(err)), | ||
} | ||
} | ||
} | ||
|
||
/// Wrapper for `Entities` and tuple of `WriteStorage`s that implements `serde::de::Visitor` | ||
struct VisitEntities<'a, 'b: 'a, M: Marker, E, T: Components<M::Identifier, E>> { | ||
entities: &'a Entities<'b>, | ||
storages: &'a mut <T as Storages<'b>>::WriteStorages, | ||
markers: &'a mut WriteStorage<'b, M>, | ||
allocator: &'a mut FetchMut<'b, M::Allocator>, | ||
pd: PhantomData<(E, T)>, | ||
} | ||
|
||
impl<'de, 'a, 'b: 'a, M, E, T> Visitor<'de> for VisitEntities<'a, 'b, M, E, T> | ||
where | ||
M: Marker, | ||
E: Display, | ||
T: Components<M::Identifier, E>, | ||
{ | ||
type Value = (); | ||
|
||
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { | ||
write!(formatter, "Sequence of serialized entities") | ||
} | ||
|
||
fn visit_seq<A>(mut self, mut seq: A) -> Result<(), A::Error> | ||
where | ||
A: SeqAccess<'de>, | ||
{ | ||
while seq.next_element_seed(DeserializeEntity { | ||
entities: self.entities, | ||
storages: self.storages, | ||
markers: self.markers, | ||
allocator: self.allocator, | ||
pd: self.pd, | ||
})? | ||
.is_some() | ||
{} | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
|
||
/// Deserialize entities | ||
pub fn deserialize<'a, 'de, D, M, E, T>( | ||
entities: &Entities<'a>, | ||
storages: &mut <T as Storages<'a>>::WriteStorages, | ||
markers: &mut WriteStorage<'a, M>, | ||
allocator: &mut FetchMut<'a, M::Allocator>, | ||
deserializer: D, | ||
) -> Result<(), D::Error> | ||
where | ||
M: Marker, | ||
E: Display, | ||
T: Components<M::Identifier, E>, | ||
D: Deserializer<'de>, | ||
{ | ||
deserializer.deserialize_seq(VisitEntities::<M, E, T> { | ||
entities, | ||
storages, | ||
markers, | ||
allocator, | ||
pd: PhantomData, | ||
}) | ||
} | ||
|
||
|
||
/// `DeerializeSeed` implementation for `World` | ||
#[derive(SystemData)] | ||
pub struct WorldDeserialize<'a, M: Marker, E, T: Components<M::Identifier, E>> { | ||
entities: Entities<'a>, | ||
storages: <T as Storages<'a>>::WriteStorages, | ||
markers: WriteStorage<'a, M>, | ||
allocator: FetchMut<'a, M::Allocator>, | ||
pd: PhantomData<E>, | ||
} | ||
|
||
impl<'de, 'a, M, E, T> DeserializeSeed<'de> for WorldDeserialize<'a, M, E, T> | ||
where | ||
M: Marker, | ||
E: Display, | ||
T: Components<M::Identifier, E>, | ||
{ | ||
type Value = (); | ||
|
||
fn deserialize<D>(mut self, deserializer: D) -> Result<(), D::Error> | ||
where | ||
D: Deserializer<'de>, | ||
{ | ||
deserialize::<D, M, E, T>( | ||
&mut self.entities, | ||
&mut self.storages, | ||
&mut self.markers, | ||
&mut self.allocator, | ||
deserializer, | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
use std::error::Error; | ||
|
||
use serde::de::DeserializeOwned; | ||
use serde::ser::Serialize; | ||
|
||
use {Component, Entity, ReadStorage, SystemData, WriteStorage}; | ||
|
||
use error::NoError; | ||
use saveload::marker::Marker; | ||
|
||
#[derive(Serialize, Deserialize)] | ||
#[serde(bound = "")] | ||
pub struct EntityData<M: Marker, E, T: Components<M::Identifier, E>> { | ||
pub marker: M, | ||
pub components: T::Data, | ||
} | ||
|
||
/// This trait should be implemented in order to allow component | ||
/// to be serializeble with `SerializeSystem`. | ||
/// It is automatically implemented for all | ||
/// `Component + DeserializeOwned + Serialize + Copy` | ||
pub trait SaveLoadComponent<M>: Component { | ||
/// Serializable data representation for component | ||
type Data: Serialize + DeserializeOwned; | ||
|
||
/// Error may occur duing serialization or deserialization of component | ||
type Error: Error; | ||
|
||
/// Convert this component into serializable form (`Data`) using | ||
/// entity to marker mapping function | ||
fn save<F>(&self, ids: F) -> Result<Self::Data, Self::Error> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm, on the other hand, |
||
where | ||
F: FnMut(Entity) -> Option<M>; | ||
|
||
/// Convert this component into deserializable form (`Data`) using | ||
/// marker to entity mapping function | ||
fn load<F>(data: Self::Data, ids: F) -> Result<Self, Self::Error> | ||
where | ||
F: FnMut(M) -> Option<Entity>; | ||
} | ||
|
||
impl<C, M> SaveLoadComponent<M> for C | ||
where | ||
C: Component + DeserializeOwned + Serialize + Copy, | ||
{ | ||
type Data = Self; | ||
type Error = NoError; | ||
|
||
fn save<F>(&self, _ids: F) -> Result<Self::Data, NoError> { | ||
Ok(*self) | ||
} | ||
|
||
fn load<F>(data: Self, _ids: F) -> Result<Self, NoError> { | ||
Ok(data) | ||
} | ||
} | ||
|
||
/// Helper trait defines storages tuples for components tuple | ||
pub trait Storages<'a> { | ||
/// Storages for read | ||
type ReadStorages: SystemData<'a> + 'a; | ||
/// Storages for write | ||
type WriteStorages: SystemData<'a> + 'a; | ||
} | ||
|
||
/// This trait is implemented by any tuple where all elements are | ||
/// `Component + Serialize + DeserializeOwned` | ||
pub trait Components<M, E>: for<'a> Storages<'a> { | ||
/// Serializable and deserializable intermediate representation | ||
type Data: Serialize + DeserializeOwned; | ||
|
||
/// Saves `Component`s of entity into `Data` serializable representation | ||
fn save<'a, F>( | ||
entity: Entity, | ||
storages: &<Self as Storages<'a>>::ReadStorages, | ||
ids: F, | ||
) -> Result<Self::Data, E> | ||
where | ||
F: FnMut(Entity) -> Option<M>; | ||
|
||
/// Loads `Component`s to entity from `Data` deserializable representation | ||
fn load<'a, F>( | ||
entity: Entity, | ||
components: Self::Data, | ||
storages: &mut <Self as Storages<'a>>::WriteStorages, | ||
ids: F, | ||
) -> Result<(), E> | ||
where | ||
F: FnMut(M) -> Option<Entity>; | ||
} | ||
|
||
macro_rules! impl_components { | ||
($($a:ident|$b:ident),*) => { | ||
impl<'a, $($a),*> Storages<'a> for ($($a,)*) | ||
where $( | ||
$a: Component, | ||
)* | ||
{ | ||
type ReadStorages = ($(ReadStorage<'a, $a>,)*); | ||
type WriteStorages = ($(WriteStorage<'a, $a>,)*); | ||
} | ||
|
||
impl<M, E $(,$a)*> Components<M, E> for ($($a,)*) | ||
where $( | ||
$a: SaveLoadComponent<M>, | ||
E: From<$a::Error>, | ||
)* | ||
{ | ||
type Data = ($(Option<$a::Data>,)*); | ||
|
||
#[allow(unused_variables, unused_mut, non_snake_case)] | ||
fn save<'a, F>(entity: Entity, storages: &($(ReadStorage<'a, $a>,)*), mut ids: F) | ||
-> Result<($(Option<$a::Data>,)*), E> | ||
where F: FnMut(Entity) -> Option<M> | ||
{ | ||
let ($(ref $b,)*) = *storages; | ||
Ok(($( | ||
$b.get(entity).map(|c| c.save(&mut ids).map(Some)).unwrap_or(Ok(None))?, | ||
)*)) | ||
} | ||
|
||
#[allow(unused_variables, unused_mut, non_snake_case)] | ||
fn load<'a, F>(entity: Entity, components: ($(Option<$a::Data>,)*), | ||
storages: &mut ($(WriteStorage<'a, $a>,)*), mut ids: F) | ||
-> Result<(), E> | ||
where F: FnMut(M) -> Option<Entity> | ||
{ | ||
let ($($a,)*) = components; | ||
let ($(ref mut $b,)*) = *storages; | ||
$( | ||
if let Some(a) = $a { | ||
$b.insert(entity, $a::load(a, &mut ids)?); | ||
} else { | ||
$b.remove(entity); | ||
} | ||
)* | ||
Ok(()) | ||
} | ||
} | ||
|
||
// Recursivly implement for smaller tuple | ||
impl_components!(@ $($a|$b),*); | ||
}; | ||
|
||
// List depleted. End of recursion | ||
(@) => {}; | ||
|
||
// Cut head of the list and call macro again | ||
(@ $ah:ident|$bh:ident $(,$a:ident|$b:ident)*) => { | ||
// Call again for tail | ||
impl_components!($($a|$b),*); | ||
}; | ||
} | ||
|
||
impl_components!( | ||
LA | LB, | ||
MA | MB, | ||
NA | NB, | ||
OA | OB, | ||
PA | PB, | ||
QA | QB, | ||
RA | RB, | ||
SA | SB, | ||
TA | TB, | ||
UA | UB, | ||
VA | VB, | ||
WA | WB, | ||
XA | XB, | ||
YA | YB, | ||
ZA | ZB | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be
PhantomError
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be
!
but it isn't stable yet.Why
PhantomError
anyway?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, first, because you are implementing
Error
forNoError
, which already looks like a hack. Secondly, because the "phantom" is rather established for such things that can't be instantiated, soPhantomError
is crystal clear here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PhantomData
is unit type and can be instantiated. And it is an example fromstd
.IDK any other examples.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a strong opinion here, @kvark do you insist on this request?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope. I think I might have confused a bit the semantics of phantom types. Pretty sure I saw them used in the context where one couldn't create instances (
enum MyPhantomType {}
), but I don't want to push hard on that here. It's fine to ship as is.