From 2a8527e2cd4709eb22fa2e453e251cc71687af56 Mon Sep 17 00:00:00 2001 From: Kent Ross Date: Tue, 6 Feb 2024 09:53:22 -0800 Subject: [PATCH] Corrections to prost benchmark (#62) * add round-trip guards to prost proving that it is not serializing all of the "logs" bench More serializers should be forced to check this! the data they serialized should be fully recoverable! * Fix prost serialization of Log this significantly slows its encoding in this bench and increases its encoded/compressed size by 15/38% * Run `cargo fmt` --------- Co-authored-by: David Koloski --- src/bench_prost.rs | 10 +- src/datasets/log/mod.rs | 59 +++++++-- src/datasets/mesh/mod.rs | 38 +++++- src/datasets/minecraft_savedata/mod.rs | 164 +++++++++++++++++++++++-- src/datasets/mk48/mod.rs | 122 ++++++++++++++++-- 5 files changed, 361 insertions(+), 32 deletions(-) diff --git a/src/bench_prost.rs b/src/bench_prost.rs index 32a806f..2d47d0b 100644 --- a/src/bench_prost.rs +++ b/src/bench_prost.rs @@ -1,15 +1,15 @@ use criterion::{black_box, Criterion}; use prost::Message; -pub trait Serialize { - type Message: Default + Message; +pub trait Serialize: Sized { + type Message: Default + Into + Message; fn serialize_pb(&self) -> Self::Message; } pub fn bench(name: &'static str, c: &mut Criterion, data: &T) where - T: Serialize, + T: Serialize + PartialEq, { const BUFFER_LEN: usize = 10_000_000; @@ -39,11 +39,13 @@ where group.bench_function("deserialize", |b| { b.iter(|| { - black_box(T::Message::decode(black_box(&deserialize_buffer).as_slice()).unwrap()); + black_box(::decode(black_box(&deserialize_buffer).as_slice()).unwrap()); }) }); crate::bench_size(name, "prost", deserialize_buffer.as_slice()); + assert!(&::decode(&*deserialize_buffer).unwrap().into() == data); + group.finish(); } diff --git a/src/datasets/log/mod.rs b/src/datasets/log/mod.rs index 7945a63..6c2afc5 100644 --- a/src/datasets/log/mod.rs +++ b/src/datasets/log/mod.rs @@ -31,7 +31,7 @@ use crate::bench_flatbuffers; use crate::bench_prost; use crate::Generate; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -114,6 +114,18 @@ impl bench_prost::Serialize for Address { } } +#[cfg(feature = "prost")] +impl From for Address { + fn from(value: log_prost::Address) -> Self { + Address { + x0: value.x0.try_into().unwrap(), + x1: value.x1.try_into().unwrap(), + x2: value.x2.try_into().unwrap(), + x3: value.x3.try_into().unwrap(), + } + } +} + #[cfg(feature = "alkahest")] impl alkahest::Pack
for Address { #[inline] @@ -128,7 +140,7 @@ impl alkahest::Pack
for Address { } } -#[derive(Clone)] +#[derive(Clone, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -310,14 +322,30 @@ impl bench_prost::Serialize for Log { #[inline] fn serialize_pb(&self) -> Self::Message { - let mut result = Self::Message::default(); - result.identity = self.identity.clone(); - result.userid = self.userid.clone(); - result.date = self.date.clone(); - result.request = self.request.clone(); - result.code = self.code as u32; - result.size = self.size; - result + log_prost::Log { + address: Some(self.address.serialize_pb()), + identity: self.identity.clone(), + userid: self.userid.clone(), + date: self.date.clone(), + request: self.request.clone(), + code: self.code as u32, + size: self.size, + } + } +} + +#[cfg(feature = "prost")] +impl From for Log { + fn from(value: log_prost::Log) -> Self { + Log { + address: value.address.unwrap().into(), + identity: value.identity, + userid: value.userid, + date: value.date, + request: value.request, + code: value.code.try_into().unwrap(), + size: value.size, + } } } @@ -338,7 +366,7 @@ impl alkahest::Pack for &'_ Log { } } -#[derive(Clone)] +#[derive(Clone, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -429,6 +457,15 @@ impl bench_prost::Serialize for Logs { } } +#[cfg(feature = "prost")] +impl From for Logs { + fn from(value: log_prost::Logs) -> Self { + Logs { + logs: value.logs.into_iter().map(Into::into).collect(), + } + } +} + #[cfg(feature = "alkahest")] #[derive(alkahest::Schema)] pub struct LogsSchema { diff --git a/src/datasets/mesh/mod.rs b/src/datasets/mesh/mod.rs index 2460bad..c7218ad 100644 --- a/src/datasets/mesh/mod.rs +++ b/src/datasets/mesh/mod.rs @@ -29,7 +29,7 @@ use crate::bench_flatbuffers; use crate::bench_prost; use crate::Generate; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -109,6 +109,17 @@ impl bench_prost::Serialize for Vector3 { } } +#[cfg(feature = "prost")] +impl From for Vector3 { + fn from(value: mesh_prost::Vector3) -> Self { + Vector3 { + x: value.x, + y: value.y, + z: value.z, + } + } +} + #[cfg(feature = "alkahest")] impl alkahest::Pack for Vector3 { #[inline] @@ -122,7 +133,7 @@ impl alkahest::Pack for Vector3 { } } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -211,6 +222,18 @@ impl bench_prost::Serialize for Triangle { } } +#[cfg(feature = "prost")] +impl From for Triangle { + fn from(value: mesh_prost::Triangle) -> Self { + Triangle { + v0: value.v0.unwrap().into(), + v1: value.v1.unwrap().into(), + v2: value.v2.unwrap().into(), + normal: value.normal.unwrap().into(), + } + } +} + #[cfg(feature = "alkahest")] impl alkahest::Pack for &'_ Triangle { #[inline] @@ -225,7 +248,7 @@ impl alkahest::Pack for &'_ Triangle { } } -#[derive(Clone)] +#[derive(Clone, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -318,6 +341,15 @@ impl bench_prost::Serialize for Mesh { } } +#[cfg(feature = "prost")] +impl From for Mesh { + fn from(value: mesh_prost::Mesh) -> Self { + Mesh { + triangles: value.triangles.into_iter().map(Into::into).collect(), + } + } +} + #[cfg(feature = "alkahest")] #[derive(alkahest::Schema)] pub struct MeshSchema { diff --git a/src/datasets/minecraft_savedata/mod.rs b/src/datasets/minecraft_savedata/mod.rs index a2f9ac9..611f0d0 100644 --- a/src/datasets/minecraft_savedata/mod.rs +++ b/src/datasets/minecraft_savedata/mod.rs @@ -31,7 +31,7 @@ use crate::bench_flatbuffers; use crate::bench_prost; use crate::{generate_vec, Generate}; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -118,6 +118,18 @@ impl Into for GameType { } } +#[cfg(feature = "prost")] +impl From for GameType { + fn from(value: pb::GameType) -> Self { + match value { + pb::GameType::Survival => GameType::Survival, + pb::GameType::Creative => GameType::Creative, + pb::GameType::Adventure => GameType::Adventure, + pb::GameType::Spectator => GameType::Spectator, + } + } +} + #[cfg(feature = "alkahest")] impl alkahest::Pack for GameType { #[inline] @@ -131,7 +143,7 @@ impl alkahest::Pack for GameType { } } -#[derive(Clone)] +#[derive(Clone, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -232,6 +244,17 @@ impl bench_prost::Serialize for Item { } } +#[cfg(feature = "prost")] +impl From for Item { + fn from(value: pb::Item) -> Self { + Item { + count: value.count.try_into().unwrap(), + slot: value.slot.try_into().unwrap(), + id: value.id, + } + } +} + #[cfg(feature = "alkahest")] #[derive(alkahest::Schema)] pub struct ItemSchema { @@ -253,7 +276,7 @@ impl alkahest::Pack for &'_ Item { } } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -358,6 +381,21 @@ impl bench_prost::Serialize for Abilities { } } +#[cfg(feature = "prost")] +impl From for Abilities { + fn from(value: pb::Abilities) -> Self { + Abilities { + walk_speed: value.walk_speed, + fly_speed: value.fly_speed, + may_fly: value.may_fly, + flying: value.flying, + invulnerable: value.invulnerable, + may_build: value.may_build, + instabuild: value.instabuild, + } + } +} + #[cfg(feature = "alkahest")] impl alkahest::Pack for Abilities { #[inline] @@ -375,7 +413,7 @@ impl alkahest::Pack for Abilities { } } -#[derive(Clone)] +#[derive(Clone, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -594,6 +632,51 @@ impl bench_prost::Serialize for Entity { } } +#[cfg(feature = "prost")] +impl From for (f64, f64, f64) { + fn from(value: pb::Vector3d) -> Self { + (value.x, value.y, value.z) + } +} + +#[cfg(feature = "prost")] +impl From for (f32, f32) { + fn from(value: pb::Vector2f) -> Self { + (value.x, value.y) + } +} + +#[cfg(feature = "prost")] +impl From for [u32; 4] { + fn from(value: pb::Uuid) -> Self { + [value.x0, value.x1, value.x2, value.x3] + } +} + +#[cfg(feature = "prost")] +impl From for Entity { + fn from(value: pb::Entity) -> Self { + Entity { + id: value.id, + pos: value.pos.unwrap().into(), + motion: value.motion.unwrap().into(), + rotation: value.rotation.unwrap().into(), + fall_distance: value.fall_distance, + fire: value.fire.try_into().unwrap(), + air: value.air.try_into().unwrap(), + on_ground: value.on_ground, + no_gravity: value.no_gravity, + invulnerable: value.invulnerable, + portal_cooldown: value.portal_cooldown, + uuid: value.uuid.unwrap().into(), + custom_name: value.custom_name, + custom_name_visible: value.custom_name_visible, + silent: value.silent, + glowing: value.glowing, + } + } +} + #[cfg(feature = "alkahest")] #[derive(alkahest::Schema)] pub struct EntitySchema { @@ -641,7 +724,7 @@ impl alkahest::Pack for &'_ Entity { } } -#[derive(Clone)] +#[derive(Clone, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -812,6 +895,24 @@ impl bench_prost::Serialize for RecipeBook { } } +#[cfg(feature = "prost")] +impl From for RecipeBook { + fn from(value: pb::RecipeBook) -> Self { + RecipeBook { + recipes: value.recipes, + to_be_displayed: value.to_be_displayed, + is_filtering_craftable: value.is_filtering_craftable, + is_gui_open: value.is_gui_open, + is_furnace_filtering_craftable: value.is_furnace_filtering_craftable, + is_furnace_gui_open: value.is_furnace_gui_open, + is_blasting_furnace_filtering_craftable: value.is_blasting_furnace_filtering_craftable, + is_blasting_furnace_gui_open: value.is_blasting_furnace_gui_open, + is_smoker_filtering_craftable: value.is_smoker_filtering_craftable, + is_smoker_gui_open: value.is_smoker_gui_open, + } + } +} + #[cfg(feature = "alkahest")] #[derive(alkahest::Schema)] pub struct RecipeBookSchema { @@ -847,7 +948,7 @@ impl alkahest::Pack for &'_ RecipeBook { } } -#[derive(Clone)] +#[derive(Clone, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -1221,6 +1322,46 @@ impl bench_prost::Serialize for Player { } } +#[cfg(feature = "prost")] +impl From for Player { + fn from(value: pb::Player) -> Self { + Player { + game_type: pb::GameType::try_from(value.game_type).unwrap().into(), + previous_game_type: pb::GameType::try_from(value.previous_game_type) + .unwrap() + .into(), + score: value.score, + dimension: value.dimension, + selected_item_slot: value.selected_item_slot, + selected_item: value.selected_item.unwrap().into(), + spawn_dimension: value.spawn_dimension, + spawn_x: value.spawn_x, + spawn_y: value.spawn_y, + spawn_z: value.spawn_z, + spawn_forced: value.spawn_forced, + sleep_timer: value.sleep_timer.try_into().unwrap(), + food_exhaustion_level: value.food_exhaustion_level, + food_saturation_level: value.food_saturation_level, + food_tick_timer: value.food_tick_timer, + xp_level: value.xp_level, + xp_p: value.xp_p, + xp_total: value.xp_total, + xp_seed: value.xp_seed, + inventory: value.inventory.into_iter().map(Into::into).collect(), + ender_items: value.ender_items.into_iter().map(Into::into).collect(), + abilities: value.abilities.unwrap().into(), + entered_nether_position: value.entered_nether_position.map(Into::into), + root_vehicle: value + .root_vehicle + .map(|vehicle| (vehicle.uuid.unwrap().into(), vehicle.entity.unwrap().into())), + shoulder_entity_left: value.shoulder_entity_left.map(Into::into), + shoulder_entity_right: value.shoulder_entity_right.map(Into::into), + seen_credits: value.seen_credits, + recipe_book: value.recipe_book.unwrap().into(), + } + } +} + #[cfg(feature = "alkahest")] #[derive(alkahest::Schema)] pub struct PlayerSchema { @@ -1298,7 +1439,7 @@ impl alkahest::Pack for &'_ Player { } } -#[derive(Clone)] +#[derive(Clone, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -1389,6 +1530,15 @@ impl bench_prost::Serialize for Players { } } +#[cfg(feature = "prost")] +impl From for Players { + fn from(value: pb::Players) -> Self { + Players { + players: value.players.into_iter().map(Into::into).collect(), + } + } +} + #[cfg(feature = "alkahest")] #[derive(alkahest::Schema)] pub struct PlayersSchema { diff --git a/src/datasets/mk48/mod.rs b/src/datasets/mk48/mod.rs index 37c0cb0..fbee28e 100644 --- a/src/datasets/mk48/mod.rs +++ b/src/datasets/mk48/mod.rs @@ -33,7 +33,7 @@ use crate::bench_flatbuffers; use crate::bench_prost; use crate::{generate_vec, Generate}; -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -220,6 +220,24 @@ impl Into for EntityType { } } +#[cfg(feature = "prost")] +impl From for EntityType { + fn from(value: pb::EntityType) -> Self { + match value { + pb::EntityType::ArleighBurke => EntityType::ArleighBurke, + pb::EntityType::Bismarck => EntityType::Bismarck, + pb::EntityType::Clemenceau => EntityType::Clemenceau, + pb::EntityType::Fletcher => EntityType::Fletcher, + pb::EntityType::G5 => EntityType::G5, + pb::EntityType::Iowa => EntityType::Iowa, + pb::EntityType::Kolkata => EntityType::Kolkata, + pb::EntityType::Osa => EntityType::Osa, + pb::EntityType::Yasen => EntityType::Yasen, + pb::EntityType::Zubr => EntityType::Zubr, + } + } +} + #[cfg(feature = "alkahest")] impl alkahest::Pack for EntityType { #[inline] @@ -265,7 +283,7 @@ fn generate_velocity(rng: &mut impl Rng) -> i16 { } } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -361,6 +379,25 @@ impl bench_prost::Serialize for Transform { } } +#[cfg(feature = "prost")] +impl From for (f32, f32) { + fn from(value: pb::Vector2f) -> Self { + (value.x, value.y) + } +} + +#[cfg(feature = "prost")] +impl From for Transform { + fn from(value: pb::Transform) -> Self { + Transform { + altitude: value.altitude.try_into().unwrap(), + angle: value.angle.try_into().unwrap(), + position: value.position.unwrap().into(), + velocity: value.velocity.try_into().unwrap(), + } + } +} + #[cfg(feature = "alkahest")] impl alkahest::Pack for Transform { #[inline] @@ -375,7 +412,7 @@ impl alkahest::Pack for Transform { } } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -454,6 +491,17 @@ impl bench_prost::Serialize for Guidance { } } +#[cfg(feature = "prost")] +impl From for Guidance { + fn from(value: pb::Guidance) -> Self { + Guidance { + angle: value.angle.try_into().unwrap(), + submerge: value.submerge, + velocity: value.velocity.try_into().unwrap(), + } + } +} + #[cfg(feature = "alkahest")] impl alkahest::Pack for Guidance { #[inline] @@ -467,7 +515,7 @@ impl alkahest::Pack for Guidance { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -642,6 +690,28 @@ impl bench_prost::Serialize for Contact { } } +#[cfg(feature = "prost")] +impl From for Contact { + fn from(value: pb::Contact) -> Self { + Contact { + damage: value.damage.try_into().unwrap(), + entity_id: value.entity_id, + entity_type: value + .entity_type + .map(|et| ::try_from(et).unwrap().into()), + guidance: value.guidance.unwrap().into(), + player_id: value.player_id.map(|id| id.try_into().unwrap()), + reloads: value.reloads, + transform: value.transform.unwrap().into(), + turret_angles: value + .turret_angles + .into_iter() + .map(|a| a.try_into().unwrap()) + .collect(), + } + } +} + #[cfg(feature = "alkahest")] #[derive(alkahest::Schema)] pub struct ContactSchema { @@ -673,7 +743,7 @@ impl alkahest::Pack for &'_ Contact { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -774,6 +844,23 @@ impl bench_prost::Serialize for TerrainUpdate { } } +#[cfg(feature = "prost")] +impl From for (i8, i8) { + fn from(value: pb::ChunkId) -> Self { + (value.x.try_into().unwrap(), value.y.try_into().unwrap()) + } +} + +#[cfg(feature = "prost")] +impl From for TerrainUpdate { + fn from(value: pb::TerrainUpdate) -> Self { + TerrainUpdate { + chunk_id: value.chunk_id.unwrap().into(), + data: value.data, + } + } +} + #[cfg(feature = "alkahest")] #[derive(alkahest::Schema)] pub struct TerrainUpdateSchema { @@ -797,7 +884,7 @@ impl alkahest::Pack for &'_ TerrainUpdate { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -931,6 +1018,18 @@ impl bench_prost::Serialize for Update { } } +#[cfg(feature = "prost")] +impl From for Update { + fn from(value: pb::Update) -> Self { + Update { + contacts: value.contacts.into_iter().map(Into::into).collect(), + score: value.score, + world_radius: value.world_radius, + terrain_updates: value.terrain_updates.into_iter().map(Into::into).collect(), + } + } +} + #[cfg(feature = "alkahest")] #[derive(alkahest::Schema)] pub struct UpdateSchema { @@ -954,7 +1053,7 @@ impl alkahest::Pack for &'_ Update { } } -#[derive(Clone)] +#[derive(Clone, PartialEq)] #[cfg_attr(feature = "abomonation", derive(abomonation_derive::Abomonation))] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] #[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))] @@ -1045,6 +1144,15 @@ impl bench_prost::Serialize for Updates { } } +#[cfg(feature = "prost")] +impl From for Updates { + fn from(value: pb::Updates) -> Self { + Updates { + updates: value.updates.into_iter().map(Into::into).collect(), + } + } +} + #[cfg(feature = "alkahest")] #[derive(alkahest::Schema)] pub struct UpdatesSchema {