From 4b61bbe4e1b364a74e706c3dd4ee06659301211d Mon Sep 17 00:00:00 2001 From: Sean Sullivan Date: Fri, 10 May 2024 06:00:08 -0400 Subject: [PATCH] bevy_core: Derive useful traits on FrameCount (#13291) # Objective I am emboldened by my last small PR and am here with another. - **Describe the objective or issue this PR addresses.** It would be nice if `FrameCount` could be used by downstream plugins that want to use frame data. The example that I have in mind is [`leafwing_input_playback`](https://github.com/Leafwing-Studios/leafwing_input_playback/issues/29) which has a [duplicate implementation of `FrameCount`](https://github.com/Leafwing-Studios/leafwing_input_playback/blob/main/src/frame_counting.rs#L9-L37) used in several structs which rely on those derives (or otherwise the higher-level structs would have to implement these traits manually). That crate, using `FrameCount`, tracks input frames and timestamps and enables various playback modes. I am aware that bevy org refrains from deriving lots of unnecessary stuff on bevy types to avoid compile time creep. It is worth mentioning the (equally reasonable) alternative that downstream crates _should_ implement some `FrameCount` themselves if they want special behavior from it. ## Solution - **Describe the solution used to achieve the objective above.** I added derives for `PartialEq, Eq, PartialOrd, Ord` and implementations for `serde::{Deserialize, Serialize}` to `FrameCount`. ## Testing Manually confirmed that the serde implementation works, but that's all. Let me know if I should do more here. --------- Co-authored-by: Alice Cecile --- crates/bevy_core/Cargo.toml | 1 + crates/bevy_core/src/lib.rs | 2 +- crates/bevy_core/src/serde.rs | 50 +++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/crates/bevy_core/Cargo.toml b/crates/bevy_core/Cargo.toml index d149581ebb..bbf51137c7 100644 --- a/crates/bevy_core/Cargo.toml +++ b/crates/bevy_core/Cargo.toml @@ -32,6 +32,7 @@ serialize = ["dep:serde"] [dev-dependencies] crossbeam-channel = "0.5.0" +serde_test = "1.0" [lints] workspace = true diff --git a/crates/bevy_core/src/lib.rs b/crates/bevy_core/src/lib.rs index 073460b3b3..dc93ddf2c0 100644 --- a/crates/bevy_core/src/lib.rs +++ b/crates/bevy_core/src/lib.rs @@ -80,7 +80,7 @@ fn tick_global_task_pools(_main_thread_marker: Option>) { /// [`FrameCount`] will wrap to 0 after exceeding [`u32::MAX`]. Within reasonable /// assumptions, one may exploit wrapping arithmetic to determine the number of frames /// that have elapsed between two observations – see [`u32::wrapping_sub()`]. -#[derive(Debug, Default, Resource, Clone, Copy)] +#[derive(Debug, Default, Resource, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct FrameCount(pub u32); /// Adds frame counting functionality to Apps. diff --git a/crates/bevy_core/src/serde.rs b/crates/bevy_core/src/serde.rs index f8835e9a5f..fc4d81b4bd 100644 --- a/crates/bevy_core/src/serde.rs +++ b/crates/bevy_core/src/serde.rs @@ -9,6 +9,7 @@ use serde::{ }; use super::name::Name; +use super::FrameCount; impl Serialize for Name { fn serialize(&self, serializer: S) -> Result { @@ -39,3 +40,52 @@ impl<'de> Visitor<'de> for EntityVisitor { Ok(Name::new(v)) } } + +// Manually implementing serialize/deserialize allows us to use a more compact representation as simple integers +impl Serialize for FrameCount { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_u32(self.0) + } +} + +impl<'de> Deserialize<'de> for FrameCount { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_u32(FrameVisitor) + } +} + +struct FrameVisitor; + +impl<'de> Visitor<'de> for FrameVisitor { + type Value = FrameCount; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str(any::type_name::()) + } + + fn visit_u32(self, v: u32) -> Result + where + E: Error, + { + Ok(FrameCount(v)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use serde_test::{assert_tokens, Token}; + + #[test] + fn test_serde_name() { + let name = Name::new("MyComponent"); + assert_tokens(&name, &[Token::String("MyComponent")]); + } + + #[test] + fn test_serde_frame_count() { + let frame_count = FrameCount(100); + assert_tokens(&frame_count, &[Token::U32(100)]); + } +}