aboutsummaryrefslogtreecommitdiff
path: root/src/command.rs
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2023-03-25 17:42:28 +0100
committerHampusM <hampus@hampusmat.com>2023-03-25 17:42:28 +0100
commitadd06dafdf874b1b419e5eef918c6b1131ab09fd (patch)
treec1d52d3ece248d96562a3d77beb44973e7720847 /src/command.rs
parentf49d77c2961be28c3cc500af185813dd5e83a367 (diff)
perf: improve XML deserialization speed
Diffstat (limited to 'src/command.rs')
-rw-r--r--src/command.rs559
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 *");
+ }
+}