diff options
author | HampusM <hampus@hampusmat.com> | 2023-03-25 17:42:28 +0100 |
---|---|---|
committer | HampusM <hampus@hampusmat.com> | 2023-03-25 17:42:28 +0100 |
commit | add06dafdf874b1b419e5eef918c6b1131ab09fd (patch) | |
tree | c1d52d3ece248d96562a3d77beb44973e7720847 /src/command.rs | |
parent | f49d77c2961be28c3cc500af185813dd5e83a367 (diff) |
perf: improve XML deserialization speed
Diffstat (limited to 'src/command.rs')
-rw-r--r-- | src/command.rs | 559 |
1 files changed, 497 insertions, 62 deletions
diff --git a/src/command.rs b/src/command.rs index c7ada95..2ba92ea 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,5 +1,14 @@ //! OpenGL command. -use crate::xml::element::{Elements, FromElements}; +use quick_xml::events::BytesStart; + +use crate::deserialization::{ + Deserialize, + DeserializeWithFn, + Deserializer, + DeserializerError, + IgnoreEnd, + ResultExt, +}; /// A command. #[derive(Debug, Clone, PartialEq, Eq)] @@ -38,25 +47,21 @@ impl Command } } -impl FromElements for Command +impl Deserialize for Command { type Error = Error; - fn from_elements( - elements: &crate::xml::element::Elements, + fn deserialize<TDeserializer: Deserializer>( + start: &BytesStart, + deserializer: &mut TDeserializer, ) -> Result<Self, Self::Error> { - let proto_element = elements - .get_first_tagged_element("proto") - .ok_or(Self::Error::MissingPrototype)?; + let prototype = deserializer.de_tag::<Prototype>("proto", IgnoreEnd::No)?; - let prototype = Prototype::from_elements(proto_element.child_elements())?; + let parameters = deserializer.de_tag_list::<Parameter>("param")?; - let parameters = elements - .get_all_tagged_elements_with_name("param") - .into_iter() - .map(|param_element| Parameter::from_elements(param_element.child_elements())) - .collect::<Result<Vec<_>, _>>()?; + deserializer + .skip_to_tag_end(std::str::from_utf8(start.name().as_ref()).unwrap())?; Ok(Self { prototype, @@ -80,6 +85,10 @@ pub enum Error /// Invalid parameter. #[error("Invalid parameter")] InvalidParameter(#[from] ParameterError), + + /// Deserialization failed. + #[error("Deserialization failed")] + DeserializationFailed(#[from] DeserializerError), } /// A command prototype. @@ -116,23 +125,22 @@ impl Prototype } } -impl FromElements for Prototype +impl Deserialize for Prototype { type Error = PrototypeError; - fn from_elements( - elements: &crate::xml::element::Elements, + fn deserialize<TDeserializer: Deserializer>( + _start: &BytesStart, + deserializer: &mut TDeserializer, ) -> Result<Self, Self::Error> { - let name = elements - .get_first_tagged_element("name") - .ok_or(Self::Error::MissingName)? - .child_elements() - .get_first_text_element() - .cloned() - .unwrap_or_default(); + let return_type = deserialize_type::<PrototypeError>(deserializer)?; - let return_type = find_type(elements); + let name = deserializer.de_tag_with::<_, _, DeserializeWithFn<_, _, _>>( + "name", + IgnoreEnd::No, + |_, deserializer| deserializer.de_text(), + )?; Ok(Self { name, return_type }) } @@ -145,6 +153,14 @@ pub enum PrototypeError /// No 'name' element was found. #[error("No 'name' element was found")] MissingName, + + /// No return type was found. + #[error("No return type was found")] + MissingReturnType, + + /// Deserialization failed. + #[error("Deserialization failed")] + DeserializationFailed(#[from] DeserializerError), } /// A command parameter. @@ -181,21 +197,22 @@ impl Parameter } } -impl FromElements for Parameter +impl Deserialize for Parameter { type Error = ParameterError; - fn from_elements(elements: &Elements) -> Result<Self, Self::Error> + fn deserialize<TDeserializer: Deserializer>( + _start: &BytesStart, + deserializer: &mut TDeserializer, + ) -> Result<Self, Self::Error> { - let name = elements - .get_first_tagged_element("name") - .ok_or(Self::Error::MissingName)? - .child_elements() - .get_first_text_element() - .cloned() - .unwrap_or_default(); + let ty = deserialize_type::<ParameterError>(deserializer)?; - let ty = find_type(elements); + let name = deserializer.de_tag_with::<_, _, DeserializeWithFn<_, _, _>>( + "name", + IgnoreEnd::No, + |_, deserializer| deserializer.de_text(), + )?; Ok(Self { name, ty }) } @@ -208,46 +225,47 @@ pub enum ParameterError /// No 'name' element was found. #[error("No 'name' element was found")] MissingName, + + /// Deserialization failed. + #[error("Deserialization failed")] + DeserializationFailed(#[from] DeserializerError), } -fn find_type(elements: &Elements) -> String +fn deserialize_type<Err>(deserializer: &mut impl Deserializer) -> Result<String, Err> +where + Err: From<DeserializerError>, { - let text_type_parts = elements - .get_all_text_elements() - .into_iter() - .map(|text_type_part| text_type_part.trim()) - .filter(|text_type_part| !text_type_part.is_empty()) - .collect::<Vec<_>>(); + let type_before = deserializer.de_text().try_event()?; - let opt_ptype_text = get_ptype_text(elements); + let type_ptype = deserializer + .de_tag_with::<_, _, DeserializeWithFn<_, _, _>>( + "ptype", + IgnoreEnd::No, + |_, deserializer| deserializer.de_text(), + ) + .try_event()?; - opt_ptype_text.map_or_else( - || join_space_strs(text_type_parts.iter()), - |ptype_text| { - let Some(first_part) = text_type_parts.first() else { - return ptype_text.clone(); - }; + let type_after = deserializer.de_text().try_event()?; - let before = if *first_part == "const" { "const " } else { "" }; + let type_before_after = [type_before.clone(), type_after.clone()] + .into_iter() + .flatten(); - let after_start_index = usize::from(*first_part == "const"); + Ok(type_ptype.map_or_else( + || join_space_strs(type_before_after), + |ptype_text| { + let before = type_before + .map(|before| format!("{before} ")) + .unwrap_or_default(); format!( - "{before}{ptype_text} {}", - text_type_parts - .get(after_start_index..) - .map(|parts| join_space_strs(parts.iter())) + "{before}{ptype_text}{}", + type_after + .map(|after| format!(" {after}")) .unwrap_or_default() ) }, - ) -} - -fn get_ptype_text(elements: &Elements) -> Option<&String> -{ - let ptype_element = elements.get_first_tagged_element("ptype")?; - - ptype_element.child_elements().get_first_text_element() + )) } fn join_space_strs<Strings, StrItem>(strings: Strings) -> String @@ -261,3 +279,420 @@ where .collect::<Vec<_>>() .join(" ") } + +#[cfg(test)] +mod tests +{ + use pretty_assertions::assert_str_eq; + use quick_xml::events::Event; + use ridicule::mock; + use ridicule::predicate::{always, eq, function}; + + use super::*; + + mock! { + MockDeserializer {} + + impl Deserializer for MockDeserializer { + fn de_tag<De: Deserialize>( + &mut self, + tag_name: &str, + ignore_end: IgnoreEnd, + ) -> Result<De, DeserializerError>; + + fn de_tag_with<Output, Err, DeserializeFn>( + &mut self, + tag_name: &str, + ignore_end: IgnoreEnd, + deserialize: DeserializeFn, + ) -> Result<Output, DeserializerError> + where + Err: std::error::Error + Send + Sync + 'static, + DeserializeFn: FnOnce(&BytesStart, &mut MockDeserializer) -> Result<Output, Err>; + + fn de_tag_list<De: Deserialize>( + &mut self, + tag_name: &str + ) -> Result<Vec<De>, DeserializerError>; + + fn de_text(&mut self) -> Result<String, DeserializerError>; + + fn skip_to_tag_start(&mut self, tag_name: &str) -> Result<(), DeserializerError>; + + fn skip_to_tag_end(&mut self, tag_name: &str) -> Result<(), DeserializerError>; + } + } + + #[test] + fn deserialize_prototype_works_with_ptype() + { + let mut mock_deserializer = MockDeserializer::new(); + + mock_deserializer + .expect_de_text() + .returning(|_| { + Err(DeserializerError::UnexpectedEvent { + expected_event_name: "text".to_string(), + found_event: Event::Start(BytesStart::new("ptype")), + }) + }) + .times(1); + + mock_deserializer + .expect_de_text() + .returning(|_| Ok("GLuint".to_string())) + .times(1); + + mock_deserializer + .expect_de_tag_with::<String, DeserializerError, DeserializeWithFn<_, _, _>>() + .with( + eq("ptype"), + function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), + always(), + ) + .returning(|deserializer, tag_name, _, func| { + func(&BytesStart::new(tag_name), deserializer) + }) + .times(1); + + mock_deserializer + .expect_de_text() + .returning(|_| { + Err(DeserializerError::UnexpectedEvent { + expected_event_name: "text".to_string(), + found_event: Event::Start(BytesStart::new("name")), + }) + }) + .times(1); + + mock_deserializer + .expect_de_text() + .returning(|_| Ok("glDoComplicatedThing".to_string())) + .times(1); + + mock_deserializer + .expect_de_tag_with::<String, DeserializerError, DeserializeWithFn<_, _, _>>() + .with( + eq("name"), + function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), + always(), + ) + .returning(|deserializer, tag_name, _, func| { + func(&BytesStart::new(tag_name), deserializer) + }) + .times(1); + + let prototype = + Prototype::deserialize(&BytesStart::new("proto"), &mut mock_deserializer) + .expect("Expected Ok"); + + assert_str_eq!(prototype.name, "glDoComplicatedThing"); + assert_str_eq!(prototype.return_type, "GLuint"); + } + + #[test] + fn deserialize_prototype_works_with_text() + { + let mut mock_deserializer = MockDeserializer::new(); + + mock_deserializer + .expect_de_text() + .returning(|_| Ok("void".to_string())) + .times(1); + + mock_deserializer + .expect_de_tag_with::<String, DeserializerError, DeserializeWithFn<_, _, _>>() + .with( + eq("ptype"), + function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), + always(), + ) + .returning(|_, _, _, _| { + Err(DeserializerError::UnexpectedEvent { + expected_event_name: "start".to_string(), + found_event: Event::Start(BytesStart::new("name")), + }) + }) + .times(1); + + mock_deserializer + .expect_de_text() + .returning(|_| { + Err(DeserializerError::UnexpectedEvent { + expected_event_name: "text".to_string(), + found_event: Event::Start(BytesStart::new("name")), + }) + }) + .times(1); + + mock_deserializer + .expect_de_text() + .returning(|_| Ok("glDoSomeThing".to_string())) + .times(1); + + mock_deserializer + .expect_de_tag_with::<String, DeserializerError, DeserializeWithFn<_, _, _>>() + .with( + eq("name"), + function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), + always(), + ) + .returning(|deserializer, tag_name, _, func| { + func(&BytesStart::new(tag_name), deserializer) + }) + .times(1); + + let prototype = + Prototype::deserialize(&BytesStart::new("proto"), &mut mock_deserializer) + .expect("Expected Ok"); + + assert_str_eq!(prototype.name, "glDoSomeThing"); + assert_str_eq!(prototype.return_type, "void"); + } + + #[test] + fn deserialize_parameter_works_with_ptype_only() + { + let mut mock_deserializer = MockDeserializer::new(); + + mock_deserializer + .expect_de_text() + .returning(|_| { + Err(DeserializerError::UnexpectedEvent { + expected_event_name: "text".to_string(), + found_event: Event::Start(BytesStart::new("ptype")), + }) + }) + .times(1); + + mock_deserializer + .expect_de_text() + .returning(|_| Ok("GLenum".to_string())) + .times(1); + + mock_deserializer + .expect_de_tag_with::<String, DeserializerError, DeserializeWithFn<_, _, _>>() + .with( + eq("ptype"), + function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), + always(), + ) + .returning(|deserializer, tag_name, _, func| { + func(&BytesStart::new(tag_name), deserializer) + }) + .times(1); + + mock_deserializer + .expect_de_text() + .returning(|_| { + Err(DeserializerError::UnexpectedEvent { + expected_event_name: "text".to_string(), + found_event: Event::Start(BytesStart::new("name")), + }) + }) + .times(1); + + mock_deserializer + .expect_de_text() + .returning(|_| Ok("value".to_string())) + .times(1); + + mock_deserializer + .expect_de_tag_with::<String, DeserializerError, DeserializeWithFn<_, _, _>>() + .with( + eq("name"), + function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), + always(), + ) + .returning(|deserializer, tag_name, _, func| { + func(&BytesStart::new(tag_name), deserializer) + }) + .times(1); + + let parameter = + Parameter::deserialize(&BytesStart::new("param"), &mut mock_deserializer) + .expect("Expected Ok"); + + assert_str_eq!(parameter.name, "value"); + assert_str_eq!(parameter.ty, "GLenum"); + } + + #[test] + fn deserialize_parameter_works_with_ptype_and_text_after() + { + let mut mock_deserializer = MockDeserializer::new(); + + mock_deserializer + .expect_de_text() + .returning(|_| { + Err(DeserializerError::UnexpectedEvent { + expected_event_name: "text".to_string(), + found_event: Event::Start(BytesStart::new("ptype")), + }) + }) + .times(1); + + mock_deserializer + .expect_de_text() + .returning(|_| Ok("GLchar".to_string())) + .times(1); + + mock_deserializer + .expect_de_tag_with::<String, DeserializerError, DeserializeWithFn<_, _, _>>() + .with( + eq("ptype"), + function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), + always(), + ) + .returning(|deserializer, tag_name, _, func| { + func(&BytesStart::new(tag_name), deserializer) + }) + .times(1); + + mock_deserializer + .expect_de_text() + .returning(|_| Ok("*".to_string())) + .times(1); + + mock_deserializer + .expect_de_text() + .returning(|_| Ok("source".to_string())) + .times(1); + + mock_deserializer + .expect_de_tag_with::<String, DeserializerError, DeserializeWithFn<_, _, _>>() + .with( + eq("name"), + function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), + always(), + ) + .returning(|deserializer, tag_name, _, func| { + func(&BytesStart::new(tag_name), deserializer) + }) + .times(1); + + let parameter = + Parameter::deserialize(&BytesStart::new("param"), &mut mock_deserializer) + .expect("Expected Ok"); + + assert_str_eq!(parameter.name, "source"); + assert_str_eq!(parameter.ty, "GLchar *"); + } + + #[test] + fn deserialize_parameter_works_with_ptype_and_text_before_and_after() + { + let mut mock_deserializer = MockDeserializer::new(); + + mock_deserializer + .expect_de_text() + .returning(|_| Ok("const".to_string())) + .times(1); + + mock_deserializer + .expect_de_text() + .returning(|_| Ok("GLchar".to_string())) + .times(1); + + mock_deserializer + .expect_de_tag_with::<String, DeserializerError, DeserializeWithFn<_, _, _>>() + .with( + eq("ptype"), + function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), + always(), + ) + .returning(|deserializer, tag_name, _, func| { + func(&BytesStart::new(tag_name), deserializer) + }) + .times(1); + + mock_deserializer + .expect_de_text() + .returning(|_| Ok("*".to_string())) + .times(1); + + mock_deserializer + .expect_de_text() + .returning(|_| Ok("name".to_string())) + .times(1); + + mock_deserializer + .expect_de_tag_with::<String, DeserializerError, DeserializeWithFn<_, _, _>>() + .with( + eq("name"), + function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), + always(), + ) + .returning(|deserializer, tag_name, _, func| { + func(&BytesStart::new(tag_name), deserializer) + }) + .times(1); + + let parameter = + Parameter::deserialize(&BytesStart::new("param"), &mut mock_deserializer) + .expect("Expected Ok"); + + assert_str_eq!(parameter.name, "name"); + assert_str_eq!(parameter.ty, "const GLchar *"); + } + + #[test] + fn deserialize_parameter_works_with_text() + { + let mut mock_deserializer = MockDeserializer::new(); + + mock_deserializer + .expect_de_text() + .returning(|_| Ok("void *".to_string())) + .times(1); + + mock_deserializer + .expect_de_tag_with::<String, DeserializerError, DeserializeWithFn<_, _, _>>() + .with( + eq("ptype"), + function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), + always(), + ) + .returning(|_, _, _, _| { + Err(DeserializerError::UnexpectedEvent { + expected_event_name: "start".to_string(), + found_event: Event::Start(BytesStart::new("name")), + }) + }) + .times(1); + + mock_deserializer + .expect_de_text() + .returning(|_| { + Err(DeserializerError::UnexpectedEvent { + expected_event_name: "text".to_string(), + found_event: Event::Start(BytesStart::new("name")), + }) + }) + .times(1); + + mock_deserializer + .expect_de_text() + .returning(|_| Ok("pixels".to_string())) + .times(1); + + mock_deserializer + .expect_de_tag_with::<String, DeserializerError, DeserializeWithFn<_, _, _>>() + .with( + eq("name"), + function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), + always(), + ) + .returning(|deserializer, tag_name, _, func| { + func(&BytesStart::new(tag_name), deserializer) + }) + .times(1); + + let parameter = + Parameter::deserialize(&BytesStart::new("param"), &mut mock_deserializer) + .expect("Expected Ok"); + + assert_str_eq!(parameter.name, "pixels"); + assert_str_eq!(parameter.ty, "void *"); + } +} |