From 7b9fbd24afb718797d20518fe27c0afb87e156a3 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Wed, 10 Apr 2019 01:48:01 +0900 Subject: [PATCH 01/60] Add almost equivalent implementation as example --- src/pull_parser/v7400/parser.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/pull_parser/v7400/parser.rs b/src/pull_parser/v7400/parser.rs index 93ab17d..a1dd811 100644 --- a/src/pull_parser/v7400/parser.rs +++ b/src/pull_parser/v7400/parser.rs @@ -395,6 +395,7 @@ impl Parser { /// detected at the different position from the true error position. /// /// To detect errors correctly, you should use [`next_event`] manually. + /// See an example to how to do this. /// /// # Panics /// @@ -414,6 +415,22 @@ impl Parser { /// parser.skip_current_node().expect("Failed to skip current node"); /// assert_eq!(parser.current_depth(), depth - 1); /// ``` + /// + /// `parser.skip_current_node()` is almost same as the code below, except + /// for error handling. + /// + /// ```no_run + /// # use fbxcel::pull_parser::{v7400::{Parser, Event}, ParserSource, Result}; + /// fn skip_current_node(parser: &mut Parser) -> Result<()> { + /// loop { + /// match parser.next_event()? { + /// Event::StartNode(_) => skip_current_node(parser)?, + /// Event::EndNode => return Ok(()), + /// Event::EndFbx(_) => panic!("Attempt to skip implicit top-level node"), + /// } + /// } + /// } + /// ``` pub fn skip_current_node(&mut self) -> Result<()> { let end_pos = self .state From 1cec9445e0798c2892d7220f68486265f08d8eb2 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Wed, 10 Apr 2019 01:56:04 +0900 Subject: [PATCH 02/60] Reword doc comments for `Parser::skip_current_node` --- src/pull_parser/v7400/parser.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/pull_parser/v7400/parser.rs b/src/pull_parser/v7400/parser.rs index a1dd811..34d8593 100644 --- a/src/pull_parser/v7400/parser.rs +++ b/src/pull_parser/v7400/parser.rs @@ -386,20 +386,22 @@ impl Parser { /// Ignore events until the current node closes. /// - /// In other words, this discards parser events including `EndNode` for the - /// current node. + /// This discards parser events until the `EndNode` event for the current + /// node is read. + /// The last `EndNode` (for the current node) is also discarded. /// - /// This method seeks to the already known node end position, without - /// parsing events to be ignored. - /// Because of this, some errors can be overlooked, or some errors can be - /// detected at the different position from the true error position. + /// This method seeks to the node end position without any additional + /// parsing, since the parser already knows the node end position. + /// Because of this, some errors can be overlooked, or detected at the + /// different position from the true error position. /// /// To detect errors correctly, you should use [`next_event`] manually. /// See an example to how to do this. /// /// # Panics /// - /// Panics if there are no open nodes. + /// Panics if there are no open nodes, i.e. when [`current_depth()`][`current_depth`] + /// returns 0. /// /// # Examples /// @@ -431,6 +433,9 @@ impl Parser { /// } /// } /// ``` + /// + /// [`next_event`]: #method.next_event + /// [`current_depth`]: #method.current_depth pub fn skip_current_node(&mut self) -> Result<()> { let end_pos = self .state From 5e3c3540709edc9ed21f9d41faac0864ede0530a Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Wed, 10 Apr 2019 02:19:16 +0900 Subject: [PATCH 03/60] Link types and functions in doc comments --- src/low/v7400/attribute/value.rs | 4 +++- src/pull_parser.rs | 3 ++- src/pull_parser/any.rs | 8 ++++++-- src/pull_parser/error.rs | 11 ++++++++++- src/pull_parser/reader.rs | 17 ++++++++++++----- src/pull_parser/reader/position_cache.rs | 9 ++++++--- src/pull_parser/reader/source.rs | 3 ++- src/pull_parser/v7400/attribute.rs | 4 +++- .../v7400/attribute/loaders/single.rs | 2 +- src/pull_parser/v7400/parser.rs | 16 ++++++++++++---- 10 files changed, 57 insertions(+), 20 deletions(-) diff --git a/src/low/v7400/attribute/value.rs b/src/low/v7400/attribute/value.rs index 7d46909..3179644 100644 --- a/src/low/v7400/attribute/value.rs +++ b/src/low/v7400/attribute/value.rs @@ -13,7 +13,9 @@ use crate::low::v7400::AttributeType; /// * `get_*_or_type()` returns `Result<_, AttributeType>`. /// + If a value of the expected type available, returns `Ok(_)`. /// + If not, returns `Ok(ty)` where `ty` is value type (same value as -/// returned by `type_()`. +/// returned by [`type_()`][`type_`]. +/// +/// [`type_`]: #method.type_ #[derive(Debug, Clone, PartialEq)] pub enum AttributeValue { /// Single `bool`. diff --git a/src/pull_parser.rs b/src/pull_parser.rs index a90041b..9adf2ee 100644 --- a/src/pull_parser.rs +++ b/src/pull_parser.rs @@ -7,7 +7,7 @@ //! These modules are common among all supported FBX versions: //! //! * Error types (defined in [`error`] module). -//! * `AnyParser` feature (defined in [`any`] module). +//! * [`AnyParser`] feature (defined in [`any`] module). //! * Parser source traits and wrappers (defined in [`reader`] module). //! //! # Using pull parser @@ -85,6 +85,7 @@ //! [`any`]: any/index.html //! [`error`]: error/index.html //! [`reader`]: reader/index.html +//! [`AnyParser`]: any/enum.AnyParser.html pub use self::{ error::{Error, Result, Warning}, diff --git a/src/pull_parser/any.rs b/src/pull_parser/any.rs index ceaebd6..3c4d70a 100644 --- a/src/pull_parser/any.rs +++ b/src/pull_parser/any.rs @@ -50,8 +50,12 @@ fn parser_version(header: FbxHeader) -> Result { /// Loads a tree from the given reader. /// -/// This works for seekable readers (which implement `std::io::Seek`), but -/// `from_seekable_reader` should be used for them, because it is more efficent. +/// This works for seekable readers (which implement [`std::io::Seek`]), but +/// [`from_seekable_reader`] should be used for them, because it is more +/// efficent. +/// +/// [`std::io::Seek`]: https://doc.rust-lang.org/stable/std/io/trait.Seek.html +/// [`from_seekable_reader`]: fn.from_seekable_reader.html pub fn from_reader(mut reader: R) -> Result>> { let header = FbxHeader::load(&mut reader)?; match parser_version(header)? { diff --git a/src/pull_parser/error.rs b/src/pull_parser/error.rs index de008ea..01b9914 100644 --- a/src/pull_parser/error.rs +++ b/src/pull_parser/error.rs @@ -47,7 +47,7 @@ impl Error { self.repr.position.as_ref() } - /// Creates a new `error` with the given syntactic position info. + /// Creates a new `Error` with the given syntactic position info. pub(crate) fn with_position(error: ErrorContainer, position: SyntacticPosition) -> Self { Self { repr: Box::new(Repr::with_position(error, position)), @@ -117,18 +117,27 @@ pub enum ErrorKind { /// Invalid data. /// /// With this error kind, the inner error must be [`DataError`]. + /// + /// [`DataError`]: enum.DataError.html Data, /// I/O error. /// /// With this error kind, the inner error must be [`std::io::Error`]. + /// + /// [`std::io::Error`]: + /// https://doc.rust-lang.org/stable/std/io/struct.Error.html Io, /// Invalid operation. /// /// With this error kind, the inner error must be [`OperationError`]. + /// + /// [`OperationError`]: enum.OperationError.html Operation, /// Critical warning. /// /// With this error kind, the inner error must be [`Warning`]. + /// + /// [`Warning`]: enum.Warning.html Warning, } diff --git a/src/pull_parser/reader.rs b/src/pull_parser/reader.rs index 5a09cde..6773875 100644 --- a/src/pull_parser/reader.rs +++ b/src/pull_parser/reader.rs @@ -34,18 +34,19 @@ pub trait ParserSource: Sized + io::Read { /// This is called many times during parsing, so it is desirable to be fast /// as possible. /// - /// Reader types with `std::io::Seek` can implement this as + /// Reader types with [`std::io::Seek`] can implement this as /// `self.seek(SeekFrom::Current(0)).unwrap()`, but this is fallible and /// can be inefficient. /// Use of [`PositionCacheReader`] is reccomended. /// + /// [`std::io::Seek`]: https://doc.rust-lang.org/stable/std/io/trait.Seek.html /// [`PositionCacheReader`]: struct.PositionCacheReader.html fn position(&self) -> u64; /// Skips (seeks formward) the given size. /// - /// Reader types can make this more efficient using `io::Seek::seek` if - /// possible. + /// Reader types can make this more efficient using [`std::io::Seek::seek`] + /// if possible. /// /// # Examples /// @@ -61,6 +62,9 @@ pub trait ParserSource: Sized + io::Read { /// reader.skip_distance(7).expect("Failed to skip"); /// assert_eq!(reader.position(), 7); /// ``` + /// + /// [`std::io::Seek::seek`]: + /// https://doc.rust-lang.org/stable/std/io/trait.Seek.html#tymethod.seek fn skip_distance(&mut self, distance: u64) -> io::Result<()> { // NOTE: `let mut limited = self.by_ref().take(distance);` is E0507. let mut limited = io::Read::take(self.by_ref(), distance); @@ -70,8 +74,8 @@ pub trait ParserSource: Sized + io::Read { /// Skips (seeks forward) to the given position. /// - /// Reader types can make this more efficient using `io::Seek::seek` if - /// possible. + /// Reader types can make this more efficient using [`std::io::Seek::seek`] + /// if possible. /// /// # Panics /// @@ -91,6 +95,9 @@ pub trait ParserSource: Sized + io::Read { /// reader.skip_to(7).expect("Failed to skip"); /// assert_eq!(reader.position(), 7); /// ``` + /// + /// [`std::io::Seek::seek`]: + /// https://doc.rust-lang.org/stable/std/io/trait.Seek.html#tymethod.seek fn skip_to(&mut self, pos: u64) -> io::Result<()> { let distance = pos .checked_sub(self.position()) diff --git a/src/pull_parser/reader/position_cache.rs b/src/pull_parser/reader/position_cache.rs index 99ded80..0f24afc 100644 --- a/src/pull_parser/reader/position_cache.rs +++ b/src/pull_parser/reader/position_cache.rs @@ -20,12 +20,12 @@ pub struct PositionCacheReader { } impl PositionCacheReader { - /// Creates a new `PositionCache`. + /// Creates a new `PositionCacheReader`. pub fn new(inner: R) -> Self { Self { inner, position: 0 } } - /// Creates a new `PositionCache` with the given offset. + /// Creates a new `PositionCacheReader` with the given offset. /// /// # Examples /// @@ -62,7 +62,7 @@ impl PositionCacheReader { /// /// A seek beyond the end of a stream is allowed, but behavior is defined by /// the implementation. - /// See the document for `std::io::Seek::seek()`. + /// See the document for [`std::io::Seek::seek()`][`std::io::Seek::seek`]. /// /// # Examples /// @@ -79,6 +79,9 @@ impl PositionCacheReader { /// reader.skip_distance(7).expect("Failed to skip"); /// assert_eq!(reader.position(), 7); /// ``` + /// + /// [`std::io::Seek::seek`]: + /// https://doc.rust-lang.org/stable/std/io/trait.Seek.html#tymethod.seek pub fn skip_distance(&mut self, mut distance: u64) -> io::Result<()> where R: io::Seek, diff --git a/src/pull_parser/reader/source.rs b/src/pull_parser/reader/source.rs index b34ca97..ca94471 100644 --- a/src/pull_parser/reader/source.rs +++ b/src/pull_parser/reader/source.rs @@ -8,11 +8,12 @@ use crate::pull_parser::{reader::PositionCacheReader, ParserSource}; /// /// This may be inefficient, but works with any reader types. /// It is recommended to use [`SeekableSource`] if the reader implements -/// `std::io::Seek`. +/// [`std::io::Seek`]. /// /// This internally uses `PositionCacheReader`, so users don't need to wrap /// readers by `PositionCacheReader` manually. /// +/// [`std::io::Seek`]: https://doc.rust-lang.org/stable/std/io/trait.Seek.html /// [`PositionCacheReader`]: struct.PositionCacheReader.html /// [`SeekableSource`]: struct.SeekableSource.html #[derive(Debug, Clone, Copy)] diff --git a/src/pull_parser/v7400/attribute.rs b/src/pull_parser/v7400/attribute.rs index ff06aca..d5eb864 100644 --- a/src/pull_parser/v7400/attribute.rs +++ b/src/pull_parser/v7400/attribute.rs @@ -14,7 +14,9 @@ use crate::{ use self::array::{ArrayAttributeValues, AttributeStreamDecoder, BooleanArrayAttributeValues}; pub use self::loader::LoadAttribute; -/// Use `low::v7400::AttributeValue` instead. +/// Use [`low::v7400::AttributeValue`] instead. +/// +/// [`low::v7400::AttributeValue`]: ../../../low/v7400/enum.AttributeValue.html #[deprecated( since = "0.4.0", note = "`DirectAttributeValue` is moved to `low::v7400::AttributeValue`" diff --git a/src/pull_parser/v7400/attribute/loaders/single.rs b/src/pull_parser/v7400/attribute/loaders/single.rs index 117f5ca..e03ed63 100644 --- a/src/pull_parser/v7400/attribute/loaders/single.rs +++ b/src/pull_parser/v7400/attribute/loaders/single.rs @@ -6,7 +6,7 @@ use crate::pull_parser::{v7400::LoadAttribute, Result}; /// Loader for primitive types. /// -/// Supported types are: [`bool`], [`i16`] , [`i32`], [`i64`], [`f32`], [`f64`]. +/// Supported types are: `bool`, `i16` , `i32`, `i64`, `f32`, and `f64`. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct PrimitiveLoader(std::marker::PhantomData); diff --git a/src/pull_parser/v7400/parser.rs b/src/pull_parser/v7400/parser.rs index 34d8593..0589289 100644 --- a/src/pull_parser/v7400/parser.rs +++ b/src/pull_parser/v7400/parser.rs @@ -20,9 +20,11 @@ use crate::{ /// Warning handler type. type WarningHandler = Box Result<()>>; -/// Creates a new `Parser` from the given reader. +/// Creates a new [`Parser`] from the given reader. /// /// Returns an error if the given FBX version in unsupported. +/// +/// [`Parser`]: struct.Parser.html pub fn from_reader(header: FbxHeader, reader: R) -> Result>> where R: io::Read, @@ -33,9 +35,11 @@ where ) } -/// Creates a new `Parser` from the given seekable reader. +/// Creates a new [`Parser`] from the given seekable reader. /// /// Returns an error if the given FBX version in unsupported. +/// +/// [`Parser`]: struct.Parser.html pub fn from_seekable_reader(header: FbxHeader, reader: R) -> Result>> where R: io::Read + io::Seek, @@ -175,6 +179,9 @@ impl Parser { /// already failed and returned error. /// If you call `next_event()` with failed parser, error created from /// [`OperationError::AlreadyAborted`] will be returned. + /// + /// [`OperationError::AlreadyAborted`]: + /// ../error/enum.OperationError.html#variant.AlreadyAborted pub fn next_event(&mut self) -> Result> { let previous_depth = self.current_depth(); @@ -386,9 +393,9 @@ impl Parser { /// Ignore events until the current node closes. /// - /// This discards parser events until the `EndNode` event for the current + /// This discards parser events until the [`EndNode`] event for the current /// node is read. - /// The last `EndNode` (for the current node) is also discarded. + /// The last [`EndNode`] (for the current node) is also discarded. /// /// This method seeks to the node end position without any additional /// parsing, since the parser already knows the node end position. @@ -436,6 +443,7 @@ impl Parser { /// /// [`next_event`]: #method.next_event /// [`current_depth`]: #method.current_depth + /// [`EndNode`]: enum.Event.html#variant.EndNode pub fn skip_current_node(&mut self) -> Result<()> { let end_pos = self .state From 485f6352ad3b291609a852615959ce848198ad45 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Wed, 10 Apr 2019 02:20:45 +0900 Subject: [PATCH 04/60] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9006f5..828a8e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [Unreleased] +* Documents are improved a little. + ## [0.4.3] * Longer lifetime for iterator returned by From f01a5d9b7335f958f148e2fba05224f0679244d8 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Wed, 10 Apr 2019 02:29:26 +0900 Subject: [PATCH 05/60] Use `Result::transpose()` rather than `match` --- src/pull_parser/v7400/attribute/iter.rs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/pull_parser/v7400/attribute/iter.rs b/src/pull_parser/v7400/attribute/iter.rs index ce11d5d..1e8cf52 100644 --- a/src/pull_parser/v7400/attribute/iter.rs +++ b/src/pull_parser/v7400/attribute/iter.rs @@ -34,14 +34,7 @@ where V: LoadAttribute, { let loader = loaders.next()?; - - // TODO: Use `transpose` when it is stabilized. - // See for detail. - match attributes.load_next(loader) { - Ok(Some(v)) => Some(Ok(v)), - Ok(None) => None, - Err(e) => Some(Err(e)), - } + attributes.load_next(loader).transpose() } /// Loads the next attrbute with buffered I/O. @@ -54,14 +47,7 @@ where V: LoadAttribute, { let loader = loaders.next()?; - - // TODO: Use `transpose` when it is stabilized. - // See for detail. - match attributes.load_next_buffered(loader) { - Ok(Some(v)) => Some(Ok(v)), - Ok(None) => None, - Err(e) => Some(Err(e)), - } + attributes.load_next(loader).transpose() } /// Node attributes iterator. From 48ed44de20144f1504ac8c8e3879bf85bb8901e2 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Wed, 10 Apr 2019 16:55:25 +0900 Subject: [PATCH 06/60] Fix English in CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 828a8e3..ba485f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,7 +95,7 @@ + It is moved to `low::v7400::AttributeValue`. + Now `DirectAttributeValue` is a type alias to `low::v7400::AttributeValue`. - + The type alias will exists for a while, but will be removed in future + + The type alias will exist for a while, but will be removed in future version. ## [0.3.0] From 15c2249ba6227b3059af8ef98ea8f293d2e574bc Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Tue, 16 Apr 2019 01:14:34 +0900 Subject: [PATCH 07/60] Add to `parser-implementations` category --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index ea13cd5..fd96629 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ readme = "README.md" description = "Excellent FBX library" repository = "https://github.com/lo48576/fbxcel" keywords = ["FBX", "3D", "model"] +categories = ["parser-implementations"] [package.metadata.docs.rs] all-features = true From af2e2293959e341e6774d7d628d5b91538da49c2 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Sun, 21 Apr 2019 19:35:41 +0900 Subject: [PATCH 08/60] Support some basic way to create and add nodes and attributes --- src/tree/v7400.rs | 79 +++++++++++++++++++++++++++++++++++++ src/tree/v7400/node/data.rs | 5 +++ 2 files changed, 84 insertions(+) diff --git a/src/tree/v7400.rs b/src/tree/v7400.rs index f55f32e..3d6e4af 100644 --- a/src/tree/v7400.rs +++ b/src/tree/v7400.rs @@ -3,6 +3,8 @@ use indextree::Arena; use string_interner::StringInterner; +use crate::low::v7400::AttributeValue; + use self::node::{NodeData, NodeNameSym}; pub use self::{ error::LoadError, @@ -77,4 +79,81 @@ impl Tree { pub(crate) fn contains_node(&self, node_id: NodeId) -> bool { self.arena.get(node_id.raw()).is_some() } + + /// Creates a new node and appends to the given parent node. + /// + /// # Panics + /// + /// Panics if the given node ID is not used in the tree. + pub fn append_new(&mut self, parent: NodeId, name: &str) -> NodeId { + let name_sym = self.node_names.get_or_intern(name); + let new_child = self.arena.new_node(NodeData::new(name_sym, Vec::new())); + parent + .raw() + .append(new_child, &mut self.arena) + .expect("Should never fail"); + + NodeId::new(new_child) + } + + /// Creates a new node and prepends to the given parent node. + /// + /// # Panics + /// + /// Panics if the given node ID is not used in the tree. + pub fn prepend_new(&mut self, parent: NodeId, name: &str) -> NodeId { + let name_sym = self.node_names.get_or_intern(name); + let new_child = self.arena.new_node(NodeData::new(name_sym, Vec::new())); + parent + .raw() + .prepend(new_child, &mut self.arena) + .expect("Should never fail"); + + NodeId::new(new_child) + } + + /// Creates a new node and inserts after the given sibling node. + /// + /// # Panics + /// + /// Panics if the given node ID is invalid (i.e. not used or root node). + pub fn insert_new_after(&mut self, sibling: NodeId, name: &str) -> NodeId { + assert_ne!(sibling, self.root_id, "Root node should have no siblings"); + let name_sym = self.node_names.get_or_intern(name); + let new_child = self.arena.new_node(NodeData::new(name_sym, Vec::new())); + sibling + .raw() + .insert_after(new_child, &mut self.arena) + .expect("Should never fail"); + + NodeId::new(new_child) + } + + /// Creates a new node and inserts before the given sibling node. + /// + /// # Panics + /// + /// Panics if the given node ID is invalid (i.e. not used or root node). + pub fn insert_new_before(&mut self, sibling: NodeId, name: &str) -> NodeId { + assert_ne!(sibling, self.root_id, "Root node should have no siblings"); + let name_sym = self.node_names.get_or_intern(name); + let new_child = self.arena.new_node(NodeData::new(name_sym, Vec::new())); + sibling + .raw() + .insert_before(new_child, &mut self.arena) + .expect("Should never fail"); + + NodeId::new(new_child) + } + + /// Creates a new node and inserts before the given sibling node. + /// + /// # Panics + /// + /// Panics if the given node ID is invalid (i.e. not used or root node). + pub fn append_attribute(&mut self, node_id: NodeId, v: AttributeValue) { + assert_ne!(node_id, self.root_id, "Root node should have no attributes"); + let node = self.arena.get_mut(node_id.raw()).expect("Invalid node ID"); + node.data.append_attribute(v) + } } diff --git a/src/tree/v7400/node/data.rs b/src/tree/v7400/node/data.rs index 9feb771..2251a1a 100644 --- a/src/tree/v7400/node/data.rs +++ b/src/tree/v7400/node/data.rs @@ -25,6 +25,11 @@ impl NodeData { &self.attributes } + /// Appends the given value to the attributes. + pub(crate) fn append_attribute(&mut self, v: AttributeValue) { + self.attributes.push(v) + } + /// Creates a new `NodeData`. pub(crate) fn new(name_sym: NodeNameSym, attributes: Vec) -> Self { Self { From d29e21f980dbee8f7c7e42dbbd5299a7fcbbaaa2 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Sun, 21 Apr 2019 22:28:36 +0900 Subject: [PATCH 09/60] Implement `Default` for `Tree` --- src/tree/v7400.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/tree/v7400.rs b/src/tree/v7400.rs index 3d6e4af..ac24174 100644 --- a/src/tree/v7400.rs +++ b/src/tree/v7400.rs @@ -157,3 +157,18 @@ impl Tree { node.data.append_attribute(v) } } + +impl Default for Tree { + fn default() -> Self { + let mut arena = Arena::new(); + let mut node_names = StringInterner::new(); + let root_id = + NodeId::new(arena.new_node(NodeData::new(node_names.get_or_intern(""), Vec::new()))); + + Self { + arena, + node_names, + root_id, + } + } +} From ea46e9850f6e608cf909d4cdeccf9582089cde85 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Sun, 21 Apr 2019 22:45:13 +0900 Subject: [PATCH 10/60] Update CHANGELOG --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba485f6..4008451 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ ## [Unreleased] * Documents are improved a little. +* Manual tree construction support is added. + +### Added +* Manual tree construction support is added. + + Methods to add new nodes and attributes are added. + + Complete modification is not yet supported, for example modifying already + added attributes or removing nodes. ## [0.4.3] From 5f0ab4542452d2c7ef975788cd2b41682e48832e Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Sun, 21 Apr 2019 19:01:41 +0900 Subject: [PATCH 11/60] Implement v7400 binary writer --- Cargo.toml | 1 + src/lib.rs | 5 + src/low.rs | 2 + src/low/fbx_header.rs | 6 +- src/low/v7400.rs | 10 +- src/low/v7400/array_attribute.rs | 9 + src/low/v7400/attribute/type_.rs | 20 ++ src/low/v7400/node_header.rs | 11 + src/low/version.rs | 6 + src/writer.rs | 5 + src/writer/v7400.rs | 3 + src/writer/v7400/binary.rs | 334 +++++++++++++++++++ src/writer/v7400/binary/attributes.rs | 461 ++++++++++++++++++++++++++ src/writer/v7400/binary/error.rs | 70 ++++ src/writer/v7400/binary/footer.rs | 74 +++++ 15 files changed, 1009 insertions(+), 8 deletions(-) create mode 100644 src/writer.rs create mode 100644 src/writer/v7400.rs create mode 100644 src/writer/v7400/binary.rs create mode 100644 src/writer/v7400/binary/attributes.rs create mode 100644 src/writer/v7400/binary/error.rs create mode 100644 src/writer/v7400/binary/footer.rs diff --git a/Cargo.toml b/Cargo.toml index fd96629..be7f3f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ all-features = true default = [] tree = ["indextree", "string-interner"] +writer = [] [dependencies] byteorder = "1" diff --git a/src/lib.rs b/src/lib.rs index 2d16dda..79c4713 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,9 @@ //! `tree` module provides tree types, which allow users to access FBX data as //! tree, not as stream of parser events. //! To use `tree` module, enable `tree` feature. +//! +//! `writer` module provides writer types. +//! To use `writer` module, enable `writer` feature. #![warn(missing_docs)] #![warn(clippy::missing_docs_in_private_items)] @@ -16,3 +19,5 @@ pub mod low; pub mod pull_parser; #[cfg(feature = "tree")] pub mod tree; +#[cfg(feature = "writer")] +pub mod writer; diff --git a/src/low.rs b/src/low.rs index 2fffb06..cf76573 100644 --- a/src/low.rs +++ b/src/low.rs @@ -1,5 +1,7 @@ //! Low-level or primitive data types for FBX binary. +#[cfg(feature = "writer")] +pub(crate) use self::fbx_header::MAGIC; pub use self::{ fbx_header::{FbxHeader, HeaderError}, version::FbxVersion, diff --git a/src/low/fbx_header.rs b/src/low/fbx_header.rs index c06985e..df45ff3 100644 --- a/src/low/fbx_header.rs +++ b/src/low/fbx_header.rs @@ -10,6 +10,9 @@ use crate::{low::FbxVersion, pull_parser::ParserVersion}; /// Magic binary length. const MAGIC_LEN: usize = 23; +/// Magic binary. +pub(crate) const MAGIC: &[u8; MAGIC_LEN] = b"Kaydara FBX Binary \x00\x1a\x00"; + /// Header read error. #[derive(Debug)] pub enum HeaderError { @@ -48,9 +51,6 @@ pub struct FbxHeader { impl FbxHeader { /// Reads an FBX header from the given reader. pub fn load(mut reader: impl io::Read) -> Result { - /// Magic binary. - const MAGIC: &[u8; MAGIC_LEN] = b"Kaydara FBX Binary \x00\x1a\x00"; - // Check magic. let mut magic_buf = [0u8; MAGIC_LEN]; reader.read_exact(&mut magic_buf)?; diff --git a/src/low/v7400.rs b/src/low/v7400.rs index 3038e99..5f9eeeb 100644 --- a/src/low/v7400.rs +++ b/src/low/v7400.rs @@ -1,14 +1,14 @@ //! Low-level or primitive data types for FBX 7.4 and compatible versions. -pub(crate) use self::{ - array_attribute::{ArrayAttributeEncoding, ArrayAttributeHeader}, - node_header::NodeHeader, - special_attribute::SpecialAttributeHeader, -}; pub use self::{ + array_attribute::ArrayAttributeEncoding, attribute::{AttributeType, AttributeValue}, fbx_footer::FbxFooter, }; +pub(crate) use self::{ + array_attribute::ArrayAttributeHeader, node_header::NodeHeader, + special_attribute::SpecialAttributeHeader, +}; mod array_attribute; mod attribute; diff --git a/src/low/v7400/array_attribute.rs b/src/low/v7400/array_attribute.rs index 07b9b76..a754e65 100644 --- a/src/low/v7400/array_attribute.rs +++ b/src/low/v7400/array_attribute.rs @@ -28,6 +28,15 @@ impl ArrayAttributeEncoding { _ => None, } } + + /// Returns the raw value. + #[cfg(feature = "writer")] + pub(crate) fn to_u32(self) -> u32 { + match self { + ArrayAttributeEncoding::Direct => 0, + ArrayAttributeEncoding::Zlib => 1, + } + } } impl From for Compression { diff --git a/src/low/v7400/attribute/type_.rs b/src/low/v7400/attribute/type_.rs index 58e645e..24796c1 100644 --- a/src/low/v7400/attribute/type_.rs +++ b/src/low/v7400/attribute/type_.rs @@ -55,6 +55,26 @@ impl AttributeType { _ => None, } } + + /// Returns the type code. + #[cfg(feature = "writer")] + pub(crate) fn type_code(self) -> u8 { + match self { + AttributeType::Bool => b'C', + AttributeType::I16 => b'Y', + AttributeType::I32 => b'I', + AttributeType::I64 => b'L', + AttributeType::F32 => b'F', + AttributeType::F64 => b'D', + AttributeType::ArrBool => b'b', + AttributeType::ArrI32 => b'i', + AttributeType::ArrI64 => b'l', + AttributeType::ArrF32 => b'f', + AttributeType::ArrF64 => b'd', + AttributeType::Binary => b'R', + AttributeType::String => b'S', + } + } } impl FromReader for AttributeType { diff --git a/src/low/v7400/node_header.rs b/src/low/v7400/node_header.rs index 02eaa43..73ef7e3 100644 --- a/src/low/v7400/node_header.rs +++ b/src/low/v7400/node_header.rs @@ -26,6 +26,17 @@ impl NodeHeader { && self.bytelen_attributes == 0 && self.bytelen_name == 0 } + + /// Returns node end marker. + #[cfg(feature = "writer")] + pub(crate) fn node_end() -> Self { + Self { + end_offset: 0, + num_attributes: 0, + bytelen_attributes: 0, + bytelen_name: 0, + } + } } impl FromParser for NodeHeader { diff --git a/src/low/version.rs b/src/low/version.rs index 35d2289..a5cdc73 100644 --- a/src/low/version.rs +++ b/src/low/version.rs @@ -5,6 +5,12 @@ pub struct FbxVersion(u32); impl FbxVersion { + /// Version 7.4. + pub const V7_4: Self = FbxVersion(7400); + + /// Version 7.5. + pub const V7_5: Self = FbxVersion(7500); + /// Creates a new `FbxVersion`. pub(crate) fn new(version: u32) -> Self { FbxVersion(version) diff --git a/src/writer.rs b/src/writer.rs new file mode 100644 index 0000000..62e5838 --- /dev/null +++ b/src/writer.rs @@ -0,0 +1,5 @@ +//! FBX writer. +//! +//! Enabled by `writer` feature. + +pub mod v7400; diff --git a/src/writer/v7400.rs b/src/writer/v7400.rs new file mode 100644 index 0000000..1874660 --- /dev/null +++ b/src/writer/v7400.rs @@ -0,0 +1,3 @@ +//! Writer for FBX 7.4 or later. + +pub mod binary; diff --git a/src/writer/v7400/binary.rs b/src/writer/v7400/binary.rs new file mode 100644 index 0000000..8139269 --- /dev/null +++ b/src/writer/v7400/binary.rs @@ -0,0 +1,334 @@ +//! Binary writer for FBX 7.4 or later. + +use std::{ + convert::TryFrom, + io::{self, Read, Seek, SeekFrom, Write}, +}; + +use log::{debug, trace}; + +use crate::low::{v7400::NodeHeader, FbxVersion, MAGIC}; + +pub use self::{ + attributes::AttributesWriter, + error::{Error, Result}, + footer::{FbxFooter, FbxFooterPaddingLength}, +}; + +mod attributes; +mod error; +mod footer; + +/// Binary writer. +#[derive(Debug, Clone)] +pub struct Writer { + /// Writer destination. + sink: W, + /// FBX version. + fbx_version: FbxVersion, + /// Node header positions not yet closed. + open_nodes: Vec, +} + +impl Writer { + /// Creates a new `Writer` and writes FBX file header. + pub fn new(mut sink: W, fbx_version: FbxVersion) -> Result { + // Check if the given version is supported. + if fbx_version.major() != 7 { + return Err(Error::UnsupportedFbxVersion(fbx_version)); + } + + // Write FBX magic binary. + sink.seek(SeekFrom::Start(0))?; + sink.write_all(MAGIC)?; + sink.write_all(&fbx_version.raw().to_le_bytes())?; + + Ok(Self { + sink, + fbx_version, + open_nodes: Vec::new(), + }) + } + + /// Returns a mutable reference to the sink. + fn sink(&mut self) -> &mut W { + &mut self.sink + } + + /// Returns a mutable reference to the node header of the current node. + fn current_node(&mut self) -> Option<&mut OpenNode> { + self.open_nodes.last_mut() + } + + /// Returns a mutable reference to the node header of the current node. + fn current_node_header(&mut self) -> Option<&mut NodeHeader> { + self.current_node().map(|v| &mut v.header) + } + + /// Writes the given node header. + fn write_node_header(&mut self, header: &NodeHeader) -> Result<()> { + if self.fbx_version.raw() < 7500 { + self.sink.write_all( + &u32::try_from(header.end_offset) + .map_err(|_| Error::FileTooLarge(header.end_offset))? + .to_le_bytes(), + )?; + self.sink.write_all( + &u32::try_from(header.num_attributes) + .map_err(|_| Error::TooManyAttributes(header.num_attributes as usize))? + .to_le_bytes(), + )?; + self.sink.write_all( + &u32::try_from(header.bytelen_attributes) + .map_err(|_| Error::AttributeTooLong(header.bytelen_attributes as usize))? + .to_le_bytes(), + )?; + } else { + self.sink.write_all(&header.end_offset.to_le_bytes())?; + self.sink.write_all(&header.num_attributes.to_le_bytes())?; + self.sink + .write_all(&header.bytelen_attributes.to_le_bytes())?; + } + self.sink.write_all(&header.bytelen_name.to_le_bytes())?; + + Ok(()) + } + + /// Writes the FBX version. + fn write_fbx_verison(&mut self) -> Result<()> { + self.sink + .write_all(&self.fbx_version.raw().to_le_bytes()) + .map_err(Into::into) + } + + /// Finalizes node attributes and update node header info. + fn finalize_attributes(&mut self) -> Result<()> { + trace!("Finalizing attributes: depth={:?}", self.open_nodes.len()); + + let current_node = match self.open_nodes.last_mut() { + Some(v) => v, + None => { + trace!("`finalize_attributes()` is called for root node, ignoring"); + return Ok(()); + } + }; + if current_node.is_attrs_finalized { + trace!("Attributes are already finalized"); + return Ok(()); + } + + let current_pos = self.sink.seek(SeekFrom::Current(0))?; + current_node.header.bytelen_attributes = current_pos - current_node.body_pos; + current_node.is_attrs_finalized = true; + + trace!("Finalized attributes: current_node={:?}", current_node); + + Ok(()) + } + + /// Creates a new node and returns node attributes writer. + pub fn new_node(&mut self, name: &str) -> Result> { + trace!( + "New node: name={:?}, depth={:?}", + name, + self.open_nodes.len() + ); + self.finalize_attributes()?; + + if let Some(current_node) = self.current_node() { + current_node.has_child = true; + } + + // Check if the node name is short enough. + let bytelen_name = + u8::try_from(name.len()).map_err(|_| Error::NodeNameTooLong(name.len()))?; + + let header_pos = self.sink.seek(SeekFrom::Current(0))?; + + let header = NodeHeader { + end_offset: 0, + num_attributes: 0, + bytelen_attributes: 0, + bytelen_name, + }; + + // Write dummy header (placeholder). + self.write_node_header(&header)?; + + // Write node name. + self.sink.write_all(name.as_ref())?; + + let body_pos = self.sink.seek(SeekFrom::Current(0))?; + + self.open_nodes.push(OpenNode { + header_pos, + body_pos, + header, + has_child: false, + is_attrs_finalized: false, + }); + + Ok(AttributesWriter::new(self)) + } + + /// Closes an open node. + pub fn close_node(&mut self) -> Result<()> { + trace!("Close node: depth={:?}", self.open_nodes.len()); + self.finalize_attributes()?; + + let mut current_node = match self.open_nodes.pop() { + Some(v) => v, + None => return Err(Error::NoNodesToClose), + }; + + // Write node end marker if necessary. + if current_node.has_child || current_node.header.num_attributes == 0 { + self.write_node_header(&NodeHeader::node_end())?; + } + + // Update node header. + let node_end_pos = self.sink.seek(SeekFrom::Current(0))?; + self.sink.seek(SeekFrom::Start(current_node.header_pos))?; + current_node.header.end_offset = node_end_pos; + assert_eq!( + current_node.header.num_attributes == 0, + current_node.header.bytelen_attributes == 0, + "Length of attributes can be zero iff there are no attributes" + ); + self.write_node_header(¤t_node.header)?; + self.sink.seek(SeekFrom::Start(node_end_pos))?; + + Ok(()) + } + + /// Writes the given tree. + #[cfg(feature = "tree")] + pub fn write_tree(&mut self, tree: &crate::tree::v7400::Tree) -> Result<()> { + use crate::low::v7400::AttributeValue; + + let mut current = match tree.root().first_child() { + Some(v) => v, + None => return Ok(()), + }; + + 'all: loop { + let mut attrs_writer = self.new_node(current.name())?; + for attr in current.attributes() { + match attr { + AttributeValue::Bool(v) => attrs_writer.append_bool(*v)?, + AttributeValue::I16(v) => attrs_writer.append_i16(*v)?, + AttributeValue::I32(v) => attrs_writer.append_i32(*v)?, + AttributeValue::I64(v) => attrs_writer.append_i64(*v)?, + AttributeValue::F32(v) => attrs_writer.append_f32(*v)?, + AttributeValue::F64(v) => attrs_writer.append_f64(*v)?, + AttributeValue::ArrBool(v) => { + attrs_writer.append_arr_bool_from_iter(None, v.iter().cloned())? + } + AttributeValue::ArrI32(v) => { + attrs_writer.append_arr_i32_from_iter(None, v.iter().cloned())? + } + AttributeValue::ArrI64(v) => { + attrs_writer.append_arr_i64_from_iter(None, v.iter().cloned())? + } + AttributeValue::ArrF32(v) => { + attrs_writer.append_arr_f32_from_iter(None, v.iter().cloned())? + } + AttributeValue::ArrF64(v) => { + attrs_writer.append_arr_f64_from_iter(None, v.iter().cloned())? + } + AttributeValue::Binary(v) => attrs_writer.append_binary_direct(v)?, + AttributeValue::String(v) => attrs_writer.append_string_direct(v)?, + } + } + + let mut visit_child = true; + current = 'next: loop { + if visit_child { + if let Some(child) = current.first_child() { + break 'next child; + } + // No children. + visit_child = false; + } + self.close_node()?; + if let Some(sib) = current.next_sibling() { + break 'next sib; + } + let parent = current + .parent() + .expect("Should never fail: `current` must not be the root note"); + if parent.node_id() == tree.root().node_id() { + break 'all; + } + current = parent; + }; + } + + Ok(()) + } + + /// Finalizes the FBX binary. + /// + /// You may want to use [`finalize_and_flush()`]. + /// + /// [`finalize_and_flush()`]: #method.finalize_and_flush + pub fn finalize(mut self, footer: &FbxFooter) -> Result<()> { + self.finalize_impl(footer) + } + + /// Finalizes the FBX binary and flushes. + pub fn finalize_and_flush(mut self, footer: &FbxFooter) -> Result<()> { + self.finalize_impl(footer)?; + self.sink.flush()?; + + Ok(()) + } + + /// Internal implementation of `finalize()` and `finalize_and_flush()`. + fn finalize_impl(&mut self, footer: &FbxFooter) -> Result<()> { + if !self.open_nodes.is_empty() { + return Err(Error::UnclosedNode(self.open_nodes.len())); + } + + // Close implicit root node. + self.write_node_header(&NodeHeader::node_end())?; + + // Write FBX footer. + self.sink.write_all(footer.unknown1())?; + { + let len = match footer.padding_len { + FbxFooterPaddingLength::Default => { + let current = self.sink.seek(SeekFrom::Current(0))?; + current.wrapping_neg() & 0x0f + } + FbxFooterPaddingLength::Forced(len) => u64::from(len), + }; + debug!( + "Footer padding: spec={:?}, len={:?}", + footer.padding_len, len + ); + io::copy(&mut io::repeat(0).take(len), &mut self.sink)?; + } + self.sink.write_all(&footer.unknown2())?; + self.write_fbx_verison()?; + io::copy(&mut io::repeat(0).take(120), &mut self.sink)?; + self.sink.write_all(footer.unknown3())?; + + Ok(()) + } +} + +/// Open node state. +#[derive(Debug, Clone, Copy)] +struct OpenNode { + /// Header position. + header_pos: u64, + /// Position of beginning of attributes part. + body_pos: u64, + /// Header. + header: NodeHeader, + /// Whether the node has child. + has_child: bool, + /// Whether the attributes are finalized. + is_attrs_finalized: bool, +} diff --git a/src/writer/v7400/binary/attributes.rs b/src/writer/v7400/binary/attributes.rs new file mode 100644 index 0000000..f90df4e --- /dev/null +++ b/src/writer/v7400/binary/attributes.rs @@ -0,0 +1,461 @@ +//! Node attributes writer. + +use std::{ + convert::TryFrom, + io::{self, Seek, SeekFrom, Write}, + mem::size_of, +}; + +use crate::{ + low::v7400::{ArrayAttributeEncoding, ArrayAttributeHeader, AttributeType}, + writer::v7400::binary::{Error, Result, Writer}, +}; + +/// Node attributes writer. +pub struct AttributesWriter<'a, W: Write> { + /// Inner writer. + writer: &'a mut Writer, +} + +macro_rules! impl_arr_from_iter { + ($( + $(#[$meta:meta])* + $name:ident: $ty_elem:ty { + from_result_iter: $name_from_result_iter:ident, + tyval: $tyval:ident, + ty_real: $ty_real:ty, + to_bytes: $to_bytes:expr, + }, + )*) => {$( + $(#[$meta])* + pub fn $name( + &mut self, + encoding: impl Into>, + iter: impl IntoIterator, + ) -> Result<()> { + let encoding = encoding.into().unwrap_or(ArrayAttributeEncoding::Direct); + if encoding != ArrayAttributeEncoding::Direct { + unimplemented!("encoding={:?}", encoding); + } + + let header_pos = self.initialize_array(AttributeType::$tyval, encoding)?; + + // Write elements. + let mut elements_count = 0u32; + iter.into_iter().try_for_each(|elem| -> Result<()> { + elements_count = elements_count + .checked_add(1) + .ok_or_else(|| Error::TooManyArrayAttributeElements(elements_count as usize + 1))?; + self.writer.sink().write_all( + &{$to_bytes}(elem) + )?; + + Ok(()) + })?; + + // Calculate header fields. + let bytelen = elements_count as usize * size_of::<$ty_real>(); + let bytelen = u32::try_from(bytelen).map_err(|_| Error::AttributeTooLong(bytelen))?; + + // Write real array header. + self.finalize_array( + header_pos, + &ArrayAttributeHeader { + elements_count, + encoding, + bytelen, + }, + )?; + + Ok(()) + } + + $(#[$meta])* + pub fn $name_from_result_iter( + &mut self, + encoding: impl Into>, + iter: impl IntoIterator>, + ) -> Result<()> + where + E: Into>, + { + let encoding = encoding.into().unwrap_or(ArrayAttributeEncoding::Direct); + if encoding != ArrayAttributeEncoding::Direct { + unimplemented!("encoding={:?}", encoding); + } + + let header_pos = self.initialize_array(AttributeType::$tyval, encoding)?; + + // Write elements. + let mut elements_count = 0u32; + iter.into_iter().try_for_each(|elem| -> Result<()> { + let elem = elem.map_err(|e| Error::UserDefined(e.into()))?; + elements_count = elements_count + .checked_add(1) + .ok_or_else(|| Error::TooManyArrayAttributeElements(elements_count as usize + 1))?; + self.writer.sink().write_all( + &{$to_bytes}(elem) + )?; + + Ok(()) + })?; + + // Calculate header fields. + let bytelen = elements_count as usize * size_of::<$ty_real>(); + let bytelen = u32::try_from(bytelen).map_err(|_| Error::AttributeTooLong(bytelen))?; + + // Write real array header. + self.finalize_array( + header_pos, + &ArrayAttributeHeader { + elements_count, + encoding, + bytelen, + }, + )?; + + Ok(()) + } + )*} +} + +impl<'a, W: Write + Seek> AttributesWriter<'a, W> { + /// Creates a new `AttributesWriter`. + pub(crate) fn new(writer: &'a mut Writer) -> Self { + Self { writer } + } + + /// Writes the given attribute type as type code. + fn write_type_code(&mut self, ty: AttributeType) -> Result<()> { + self.writer + .sink() + .write_all(&ty.type_code().to_le_bytes()) + .map_err(Into::into) + } + + /// Updates the node header. + fn update_node_header(&mut self) -> Result<()> { + let node_header = self + .writer + .current_node_header() + .expect("Should never fail: some nodes must be open if `AttributesWriter` exists"); + node_header.num_attributes = node_header + .num_attributes + .checked_add(1) + .ok_or_else(|| Error::TooManyAttributes(node_header.num_attributes as usize))?; + + Ok(()) + } + + /// Writes a single boolean attribute. + pub fn append_bool(&mut self, v: bool) -> Result<()> { + self.update_node_header()?; + self.write_type_code(AttributeType::Bool)?; + let v = if v { b'Y' } else { b'T' }; + self.writer + .sink() + .write_all(&v.to_le_bytes()) + .map_err(Into::into) + } + + /// Writes a single `i16` attribute. + pub fn append_i16(&mut self, v: i16) -> Result<()> { + self.update_node_header()?; + self.write_type_code(AttributeType::I16)?; + self.writer + .sink() + .write_all(&v.to_le_bytes()) + .map_err(Into::into) + } + + /// Writes a single `i32` attribute. + pub fn append_i32(&mut self, v: i32) -> Result<()> { + self.update_node_header()?; + self.write_type_code(AttributeType::I32)?; + self.writer + .sink() + .write_all(&v.to_le_bytes()) + .map_err(Into::into) + } + + /// Writes a single `i64` attribute. + pub fn append_i64(&mut self, v: i64) -> Result<()> { + self.update_node_header()?; + self.write_type_code(AttributeType::I64)?; + self.writer + .sink() + .write_all(&v.to_le_bytes()) + .map_err(Into::into) + } + + /// Writes a single `f32` attribute. + pub fn append_f32(&mut self, v: f32) -> Result<()> { + self.update_node_header()?; + self.write_type_code(AttributeType::F32)?; + self.writer + .sink() + .write_all(&v.to_bits().to_le_bytes()) + .map_err(Into::into) + } + + /// Writes a single `f64` attribute. + pub fn append_f64(&mut self, v: f64) -> Result<()> { + self.update_node_header()?; + self.write_type_code(AttributeType::F64)?; + self.writer + .sink() + .write_all(&v.to_bits().to_le_bytes()) + .map_err(Into::into) + } + + /// Writes the given array attribute header. + fn write_array_header(&mut self, header: &ArrayAttributeHeader) -> Result<()> { + self.writer + .sink() + .write_all(&header.elements_count.to_le_bytes())?; + self.writer + .sink() + .write_all(&header.encoding.to_u32().to_le_bytes())?; + self.writer + .sink() + .write_all(&header.bytelen.to_le_bytes())?; + + Ok(()) + } + + /// Writes some headers for an array attibute, and returns header position. + fn initialize_array( + &mut self, + ty: AttributeType, + encoding: ArrayAttributeEncoding, + ) -> Result { + self.update_node_header()?; + + // Write attribute header. + self.write_type_code(ty)?; + let header_pos = self.writer.sink().seek(SeekFrom::Current(0))?; + + // Write array header placeholder. + self.write_array_header(&ArrayAttributeHeader { + elements_count: 0, + encoding, + bytelen: 0, + })?; + + Ok(header_pos) + } + + /// Updates an array attribute header. + /// + /// Note that this should be called at the end of the array attribute. + fn finalize_array(&mut self, header_pos: u64, header: &ArrayAttributeHeader) -> Result<()> { + // Write real array header. + let end_pos = self.writer.sink().seek(SeekFrom::Current(0))?; + self.writer.sink().seek(SeekFrom::Start(header_pos))?; + self.write_array_header(header)?; + self.writer.sink().seek(SeekFrom::Start(end_pos))?; + + Ok(()) + } + + impl_arr_from_iter! { + /// Writes a boolean array attribute. + append_arr_bool_from_iter: bool { + from_result_iter: append_arr_bool_from_result_iter, + tyval: ArrBool, + ty_real: u8, + to_bytes: |elem: bool| if elem { [b'Y'] } else { [b'T'] }, + }, + + /// Writes an `i32` array attribute. + append_arr_i32_from_iter: i32 { + from_result_iter: append_arr_i32_from_result_iter, + tyval: ArrI32, + ty_real: i32, + to_bytes: |elem: i32| elem.to_le_bytes(), + }, + + /// Writes an `i64` array attribute. + append_arr_i64_from_iter: i64 { + from_result_iter: append_arr_i64_from_result_iter, + tyval: ArrI64, + ty_real: i64, + to_bytes: |elem: i64| elem.to_le_bytes(), + }, + + /// Writes an `f32` array attribute. + append_arr_f32_from_iter: f32 { + from_result_iter: append_arr_f32_from_result_iter, + tyval: ArrI32, + ty_real: f32, + to_bytes: |elem: f32| elem.to_bits().to_le_bytes(), + }, + + /// Writes an `f64` array attribute. + append_arr_f64_from_iter: f64 { + from_result_iter: append_arr_f64_from_result_iter, + tyval: ArrI64, + ty_real: f64, + to_bytes: |elem: f64| elem.to_bits().to_le_bytes(), + }, + } + + /// Writes some headers for a special attribute, and returns the special + /// header position. + fn initialize_special(&mut self, ty: AttributeType) -> Result { + self.update_node_header()?; + + // Write attribute header. + self.write_type_code(ty)?; + + // Write special attribute header (dummy). + let header_pos = self.writer.sink().seek(SeekFrom::Current(0))?; + self.writer.sink().write_all(&0u32.to_le_bytes())?; + + Ok(header_pos) + } + + /// Updates an array attribute header. + /// + /// Note that this should be called at the end of the array attribute. + fn finalize_special(&mut self, header_pos: u64, bytelen: usize) -> Result<()> { + // Calculate header fields. + let bytelen = u32::try_from(bytelen).map_err(|_| Error::AttributeTooLong(bytelen))?; + + // Write real special attribute header. + let end_pos = self.writer.sink().seek(SeekFrom::Current(0))?; + self.writer.sink().seek(SeekFrom::Start(header_pos))?; + self.writer.sink().write_all(&bytelen.to_le_bytes())?; + self.writer.sink().seek(SeekFrom::Start(end_pos))?; + + Ok(()) + } + + /// Writes a binary attribute. + pub fn append_binary_direct(&mut self, binary: &[u8]) -> Result<()> { + let header_pos = self.initialize_special(AttributeType::Binary)?; + + self.writer.sink().write_all(binary)?; + + self.finalize_special(header_pos, binary.len())?; + + Ok(()) + } + + /// Writes a string attribute. + pub fn append_string_direct(&mut self, string: &str) -> Result<()> { + let header_pos = self.initialize_special(AttributeType::String)?; + + self.writer.sink().write_all(string.as_ref())?; + + self.finalize_special(header_pos, string.len())?; + + Ok(()) + } + + /// Writes a binary attribute read from the given reader. + pub fn append_binary_from_reader(&mut self, mut reader: impl io::Read) -> Result<()> { + let header_pos = self.initialize_special(AttributeType::Binary)?; + + // Write bytes. + let written_len = io::copy(&mut reader, self.writer.sink())?; + + self.finalize_special(header_pos, written_len as usize)?; + + Ok(()) + } + + /// Writes a binary attribute from the given iterator. + pub fn append_binary_from_iter(&mut self, iter: impl IntoIterator) -> Result<()> { + let header_pos = self.initialize_special(AttributeType::Binary)?; + + let mut len = 0usize; + iter.into_iter().try_for_each(|v| -> Result<_> { + self.writer.sink().write_all(&[v])?; + len = len + .checked_add(1) + .ok_or_else(|| Error::AttributeTooLong(std::usize::MAX))?; + + Ok(()) + })?; + + self.finalize_special(header_pos, len)?; + + Ok(()) + } + + /// Writes a binary attribute from the given iterator. + pub fn append_binary_from_result_iter( + &mut self, + iter: impl IntoIterator>, + ) -> Result<()> + where + E: Into>, + { + let header_pos = self.initialize_special(AttributeType::Binary)?; + + let mut len = 0usize; + iter.into_iter().try_for_each(|v| -> Result<_> { + let v = v.map_err(|e| Error::UserDefined(e.into()))?; + self.writer.sink().write_all(&[v])?; + len = len + .checked_add(1) + .ok_or_else(|| Error::AttributeTooLong(std::usize::MAX))?; + + Ok(()) + })?; + + self.finalize_special(header_pos, len)?; + + Ok(()) + } + + /// Writes a string attribute from the given iterator. + pub fn append_string_from_iter(&mut self, iter: impl IntoIterator) -> Result<()> { + let header_pos = self.initialize_special(AttributeType::String)?; + + let buf = &mut [0u8; 4]; + let mut len = 0usize; + iter.into_iter().try_for_each(|c| -> Result<_> { + let char_len = c.encode_utf8(buf).len(); + self.writer.sink().write_all(buf)?; + len = len + .checked_add(char_len) + .ok_or_else(|| Error::AttributeTooLong(std::usize::MAX))?; + + Ok(()) + })?; + + self.finalize_special(header_pos, len)?; + + Ok(()) + } + + /// Writes a string attribute from the given iterator. + pub fn append_string_from_result_iter( + &mut self, + iter: impl IntoIterator>, + ) -> Result<()> + where + E: Into>, + { + let header_pos = self.initialize_special(AttributeType::String)?; + + let buf = &mut [0u8; 4]; + let mut len = 0usize; + iter.into_iter().try_for_each(|c| -> Result<_> { + let c = c.map_err(|e| Error::UserDefined(e.into()))?; + let char_len = c.encode_utf8(buf).len(); + self.writer.sink().write_all(buf)?; + len = len + .checked_add(char_len) + .ok_or_else(|| Error::AttributeTooLong(std::usize::MAX))?; + + Ok(()) + })?; + + self.finalize_special(header_pos, len)?; + + Ok(()) + } +} diff --git a/src/writer/v7400/binary/error.rs b/src/writer/v7400/binary/error.rs new file mode 100644 index 0000000..f067230 --- /dev/null +++ b/src/writer/v7400/binary/error.rs @@ -0,0 +1,70 @@ +//! Binary writer error. + +use std::{error, fmt, io}; + +use crate::low::FbxVersion; + +/// Write result. +pub type Result = std::result::Result; + +/// Write error. +#[derive(Debug)] +pub enum Error { + /// Node attribute is too long. + AttributeTooLong(usize), + /// File is too large. + FileTooLarge(u64), + /// I/O error. + Io(io::Error), + /// There are no nodes to close. + NoNodesToClose, + /// Node name is too long. + NodeNameTooLong(usize), + /// Too many array attribute elements. + TooManyArrayAttributeElements(usize), + /// Too many attributes. + TooManyAttributes(usize), + /// There remains unclosed nodes. + UnclosedNode(usize), + /// Unsupported FBX version. + UnsupportedFbxVersion(FbxVersion), + /// User-defined error. + UserDefined(Box), +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Error::Io(e) => Some(e), + Error::UserDefined(e) => Some(&**e), + _ => None, + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::AttributeTooLong(v) => write!(f, "Node attribute is too long: {} bytes", v), + Error::FileTooLarge(v) => write!(f, "File is too large: {} bytes", v), + Error::Io(e) => write!(f, "I/O error: {}", e), + Error::NoNodesToClose => write!(f, "There are no nodes to close"), + Error::NodeNameTooLong(v) => write!(f, "Node name is too long: {} bytes", v), + Error::TooManyArrayAttributeElements(v) => write!( + f, + "Too many array elements for a single node attribute: count={}", + v + ), + Error::TooManyAttributes(v) => write!(f, "Too many attributes: count={}", v), + Error::UnclosedNode(v) => write!(f, "There remains unclosed nodes: depth={}", v), + Error::UnsupportedFbxVersion(v) => write!(f, "Unsupported FBX version: {:?}", v), + Error::UserDefined(e) => write!(f, "User-defined error: {}", e), + } + } +} + +impl From for Error { + fn from(e: io::Error) -> Self { + Error::Io(e) + } +} diff --git a/src/writer/v7400/binary/footer.rs b/src/writer/v7400/binary/footer.rs new file mode 100644 index 0000000..2d508bc --- /dev/null +++ b/src/writer/v7400/binary/footer.rs @@ -0,0 +1,74 @@ +//! FBX footer. + +/// FBX footer padding length. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum FbxFooterPaddingLength { + /// Default (correct) value. + Default, + /// Forced specified value, which can be wrong. + Forced(u8), +} + +impl Default for FbxFooterPaddingLength { + fn default() -> Self { + FbxFooterPaddingLength::Default + } +} + +/// FBX 7.4 footer. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct FbxFooter<'a> { + /// Unknown (semirandom) 16-bytes data. + /// + /// This field is expected to have prescribed upper 4 bits, i.e. the field + /// is `fx bx ax 0x dx cx dx 6x bx 7x fx 8x 1x fx 2x 7x` if the FBX data is + /// exported from official SDK. + /// + /// Note that third party exporter will use completely random data. + pub unknown1: Option<&'a [u8; 16]>, + /// Padding length. + /// + /// Padding is `padding_len` `0`s. + /// `padding_len >= 0 && padding <= 15` should hold. + /// + /// Note that third party exporter will not use correct padding length. + pub padding_len: FbxFooterPaddingLength, + /// Unknown 4-bytes data. + /// + /// This is expected to be `[0u8; 4]`. + pub unknown2: Option<[u8; 4]>, + /// Unknown 16-bytes data. + /// + /// This is expected to be `[0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, + /// 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 0x29, 0x0b]`. + pub unknown3: Option<&'a [u8; 16]>, +} + +impl<'a> FbxFooter<'a> { + /// Returns the first unknown field or default. + pub(crate) fn unknown1(&self) -> &'a [u8; 16] { + /// Default value. + const DEFAULT: [u8; 16] = [ + 0xf0, 0xb1, 0xa2, 0x03, 0xd4, 0xc5, 0xd6, 0x67, 0xb8, 0x79, 0xfa, 0x8b, 0x1c, 0xfd, + 0x2e, 0x7f, + ]; + self.unknown1.unwrap_or(&DEFAULT) + } + + /// Returns the second unknown field or default. + pub(crate) fn unknown2(&self) -> [u8; 4] { + /// Default value. + const DEFAULT: [u8; 4] = [0; 4]; + self.unknown2.unwrap_or(DEFAULT) + } + + /// Returns the third unknown field or default. + pub(crate) fn unknown3(&self) -> &'a [u8; 16] { + /// Default value. + const DEFAULT: [u8; 16] = [ + 0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, + 0x29, 0x0b, + ]; + self.unknown3.unwrap_or(&DEFAULT) + } +} From 26999e72c7b95c6aec9fc94c657980b7d837e88d Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Sun, 21 Apr 2019 19:01:54 +0900 Subject: [PATCH 12/60] Add simple v7400 binary writer test --- tests/writer-binary-v7400.rs | 237 +++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 tests/writer-binary-v7400.rs diff --git a/tests/writer-binary-v7400.rs b/tests/writer-binary-v7400.rs new file mode 100644 index 0000000..bc2e463 --- /dev/null +++ b/tests/writer-binary-v7400.rs @@ -0,0 +1,237 @@ +//! Writer test. + +#[cfg(feature = "writer")] +mod tests { + use std::{cell::RefCell, io::Cursor, iter, rc::Rc}; + + use fbxcel::{ + low::{v7400::AttributeValue, FbxVersion}, + pull_parser::{ + any::{from_seekable_reader, AnyParser}, + v7400::{Attributes, Event, Parser}, + Error as ParseError, ParserSource, + }, + writer::v7400::binary::{FbxFooter, Writer}, + }; + + const MAGIC: &[u8] = b"Kaydara FBX Binary \x00\x1a\x00"; + + const CUSTOM_UNKNOWN1: [u8; 16] = [ + 0xff, 0xbe, 0xad, 0x0c, 0xdb, 0xca, 0xd9, 0x68, 0xb7, 0x76, 0xf5, 0x84, 0x13, 0xf2, 0x21, + 0x70, + ]; + + const UNKNOWN3: [u8; 16] = [ + 0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 0x29, + 0x0b, + ]; + + #[test] + fn empty_write_v7400() -> Result<(), Box> { + let mut dest = Vec::new(); + let cursor = Cursor::new(&mut dest); + let writer = Writer::new(cursor, FbxVersion::V7_4)?; + let footer = FbxFooter { + unknown1: Some(&CUSTOM_UNKNOWN1), + padding_len: Default::default(), + unknown2: None, + unknown3: None, + }; + writer.finalize_and_flush(&footer)?; + + let expected = { + let raw_ver = 7400u32; + let mut vec = Vec::new(); + // Header. + { + // Magic. + vec.extend(MAGIC); + // Version. + vec.extend(&raw_ver.to_le_bytes()); + } + // No nodes. + { + // End of implicit root. + { + vec.extend(iter::repeat(0).take(4 * 3 + 1)); + } + } + // Footer. + { + // Footer: unknown1. + vec.extend(&CUSTOM_UNKNOWN1); + // Footer: padding. + { + let len = vec.len().wrapping_neg() % 16; + assert_eq!((vec.len() + len) % 16, 0); + vec.extend(iter::repeat(0).take(len)); + } + // Footer: unknown2. + vec.extend(&[0; 4]); + // Footer: FBX version. + vec.extend(&raw_ver.to_le_bytes()); + // Footer: 120 zeroes. + vec.extend(iter::repeat(0).take(120)); + // Footer: unknown3. + vec.extend(&UNKNOWN3); + } + vec + }; + + assert_eq!(dest.len() % 16, 0); + assert_eq!(dest, expected); + + let mut parser = match from_seekable_reader(Cursor::new(dest))? { + AnyParser::V7400(parser) => parser, + _ => panic!("Generated data should be parsable with v7400 parser"), + }; + let warnings = Rc::new(RefCell::new(Vec::new())); + parser.set_warning_handler({ + let warnings = warnings.clone(); + move |warning, _pos| { + warnings.borrow_mut().push(warning); + Ok(()) + } + }); + assert_eq!(parser.fbx_version(), FbxVersion::V7_4); + + { + let footer_res = expect_fbx_end(&mut parser)?; + let footer = footer_res?; + assert_eq!(footer.unknown1, CUSTOM_UNKNOWN1); + assert_eq!(footer.unknown2, [0u8; 4]); + assert_eq!(footer.unknown3, UNKNOWN3); + } + + assert_eq!(warnings.borrow().len(), 0); + + Ok(()) + } + + #[cfg(feature = "writer")] + #[test] + fn tree_write_7500() -> Result<(), Box> { + use fbxcel::tree::v7400::Tree; + + let mut tree = Tree::default(); + { + // Node 0: Without attributes, with children. + // Node 0-x: Without attributes, without children. + let node0_id = tree.append_new(tree.root().node_id(), "Node0"); + tree.append_new(node0_id, "Node0-0"); + tree.append_new(node0_id, "Node0-1"); + + // Node 1: With attributes, with children. + // Node 1-x: With attributes, without children. + let node1_id = tree.append_new(tree.root().node_id(), "Node1"); + tree.append_attribute(node1_id, AttributeValue::Bool(true)); + let node1_1_id = tree.append_new(node1_id, "Node1-0"); + tree.append_attribute(node1_1_id, AttributeValue::I32(42)); + tree.append_attribute(node1_1_id, AttributeValue::F64(3.14)); + let node1_2_id = tree.append_new(node1_id, "Node1-1"); + tree.append_attribute(node1_2_id, AttributeValue::Binary(vec![1, 2, 4, 8, 16])); + tree.append_attribute(node1_2_id, AttributeValue::String("Hello, world".into())); + } + + let mut dest = Vec::new(); + let cursor = Cursor::new(&mut dest); + let mut writer = Writer::new(cursor, FbxVersion::V7_5)?; + let footer = FbxFooter { + unknown1: Some(&CUSTOM_UNKNOWN1), + padding_len: Default::default(), + unknown2: None, + unknown3: None, + }; + writer.write_tree(&tree)?; + writer.finalize_and_flush(&footer)?; + + let mut parser = match from_seekable_reader(Cursor::new(dest))? { + AnyParser::V7400(parser) => parser, + _ => panic!("Generated data should be parsable with v7400 parser"), + }; + let warnings = Rc::new(RefCell::new(Vec::new())); + parser.set_warning_handler({ + let warnings = warnings.clone(); + move |warning, _pos| { + warnings.borrow_mut().push(warning); + Ok(()) + } + }); + assert_eq!(parser.fbx_version(), FbxVersion::V7_5); + + { + let attrs = expect_node_start(&mut parser, "Node0")?; + assert_eq!(attrs.total_count(), 0); + } + { + let attrs = expect_node_start(&mut parser, "Node0-0")?; + assert_eq!(attrs.total_count(), 0); + } + expect_node_end(&mut parser)?; + { + let attrs = expect_node_start(&mut parser, "Node0-1")?; + assert_eq!(attrs.total_count(), 0); + } + expect_node_end(&mut parser)?; + expect_node_end(&mut parser)?; + { + let attrs = expect_node_start(&mut parser, "Node1")?; + assert_eq!(attrs.total_count(), 1); + } + { + let attrs = expect_node_start(&mut parser, "Node1-0")?; + assert_eq!(attrs.total_count(), 2); + } + expect_node_end(&mut parser)?; + { + let attrs = expect_node_start(&mut parser, "Node1-1")?; + assert_eq!(attrs.total_count(), 2); + } + expect_node_end(&mut parser)?; + expect_node_end(&mut parser)?; + + { + let footer_res = expect_fbx_end(&mut parser)?; + let footer = footer_res?; + assert_eq!(footer.unknown1, CUSTOM_UNKNOWN1); + assert_eq!(footer.unknown2, [0u8; 4]); + assert_eq!(footer.unknown3, UNKNOWN3); + } + + assert_eq!(warnings.borrow().len(), 0); + + Ok(()) + } + + fn expect_node_start<'a, R: ParserSource + std::fmt::Debug>( + parser: &'a mut Parser, + name: &str, + ) -> Result, Box> { + match parser.next_event()? { + Event::StartNode(node) => { + assert_eq!(node.name(), name); + Ok(node.attributes()) + } + ev => panic!("Unexpected event: {:?}", ev), + } + } + + fn expect_node_end( + parser: &mut Parser, + ) -> Result<(), Box> { + match parser.next_event()? { + Event::EndNode => Ok(()), + ev => panic!("Unexpected event: {:?}", ev), + } + } + + fn expect_fbx_end( + parser: &mut Parser, + ) -> Result, ParseError>, Box> + { + match parser.next_event()? { + Event::EndFbx(footer_res) => Ok(footer_res), + ev => panic!("Unexpected event: {:?}", ev), + } + } +} From 946a060732bed3ea95d7ab855c6f27a334d7ce92 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Sun, 21 Apr 2019 23:10:53 +0900 Subject: [PATCH 13/60] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4008451..87a4db5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,16 @@ * Documents are improved a little. * Manual tree construction support is added. +* FBX binary writer is added. ### Added * Manual tree construction support is added. + Methods to add new nodes and attributes are added. + Complete modification is not yet supported, for example modifying already added attributes or removing nodes. +* FBX binary writer is added. + + `writer::v7400::binary` contains FBX binary writer stuff. + + This can be enabled by `writer` feature. ## [0.4.3] From c75cb13e0f5d61a93fd66b92955b2e5eac728e3c Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Sun, 21 Apr 2019 23:22:57 +0900 Subject: [PATCH 14/60] Replace `read_fbx_header()` to `load()` in doc comment `FbxHeader::read_fbx_header` is deprecated. --- src/pull_parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pull_parser.rs b/src/pull_parser.rs index 9adf2ee..835fe3e 100644 --- a/src/pull_parser.rs +++ b/src/pull_parser.rs @@ -56,7 +56,7 @@ //! let mut reader = std::io::BufReader::new(file); //! //! // 1. Get FBX header. -//! let header = FbxHeader::read_fbx_header(&mut reader) +//! let header = FbxHeader::load(&mut reader) //! .expect("Failed to load FBX header"); //! // 2. Decide which version of parser to use. //! match header.parser_version() { From 52f67b6b7ae63029a59caf0faa8f5867e54139bf Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 01:32:11 +0900 Subject: [PATCH 15/60] Add `CompressionError` type and `Error::Compression` variant Zlib compression can fail. --- src/writer/v7400/binary.rs | 2 +- src/writer/v7400/binary/error.rs | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/writer/v7400/binary.rs b/src/writer/v7400/binary.rs index 8139269..e056075 100644 --- a/src/writer/v7400/binary.rs +++ b/src/writer/v7400/binary.rs @@ -11,7 +11,7 @@ use crate::low::{v7400::NodeHeader, FbxVersion, MAGIC}; pub use self::{ attributes::AttributesWriter, - error::{Error, Result}, + error::{CompressionError, Error, Result}, footer::{FbxFooter, FbxFooterPaddingLength}, }; diff --git a/src/writer/v7400/binary/error.rs b/src/writer/v7400/binary/error.rs index f067230..3f754d4 100644 --- a/src/writer/v7400/binary/error.rs +++ b/src/writer/v7400/binary/error.rs @@ -12,6 +12,8 @@ pub type Result = std::result::Result; pub enum Error { /// Node attribute is too long. AttributeTooLong(usize), + /// Compression error. + Compression(CompressionError), /// File is too large. FileTooLarge(u64), /// I/O error. @@ -35,6 +37,7 @@ pub enum Error { impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { + Error::Compression(e) => Some(e), Error::Io(e) => Some(e), Error::UserDefined(e) => Some(&**e), _ => None, @@ -46,6 +49,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::AttributeTooLong(v) => write!(f, "Node attribute is too long: {} bytes", v), + Error::Compression(e) => write!(f, "Compression error: {}", e), Error::FileTooLarge(v) => write!(f, "File is too large: {} bytes", v), Error::Io(e) => write!(f, "I/O error: {}", e), Error::NoNodesToClose => write!(f, "There are no nodes to close"), @@ -68,3 +72,32 @@ impl From for Error { Error::Io(e) } } + +impl From for Error { + fn from(e: CompressionError) -> Self { + Error::Compression(e) + } +} + +/// Compression error. +#[derive(Debug)] +pub enum CompressionError { + /// Zlib error. + Zlib(io::Error), +} + +impl error::Error for CompressionError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + CompressionError::Zlib(e) => Some(e), + } + } +} + +impl fmt::Display for CompressionError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + CompressionError::Zlib(e) => write!(f, "Zlib compression error: {}", e), + } + } +} From d97c21640c6b454fabaab60eb32d9642d51d51ff Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 01:32:54 +0900 Subject: [PATCH 16/60] Add support for Zlib compression of array attributes --- src/writer/v7400/binary/attributes.rs | 110 ++++++++++++++++++-------- 1 file changed, 78 insertions(+), 32 deletions(-) diff --git a/src/writer/v7400/binary/attributes.rs b/src/writer/v7400/binary/attributes.rs index f90df4e..d8837fa 100644 --- a/src/writer/v7400/binary/attributes.rs +++ b/src/writer/v7400/binary/attributes.rs @@ -8,7 +8,7 @@ use std::{ use crate::{ low::v7400::{ArrayAttributeEncoding, ArrayAttributeHeader, AttributeType}, - writer::v7400::binary::{Error, Result, Writer}, + writer::v7400::binary::{CompressionError, Error, Result, Writer}, }; /// Node attributes writer. @@ -34,27 +34,49 @@ macro_rules! impl_arr_from_iter { iter: impl IntoIterator, ) -> Result<()> { let encoding = encoding.into().unwrap_or(ArrayAttributeEncoding::Direct); - if encoding != ArrayAttributeEncoding::Direct { - unimplemented!("encoding={:?}", encoding); - } let header_pos = self.initialize_array(AttributeType::$tyval, encoding)?; + + fn write_elements( + mut writer: impl Write, + iter: impl IntoIterator, + ) -> Result { + let mut elements_count = 0u32; + iter.into_iter().try_for_each(|elem| -> Result<()> { + elements_count = elements_count + .checked_add(1) + .ok_or_else(|| Error::TooManyArrayAttributeElements(elements_count as usize + 1))?; + writer.write_all( + &{$to_bytes}(elem) + )?; + + Ok(()) + })?; + Ok(elements_count) + } + // Write elements. - let mut elements_count = 0u32; - iter.into_iter().try_for_each(|elem| -> Result<()> { - elements_count = elements_count - .checked_add(1) - .ok_or_else(|| Error::TooManyArrayAttributeElements(elements_count as usize + 1))?; - self.writer.sink().write_all( - &{$to_bytes}(elem) - )?; - - Ok(()) - })?; + let (elements_count, bytelen) = match encoding { + ArrayAttributeEncoding::Direct => { + let elements_count = write_elements(self.writer.sink(), iter)?; + let bytelen = elements_count as usize * size_of::<$ty_real>(); + (elements_count, bytelen) + }, + ArrayAttributeEncoding::Zlib => { + let start_pos = self.writer.sink().seek(SeekFrom::Current(0))?; + let elements_count = { + let mut sink = libflate::zlib::Encoder::new(self.writer.sink())?; + let count = write_elements(&mut sink, iter)?; + sink.finish().into_result().map_err(CompressionError::Zlib)?; + count + }; + let end_pos = self.writer.sink().seek(SeekFrom::Current(0))?; + (elements_count, (end_pos - start_pos) as usize) + }, + }; // Calculate header fields. - let bytelen = elements_count as usize * size_of::<$ty_real>(); let bytelen = u32::try_from(bytelen).map_err(|_| Error::AttributeTooLong(bytelen))?; // Write real array header. @@ -80,28 +102,52 @@ macro_rules! impl_arr_from_iter { E: Into>, { let encoding = encoding.into().unwrap_or(ArrayAttributeEncoding::Direct); - if encoding != ArrayAttributeEncoding::Direct { - unimplemented!("encoding={:?}", encoding); - } let header_pos = self.initialize_array(AttributeType::$tyval, encoding)?; + fn write_elements( + mut writer: impl Write, + iter: impl IntoIterator>, + ) -> Result + where + E: Into>, + { + let mut elements_count = 0u32; + iter.into_iter().try_for_each(|elem| -> Result<()> { + let elem = elem.map_err(|e| Error::UserDefined(e.into()))?; + elements_count = elements_count + .checked_add(1) + .ok_or_else(|| Error::TooManyArrayAttributeElements(elements_count as usize + 1))?; + writer.write_all( + &{$to_bytes}(elem) + )?; + + Ok(()) + })?; + Ok(elements_count) + } + // Write elements. - let mut elements_count = 0u32; - iter.into_iter().try_for_each(|elem| -> Result<()> { - let elem = elem.map_err(|e| Error::UserDefined(e.into()))?; - elements_count = elements_count - .checked_add(1) - .ok_or_else(|| Error::TooManyArrayAttributeElements(elements_count as usize + 1))?; - self.writer.sink().write_all( - &{$to_bytes}(elem) - )?; - - Ok(()) - })?; + let (elements_count, bytelen) = match encoding { + ArrayAttributeEncoding::Direct => { + let elements_count = write_elements(self.writer.sink(), iter)?; + let bytelen = elements_count as usize * size_of::<$ty_real>(); + (elements_count, bytelen) + }, + ArrayAttributeEncoding::Zlib => { + let start_pos = self.writer.sink().seek(SeekFrom::Current(0))?; + let elements_count = { + let mut sink = libflate::zlib::Encoder::new(self.writer.sink())?; + let count = write_elements(&mut sink, iter)?; + sink.finish().into_result().map_err(CompressionError::Zlib)?; + count + }; + let end_pos = self.writer.sink().seek(SeekFrom::Current(0))?; + (elements_count, (end_pos - start_pos) as usize) + }, + }; // Calculate header fields. - let bytelen = elements_count as usize * size_of::<$ty_real>(); let bytelen = u32::try_from(bytelen).map_err(|_| Error::AttributeTooLong(bytelen))?; // Write real array header. From 3d3bfe1639b92c30c7fab9a7983df3075ad46e9f Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 02:40:22 +0900 Subject: [PATCH 17/60] Introduce `IntoBytes` trait and use macro to define methods --- src/writer/v7400/binary/attributes.rs | 132 ++++++++++++++------------ 1 file changed, 73 insertions(+), 59 deletions(-) diff --git a/src/writer/v7400/binary/attributes.rs b/src/writer/v7400/binary/attributes.rs index d8837fa..3447ae7 100644 --- a/src/writer/v7400/binary/attributes.rs +++ b/src/writer/v7400/binary/attributes.rs @@ -11,12 +11,72 @@ use crate::{ writer::v7400::binary::{CompressionError, Error, Result, Writer}, }; +/// A trait for types which can be represented as single bytes array. +pub(crate) trait IntoBytes: Sized { + /// Calls the given function with the bytes array. + fn call_with_le_bytes(self, f: impl FnOnce(&[u8]) -> R) -> R; +} + +impl IntoBytes for bool { + fn call_with_le_bytes(self, f: impl FnOnce(&[u8]) -> R) -> R { + let v = if self { b'Y' } else { b'T' }; + f(&v.to_le_bytes()) + } +} + +impl IntoBytes for i16 { + fn call_with_le_bytes(self, f: impl FnOnce(&[u8]) -> R) -> R { + f(&self.to_le_bytes()) + } +} + +impl IntoBytes for i32 { + fn call_with_le_bytes(self, f: impl FnOnce(&[u8]) -> R) -> R { + f(&self.to_le_bytes()) + } +} + +impl IntoBytes for i64 { + fn call_with_le_bytes(self, f: impl FnOnce(&[u8]) -> R) -> R { + f(&self.to_le_bytes()) + } +} + +impl IntoBytes for f32 { + fn call_with_le_bytes(self, f: impl FnOnce(&[u8]) -> R) -> R { + f(&self.to_bits().to_le_bytes()) + } +} + +impl IntoBytes for f64 { + fn call_with_le_bytes(self, f: impl FnOnce(&[u8]) -> R) -> R { + f(&self.to_bits().to_le_bytes()) + } +} + /// Node attributes writer. pub struct AttributesWriter<'a, W: Write> { /// Inner writer. writer: &'a mut Writer, } +macro_rules! impl_single_attr_append { + ($( + $(#[$meta:meta])* + $method:ident($ty:ty): $variant:ident; + )*) => { + $( + $(#[$meta])* + pub fn $method(&mut self, v: $ty) -> Result<()> { + self.update_node_header()?; + self.write_type_code(AttributeType::$variant)?; + v.call_with_le_bytes(|bytes| self.writer.sink().write_all(bytes)) + .map_err(Into::into) + } + )* + } +} + macro_rules! impl_arr_from_iter { ($( $(#[$meta:meta])* @@ -193,65 +253,19 @@ impl<'a, W: Write + Seek> AttributesWriter<'a, W> { Ok(()) } - /// Writes a single boolean attribute. - pub fn append_bool(&mut self, v: bool) -> Result<()> { - self.update_node_header()?; - self.write_type_code(AttributeType::Bool)?; - let v = if v { b'Y' } else { b'T' }; - self.writer - .sink() - .write_all(&v.to_le_bytes()) - .map_err(Into::into) - } - - /// Writes a single `i16` attribute. - pub fn append_i16(&mut self, v: i16) -> Result<()> { - self.update_node_header()?; - self.write_type_code(AttributeType::I16)?; - self.writer - .sink() - .write_all(&v.to_le_bytes()) - .map_err(Into::into) - } - - /// Writes a single `i32` attribute. - pub fn append_i32(&mut self, v: i32) -> Result<()> { - self.update_node_header()?; - self.write_type_code(AttributeType::I32)?; - self.writer - .sink() - .write_all(&v.to_le_bytes()) - .map_err(Into::into) - } - - /// Writes a single `i64` attribute. - pub fn append_i64(&mut self, v: i64) -> Result<()> { - self.update_node_header()?; - self.write_type_code(AttributeType::I64)?; - self.writer - .sink() - .write_all(&v.to_le_bytes()) - .map_err(Into::into) - } - - /// Writes a single `f32` attribute. - pub fn append_f32(&mut self, v: f32) -> Result<()> { - self.update_node_header()?; - self.write_type_code(AttributeType::F32)?; - self.writer - .sink() - .write_all(&v.to_bits().to_le_bytes()) - .map_err(Into::into) - } - - /// Writes a single `f64` attribute. - pub fn append_f64(&mut self, v: f64) -> Result<()> { - self.update_node_header()?; - self.write_type_code(AttributeType::F64)?; - self.writer - .sink() - .write_all(&v.to_bits().to_le_bytes()) - .map_err(Into::into) + impl_single_attr_append! { + /// Writes a single boolean attribute. + append_bool(bool): Bool; + /// Writes a single `i16` attribute. + append_i16(i16): I16; + /// Writes a single `i32` attribute. + append_i32(i32): I32; + /// Writes a single `i64` attribute. + append_i64(i64): I64; + /// Writes a single `f32` attribute. + append_f32(f32): F32; + /// Writes a single `f64` attribute. + append_f64(f64): F64; } /// Writes the given array attribute header. From b56b7b0157b6734ea56e9d8ea9c96909f204864c Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 02:40:53 +0900 Subject: [PATCH 18/60] Move some codes to submodule, outside macro --- src/writer/v7400/binary/attributes.rs | 58 +++--------------- src/writer/v7400/binary/attributes/array.rs | 66 +++++++++++++++++++++ 2 files changed, 73 insertions(+), 51 deletions(-) create mode 100644 src/writer/v7400/binary/attributes/array.rs diff --git a/src/writer/v7400/binary/attributes.rs b/src/writer/v7400/binary/attributes.rs index 3447ae7..015598d 100644 --- a/src/writer/v7400/binary/attributes.rs +++ b/src/writer/v7400/binary/attributes.rs @@ -11,6 +11,8 @@ use crate::{ writer::v7400::binary::{CompressionError, Error, Result, Writer}, }; +mod array; + /// A trait for types which can be represented as single bytes array. pub(crate) trait IntoBytes: Sized { /// Calls the given function with the bytes array. @@ -84,7 +86,6 @@ macro_rules! impl_arr_from_iter { from_result_iter: $name_from_result_iter:ident, tyval: $tyval:ident, ty_real: $ty_real:ty, - to_bytes: $to_bytes:expr, }, )*) => {$( $(#[$meta])* @@ -97,29 +98,10 @@ macro_rules! impl_arr_from_iter { let header_pos = self.initialize_array(AttributeType::$tyval, encoding)?; - - fn write_elements( - mut writer: impl Write, - iter: impl IntoIterator, - ) -> Result { - let mut elements_count = 0u32; - iter.into_iter().try_for_each(|elem| -> Result<()> { - elements_count = elements_count - .checked_add(1) - .ok_or_else(|| Error::TooManyArrayAttributeElements(elements_count as usize + 1))?; - writer.write_all( - &{$to_bytes}(elem) - )?; - - Ok(()) - })?; - Ok(elements_count) - } - // Write elements. let (elements_count, bytelen) = match encoding { ArrayAttributeEncoding::Direct => { - let elements_count = write_elements(self.writer.sink(), iter)?; + let elements_count = array::write_elements_direct_iter(self.writer.sink(), iter)?; let bytelen = elements_count as usize * size_of::<$ty_real>(); (elements_count, bytelen) }, @@ -127,7 +109,7 @@ macro_rules! impl_arr_from_iter { let start_pos = self.writer.sink().seek(SeekFrom::Current(0))?; let elements_count = { let mut sink = libflate::zlib::Encoder::new(self.writer.sink())?; - let count = write_elements(&mut sink, iter)?; + let count = array::write_elements_direct_iter(&mut sink, iter)?; sink.finish().into_result().map_err(CompressionError::Zlib)?; count }; @@ -165,32 +147,11 @@ macro_rules! impl_arr_from_iter { let header_pos = self.initialize_array(AttributeType::$tyval, encoding)?; - fn write_elements( - mut writer: impl Write, - iter: impl IntoIterator>, - ) -> Result - where - E: Into>, - { - let mut elements_count = 0u32; - iter.into_iter().try_for_each(|elem| -> Result<()> { - let elem = elem.map_err(|e| Error::UserDefined(e.into()))?; - elements_count = elements_count - .checked_add(1) - .ok_or_else(|| Error::TooManyArrayAttributeElements(elements_count as usize + 1))?; - writer.write_all( - &{$to_bytes}(elem) - )?; - - Ok(()) - })?; - Ok(elements_count) - } - // Write elements. + let iter = iter.into_iter().map(|res| res.map_err(|e| Error::UserDefined(e.into()))); let (elements_count, bytelen) = match encoding { ArrayAttributeEncoding::Direct => { - let elements_count = write_elements(self.writer.sink(), iter)?; + let elements_count = array::write_elements_result_iter(self.writer.sink(), iter)?; let bytelen = elements_count as usize * size_of::<$ty_real>(); (elements_count, bytelen) }, @@ -198,7 +159,7 @@ macro_rules! impl_arr_from_iter { let start_pos = self.writer.sink().seek(SeekFrom::Current(0))?; let elements_count = { let mut sink = libflate::zlib::Encoder::new(self.writer.sink())?; - let count = write_elements(&mut sink, iter)?; + let count = array::write_elements_result_iter(&mut sink, iter)?; sink.finish().into_result().map_err(CompressionError::Zlib)?; count }; @@ -324,7 +285,6 @@ impl<'a, W: Write + Seek> AttributesWriter<'a, W> { from_result_iter: append_arr_bool_from_result_iter, tyval: ArrBool, ty_real: u8, - to_bytes: |elem: bool| if elem { [b'Y'] } else { [b'T'] }, }, /// Writes an `i32` array attribute. @@ -332,7 +292,6 @@ impl<'a, W: Write + Seek> AttributesWriter<'a, W> { from_result_iter: append_arr_i32_from_result_iter, tyval: ArrI32, ty_real: i32, - to_bytes: |elem: i32| elem.to_le_bytes(), }, /// Writes an `i64` array attribute. @@ -340,7 +299,6 @@ impl<'a, W: Write + Seek> AttributesWriter<'a, W> { from_result_iter: append_arr_i64_from_result_iter, tyval: ArrI64, ty_real: i64, - to_bytes: |elem: i64| elem.to_le_bytes(), }, /// Writes an `f32` array attribute. @@ -348,7 +306,6 @@ impl<'a, W: Write + Seek> AttributesWriter<'a, W> { from_result_iter: append_arr_f32_from_result_iter, tyval: ArrI32, ty_real: f32, - to_bytes: |elem: f32| elem.to_bits().to_le_bytes(), }, /// Writes an `f64` array attribute. @@ -356,7 +313,6 @@ impl<'a, W: Write + Seek> AttributesWriter<'a, W> { from_result_iter: append_arr_f64_from_result_iter, tyval: ArrI64, ty_real: f64, - to_bytes: |elem: f64| elem.to_bits().to_le_bytes(), }, } diff --git a/src/writer/v7400/binary/attributes/array.rs b/src/writer/v7400/binary/attributes/array.rs new file mode 100644 index 0000000..104f9d1 --- /dev/null +++ b/src/writer/v7400/binary/attributes/array.rs @@ -0,0 +1,66 @@ +//! Array attributes things. + +use std::{convert::TryFrom, io::Write}; + +use crate::writer::v7400::binary::{attributes::IntoBytes, Error, Result}; + +/// A trait for types which can be represented as multiple bytes array. +pub(crate) trait IntoBytesMulti: Sized { + /// Calls the given function with the bytes array multiple times. + fn call_with_le_bytes_multi( + self, + f: impl FnMut(&[u8]) -> std::result::Result<(), E>, + ) -> std::result::Result; +} + +impl>> IntoBytesMulti for I { + fn call_with_le_bytes_multi( + self, + mut f: impl FnMut(&[u8]) -> std::result::Result<(), E>, + ) -> std::result::Result { + let mut count = 0usize; + self.into_iter() + .inspect(|_| count = count.checked_add(1).expect("Too many elements")) + .try_for_each(|elem| elem?.call_with_le_bytes(&mut f))?; + + Ok(count) + } +} + +/// Writes array elements into the given writer. +pub(crate) fn write_elements_direct_iter( + writer: impl Write, + iter: impl IntoIterator, +) -> Result +where + T: IntoBytes, +{ + /// A dummy type for impossible error. + enum Never {} + impl From for Error { + fn from(_: Never) -> Self { + unreachable!("Should never happen") + } + } + write_elements_result_iter(writer, iter.into_iter().map(Ok::<_, Never>)) +} + +/// Writes array elements into the given writer. +pub(crate) fn write_elements_result_iter( + mut writer: impl Write, + iter: impl IntoIterator>, +) -> Result +where + T: IntoBytes, + E: Into, + //Error: From, +{ + let elements_count = iter + .into_iter() + .map(|res| res.map_err(Into::into)) + .call_with_le_bytes_multi(|bytes| writer.write_all(bytes).map_err(Into::into))?; + let elements_count = u32::try_from(elements_count) + .map_err(|_| Error::TooManyArrayAttributeElements(elements_count + 1))?; + + Ok(elements_count) +} From 173284e7b9110d25ecfd925eae57236f40fab01c Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 03:03:35 +0900 Subject: [PATCH 19/60] Move more array-related codes to sub module Now `impl_arr_from_iter` is a simple macro to call single function. --- src/writer/v7400/binary/attributes.rs | 126 +++++--------------- src/writer/v7400/binary/attributes/array.rs | 86 +++++++++---- 2 files changed, 94 insertions(+), 118 deletions(-) diff --git a/src/writer/v7400/binary/attributes.rs b/src/writer/v7400/binary/attributes.rs index 015598d..1da9f3f 100644 --- a/src/writer/v7400/binary/attributes.rs +++ b/src/writer/v7400/binary/attributes.rs @@ -3,16 +3,24 @@ use std::{ convert::TryFrom, io::{self, Seek, SeekFrom, Write}, - mem::size_of, }; use crate::{ low::v7400::{ArrayAttributeEncoding, ArrayAttributeHeader, AttributeType}, - writer::v7400::binary::{CompressionError, Error, Result, Writer}, + writer::v7400::binary::{Error, Result, Writer}, }; mod array; +/// A dummy type for impossible error. +pub(crate) enum Never {} + +impl From for Error { + fn from(_: Never) -> Self { + unreachable!("Should never happen") + } +} + /// A trait for types which can be represented as single bytes array. pub(crate) trait IntoBytes: Sized { /// Calls the given function with the bytes array. @@ -85,7 +93,6 @@ macro_rules! impl_arr_from_iter { $name:ident: $ty_elem:ty { from_result_iter: $name_from_result_iter:ident, tyval: $tyval:ident, - ty_real: $ty_real:ty, }, )*) => {$( $(#[$meta])* @@ -94,44 +101,12 @@ macro_rules! impl_arr_from_iter { encoding: impl Into>, iter: impl IntoIterator, ) -> Result<()> { - let encoding = encoding.into().unwrap_or(ArrayAttributeEncoding::Direct); - - let header_pos = self.initialize_array(AttributeType::$tyval, encoding)?; - - // Write elements. - let (elements_count, bytelen) = match encoding { - ArrayAttributeEncoding::Direct => { - let elements_count = array::write_elements_direct_iter(self.writer.sink(), iter)?; - let bytelen = elements_count as usize * size_of::<$ty_real>(); - (elements_count, bytelen) - }, - ArrayAttributeEncoding::Zlib => { - let start_pos = self.writer.sink().seek(SeekFrom::Current(0))?; - let elements_count = { - let mut sink = libflate::zlib::Encoder::new(self.writer.sink())?; - let count = array::write_elements_direct_iter(&mut sink, iter)?; - sink.finish().into_result().map_err(CompressionError::Zlib)?; - count - }; - let end_pos = self.writer.sink().seek(SeekFrom::Current(0))?; - (elements_count, (end_pos - start_pos) as usize) - }, - }; - - // Calculate header fields. - let bytelen = u32::try_from(bytelen).map_err(|_| Error::AttributeTooLong(bytelen))?; - - // Write real array header. - self.finalize_array( - header_pos, - &ArrayAttributeHeader { - elements_count, - encoding, - bytelen, - }, - )?; - - Ok(()) + array::write_array_attr_result_iter( + self, + AttributeType::$tyval, + encoding.into(), + iter.into_iter().map(Ok::<_, Never>), + ) } $(#[$meta])* @@ -143,45 +118,12 @@ macro_rules! impl_arr_from_iter { where E: Into>, { - let encoding = encoding.into().unwrap_or(ArrayAttributeEncoding::Direct); - - let header_pos = self.initialize_array(AttributeType::$tyval, encoding)?; - - // Write elements. - let iter = iter.into_iter().map(|res| res.map_err(|e| Error::UserDefined(e.into()))); - let (elements_count, bytelen) = match encoding { - ArrayAttributeEncoding::Direct => { - let elements_count = array::write_elements_result_iter(self.writer.sink(), iter)?; - let bytelen = elements_count as usize * size_of::<$ty_real>(); - (elements_count, bytelen) - }, - ArrayAttributeEncoding::Zlib => { - let start_pos = self.writer.sink().seek(SeekFrom::Current(0))?; - let elements_count = { - let mut sink = libflate::zlib::Encoder::new(self.writer.sink())?; - let count = array::write_elements_result_iter(&mut sink, iter)?; - sink.finish().into_result().map_err(CompressionError::Zlib)?; - count - }; - let end_pos = self.writer.sink().seek(SeekFrom::Current(0))?; - (elements_count, (end_pos - start_pos) as usize) - }, - }; - - // Calculate header fields. - let bytelen = u32::try_from(bytelen).map_err(|_| Error::AttributeTooLong(bytelen))?; - - // Write real array header. - self.finalize_array( - header_pos, - &ArrayAttributeHeader { - elements_count, - encoding, - bytelen, - }, - )?; - - Ok(()) + array::write_array_attr_result_iter( + self, + AttributeType::$tyval, + encoding.into(), + iter.into_iter().map(|res| res.map_err(|e| Error::UserDefined(e.into()))), + ) } )*} } @@ -192,6 +134,11 @@ impl<'a, W: Write + Seek> AttributesWriter<'a, W> { Self { writer } } + /// Returns the inner writer. + pub(crate) fn sink(&mut self) -> &mut W { + self.writer.sink() + } + /// Writes the given attribute type as type code. fn write_type_code(&mut self, ty: AttributeType) -> Result<()> { self.writer @@ -231,21 +178,11 @@ impl<'a, W: Write + Seek> AttributesWriter<'a, W> { /// Writes the given array attribute header. fn write_array_header(&mut self, header: &ArrayAttributeHeader) -> Result<()> { - self.writer - .sink() - .write_all(&header.elements_count.to_le_bytes())?; - self.writer - .sink() - .write_all(&header.encoding.to_u32().to_le_bytes())?; - self.writer - .sink() - .write_all(&header.bytelen.to_le_bytes())?; - - Ok(()) + array::write_array_header(self.writer.sink(), header).map_err(Into::into) } /// Writes some headers for an array attibute, and returns header position. - fn initialize_array( + pub(crate) fn initialize_array( &mut self, ty: AttributeType, encoding: ArrayAttributeEncoding, @@ -284,35 +221,30 @@ impl<'a, W: Write + Seek> AttributesWriter<'a, W> { append_arr_bool_from_iter: bool { from_result_iter: append_arr_bool_from_result_iter, tyval: ArrBool, - ty_real: u8, }, /// Writes an `i32` array attribute. append_arr_i32_from_iter: i32 { from_result_iter: append_arr_i32_from_result_iter, tyval: ArrI32, - ty_real: i32, }, /// Writes an `i64` array attribute. append_arr_i64_from_iter: i64 { from_result_iter: append_arr_i64_from_result_iter, tyval: ArrI64, - ty_real: i64, }, /// Writes an `f32` array attribute. append_arr_f32_from_iter: f32 { from_result_iter: append_arr_f32_from_result_iter, tyval: ArrI32, - ty_real: f32, }, /// Writes an `f64` array attribute. append_arr_f64_from_iter: f64 { from_result_iter: append_arr_f64_from_result_iter, tyval: ArrI64, - ty_real: f64, }, } diff --git a/src/writer/v7400/binary/attributes/array.rs b/src/writer/v7400/binary/attributes/array.rs index 104f9d1..d279359 100644 --- a/src/writer/v7400/binary/attributes/array.rs +++ b/src/writer/v7400/binary/attributes/array.rs @@ -1,8 +1,16 @@ //! Array attributes things. -use std::{convert::TryFrom, io::Write}; +use std::{ + convert::TryFrom, + io::{self, Seek, SeekFrom, Write}, +}; -use crate::writer::v7400::binary::{attributes::IntoBytes, Error, Result}; +use crate::{ + low::v7400::{ArrayAttributeEncoding, ArrayAttributeHeader, AttributeType}, + writer::v7400::binary::{ + attributes::IntoBytes, AttributesWriter, CompressionError, Error, Result, + }, +}; /// A trait for types which can be represented as multiple bytes array. pub(crate) trait IntoBytesMulti: Sized { @@ -27,24 +35,6 @@ impl>> IntoByt } } -/// Writes array elements into the given writer. -pub(crate) fn write_elements_direct_iter( - writer: impl Write, - iter: impl IntoIterator, -) -> Result -where - T: IntoBytes, -{ - /// A dummy type for impossible error. - enum Never {} - impl From for Error { - fn from(_: Never) -> Self { - unreachable!("Should never happen") - } - } - write_elements_result_iter(writer, iter.into_iter().map(Ok::<_, Never>)) -} - /// Writes array elements into the given writer. pub(crate) fn write_elements_result_iter( mut writer: impl Write, @@ -53,7 +43,6 @@ pub(crate) fn write_elements_result_iter( where T: IntoBytes, E: Into, - //Error: From, { let elements_count = iter .into_iter() @@ -64,3 +53,58 @@ where Ok(elements_count) } + +/// Writes the given array attribute header. +pub(crate) fn write_array_header( + mut writer: impl Write, + header: &ArrayAttributeHeader, +) -> io::Result<()> { + writer.write_all(&header.elements_count.to_le_bytes())?; + writer.write_all(&header.encoding.to_u32().to_le_bytes())?; + writer.write_all(&header.bytelen.to_le_bytes())?; + + Ok(()) +} + +/// Writes the given array attribute. +pub(crate) fn write_array_attr_result_iter>( + writer: &mut AttributesWriter, + ty: AttributeType, + encoding: Option, + iter: impl IntoIterator>, +) -> Result<()> { + let encoding = encoding.unwrap_or(ArrayAttributeEncoding::Direct); + + let header_pos = writer.initialize_array(ty, encoding)?; + + // Write elements. + let start_pos = writer.sink().seek(SeekFrom::Current(0))?; + let elements_count = match encoding { + ArrayAttributeEncoding::Direct => write_elements_result_iter(writer.sink(), iter)?, + ArrayAttributeEncoding::Zlib => { + let mut sink = libflate::zlib::Encoder::new(writer.sink())?; + let count = write_elements_result_iter(&mut sink, iter)?; + sink.finish() + .into_result() + .map_err(CompressionError::Zlib)?; + count + } + }; + let end_pos = writer.sink().seek(SeekFrom::Current(0))?; + let bytelen = end_pos - start_pos; + + // Calculate header fields. + let bytelen = u32::try_from(bytelen).map_err(|_| Error::AttributeTooLong(bytelen as usize))?; + + // Write real array header. + writer.finalize_array( + header_pos, + &ArrayAttributeHeader { + elements_count, + encoding, + bytelen, + }, + )?; + + Ok(()) +} From af749aff9673f363fdb76b47bc79e3619fe99a7e Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 03:14:06 +0900 Subject: [PATCH 20/60] Fix `unreachable_pub` lint --- src/low/v7400.rs | 2 +- src/low/v7400/attribute.rs | 6 ++---- src/tree/v7400.rs | 2 +- src/tree/v7400/node.rs | 5 ++--- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/low/v7400.rs b/src/low/v7400.rs index 5f9eeeb..48d4f8d 100644 --- a/src/low/v7400.rs +++ b/src/low/v7400.rs @@ -2,7 +2,7 @@ pub use self::{ array_attribute::ArrayAttributeEncoding, - attribute::{AttributeType, AttributeValue}, + attribute::{type_::AttributeType, value::AttributeValue}, fbx_footer::FbxFooter, }; pub(crate) use self::{ diff --git a/src/low/v7400/attribute.rs b/src/low/v7400/attribute.rs index 137c077..84adb5c 100644 --- a/src/low/v7400/attribute.rs +++ b/src/low/v7400/attribute.rs @@ -1,6 +1,4 @@ //! Node attribute. -pub use self::{type_::AttributeType, value::AttributeValue}; - -mod type_; -mod value; +pub(crate) mod type_; +pub(crate) mod value; diff --git a/src/tree/v7400.rs b/src/tree/v7400.rs index ac24174..7bae33b 100644 --- a/src/tree/v7400.rs +++ b/src/tree/v7400.rs @@ -9,7 +9,7 @@ use self::node::{NodeData, NodeNameSym}; pub use self::{ error::LoadError, loader::Loader, - node::{NodeHandle, NodeId}, + node::{handle::NodeHandle, NodeId}, }; mod error; diff --git a/src/tree/v7400/node.rs b/src/tree/v7400/node.rs index 349e394..40044f4 100644 --- a/src/tree/v7400/node.rs +++ b/src/tree/v7400/node.rs @@ -2,13 +2,12 @@ use indextree; -use crate::tree::v7400::Tree; +use crate::tree::v7400::{NodeHandle, Tree}; -pub use self::handle::NodeHandle; pub(crate) use self::{data::NodeData, name::NodeNameSym}; mod data; -mod handle; +pub(crate) mod handle; mod name; /// Node ID in FBX data tree. From bed0b69d05a39638e4a272124c5256bdec39a580 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 04:05:25 +0900 Subject: [PATCH 21/60] Implement `Default` for `writer::v7400::binary::FbxFooter` It would be convenient for users who don't care about FBX footer content. --- src/writer/v7400/binary/footer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/writer/v7400/binary/footer.rs b/src/writer/v7400/binary/footer.rs index 2d508bc..0ef0ff9 100644 --- a/src/writer/v7400/binary/footer.rs +++ b/src/writer/v7400/binary/footer.rs @@ -16,7 +16,7 @@ impl Default for FbxFooterPaddingLength { } /// FBX 7.4 footer. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct FbxFooter<'a> { /// Unknown (semirandom) 16-bytes data. /// From 21b5bc1795dc3fd2dc71c77e3164a995452f00c3 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 03:31:16 +0900 Subject: [PATCH 22/60] Update CHANGELOG --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87a4db5..2dd9888 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ * Documents are improved a little. * Manual tree construction support is added. * FBX binary writer is added. +* Now rustc-1.34 or later is required. + + To use `std::convert::{TryFrom, TryInto}`. ### Added * Manual tree construction support is added. @@ -15,6 +17,15 @@ + `writer::v7400::binary` contains FBX binary writer stuff. + This can be enabled by `writer` feature. +### Non-breaking change +* Now rustc-1.34 or later is required. + + To use `std::convert::TryFrom`. + + Strictly, this is a breaking change (for users with rustc-1.33 or below), + but not breaking for users with latest rustc. + + Currently, only `writer` module uses `TryFrom`. + Users not using `writer` feature won't be affected for now, but they could + encounter compile error in future version of fbxcel. + ## [0.4.3] * Longer lifetime for iterator returned by From cef970a2bb678eda3a8d28bc841eb928ad05b6e1 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 04:04:03 +0900 Subject: [PATCH 23/60] Describe writer usage --- src/writer/v7400/binary.rs | 99 +++++++++++++++++++++++++++ src/writer/v7400/binary/attributes.rs | 2 + 2 files changed, 101 insertions(+) diff --git a/src/writer/v7400/binary.rs b/src/writer/v7400/binary.rs index e056075..6257c1f 100644 --- a/src/writer/v7400/binary.rs +++ b/src/writer/v7400/binary.rs @@ -1,4 +1,101 @@ //! Binary writer for FBX 7.4 or later. +//! +//! # Using writer +//! +//! ## Setup and finalize +//! +//! To setup writer, use [`Writer::new`]. +//! +//! To finalize writer, use [`Writer::finalize`] or +//! [`Writer::finalize_and_flush`]. +//! Users should explicitly finalize the writer, because finalizing is not +//! implicitly done on drop. +//! +//! ``` +//! use fbxcel::{low::FbxVersion, writer::v7400::binary::{FbxFooter, Writer}}; +//! # let mut sink = std::io::Cursor::new(Vec::new()); +//! let mut writer = Writer::new(sink, FbxVersion::V7_4)?; +//! +//! // Do something here. +//! +//! // Prepare FBX footer. +//! // Use default if you don't care. +//! let footer = FbxFooter::default(); +//! writer.finalize(&footer)?; +//! // Or `writer.finalize_and_flush(&footer)?;` if you want to flush. +//! # Ok::<_, fbxcel::writer::v7400::binary::Error>(()) +//! ``` +//! +//! ## Create node and add node attributes +//! +//! To create node, use [`Writer::new_node`]. +//! It returns [`AttributesWriter`] and users can add node attributes to the +//! newly created node through it. +//! +//! Once `AttributesWriter` is dropped, you cannot add node attributes to the +//! node anymore. +//! +//! ``` +//! use fbxcel::{ +//! low::{v7400::ArrayAttributeEncoding, FbxVersion}, +//! writer::v7400::binary::{FbxFooter, Writer} +//! }; +//! # let mut sink = std::io::Cursor::new(Vec::new()); +//! let mut writer = Writer::new(sink, FbxVersion::V7_4)?; +//! +//! // Create a node with name `NodeName`. +//! let mut attrs_writer = writer.new_node("NodeName")?; +//! +//! // Add attributes to the node. +//! attrs_writer.append_bool(true)?; +//! // If you don't care about compression, pass `None`. +//! attrs_writer.append_arr_i32_from_iter(None, [1, 2, 4, 8, 16].iter().cloned())?; +//! // If you want to use specific compression, pass `Some(_)`. +//! attrs_writer.append_arr_f32_from_iter( +//! Some(ArrayAttributeEncoding::Zlib), +//! [3.14, 1.412].iter().cloned(), +//! )?; +//! attrs_writer.append_string_direct("Hello, world")?; +//! +//! # Ok::<_, fbxcel::writer::v7400::binary::Error>(()) +//! ``` +//! +//! ## Close current node +//! +//! Simply call [`Writer::close_node`]. +//! +//! It is user's responsibility to manage depth of current node and avoid +//! calling extra `close_node`. +//! +//! If `close_node` call is too few and there remains open nodes on finalizing +//! writer, `finalize()` and `finalize_and_flush()` will return error. +//! +//! ``` +//! use fbxcel::{ +//! low::{v7400::ArrayAttributeEncoding, FbxVersion}, +//! writer::v7400::binary::{FbxFooter, Writer} +//! }; +//! # let mut sink = std::io::Cursor::new(Vec::new()); +//! let mut writer = Writer::new(sink, FbxVersion::V7_4)?; +//! +//! // Create a node with name `NodeName`. +//! let mut attrs_writer = writer.new_node("NodeName")?; +//! +//! // Do something here. +//! # let _ = &attrs_writer; +//! +//! // To close current node, simply call `close_node()`. +//! writer.close_node()?; +//! +//! # Ok::<_, fbxcel::writer::v7400::binary::Error>(()) +//! ``` +//! +//! [`AttributesWriter`]: struct.AttributesWriter.html +//! [`Writer::close_node`]: struct.Writer.html#method.close_node +//! [`Writer::finalize`]: struct.Writer.html#method.finalize +//! [`Writer::finalize_and_flush`]: struct.Writer.html#method.finalize_and_flush +//! [`Writer::new`]: struct.Writer.html#method.new +//! [`Writer::new_node`]: struct.Writer.html#method.new_node use std::{ convert::TryFrom, @@ -20,6 +117,8 @@ mod error; mod footer; /// Binary writer. +/// +/// See [module documentation](index.html) for usage. #[derive(Debug, Clone)] pub struct Writer { /// Writer destination. diff --git a/src/writer/v7400/binary/attributes.rs b/src/writer/v7400/binary/attributes.rs index 1da9f3f..124fe42 100644 --- a/src/writer/v7400/binary/attributes.rs +++ b/src/writer/v7400/binary/attributes.rs @@ -65,6 +65,8 @@ impl IntoBytes for f64 { } /// Node attributes writer. +/// +/// See [module documentation](index.html) for usage. pub struct AttributesWriter<'a, W: Write> { /// Inner writer. writer: &'a mut Writer, From a5d27665f207bff9e55a877dc3759887709c8cd8 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 16:56:03 +0900 Subject: [PATCH 24/60] Implement `From<_>` for `AttributeValue` It would be useful especially when creating tree. --- src/low/v7400/attribute/value.rs | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/low/v7400/attribute/value.rs b/src/low/v7400/attribute/value.rs index 3179644..8a85386 100644 --- a/src/low/v7400/attribute/value.rs +++ b/src/low/v7400/attribute/value.rs @@ -223,3 +223,40 @@ impl AttributeValue { "Returns the reference to the inner binary data, if available.\n\nReturns `Err(type)` on type mismatch.", } } + +macro_rules! impl_from { + (direct: $ty:ty, $variant:ident) => { + impl From<$ty> for AttributeValue { + fn from(v: $ty) -> Self { + AttributeValue::$variant(v.into()) + } + } + }; + (map: $ty:ty, $variant:ident, $arg:ident, $v:expr) => { + impl From<$ty> for AttributeValue { + fn from($arg: $ty) -> Self { + AttributeValue::$variant($v) + } + } + }; +} + +impl_from! { direct: bool, Bool } +impl_from! { direct: i16, I16 } +impl_from! { direct: i32, I32 } +impl_from! { direct: i64, I64 } +impl_from! { direct: f32, F32 } +impl_from! { direct: f64, F64 } +impl_from! { direct: Vec, ArrI32 } +impl_from! { direct: Vec, ArrI64 } +impl_from! { direct: Vec, ArrF32 } +impl_from! { direct: Vec, ArrF64 } +impl_from! { direct: Vec, Binary } +impl_from! { direct: String, String } +impl_from! { map: &[bool], ArrBool, v, v.to_owned() } +impl_from! { map: &[i32], ArrI32, v, v.to_owned() } +impl_from! { map: &[i64], ArrI64, v, v.to_owned() } +impl_from! { map: &[f32], ArrF32, v, v.to_owned() } +impl_from! { map: &[f64], ArrF64, v, v.to_owned() } +impl_from! { map: &[u8], Binary, v, v.to_owned() } +impl_from! { map: &str, String, v, v.to_owned() } From f22d075d69a4ef26903c09b08608d81cfeae0f05 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 17:30:37 +0900 Subject: [PATCH 25/60] Let `append_attribute()` accept more types --- src/tree/v7400.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/tree/v7400.rs b/src/tree/v7400.rs index 7bae33b..aca4481 100644 --- a/src/tree/v7400.rs +++ b/src/tree/v7400.rs @@ -151,7 +151,16 @@ impl Tree { /// # Panics /// /// Panics if the given node ID is invalid (i.e. not used or root node). - pub fn append_attribute(&mut self, node_id: NodeId, v: AttributeValue) { + pub fn append_attribute(&mut self, node_id: NodeId, v: impl Into) { + self.append_attribute_impl(node_id, v.into()) + } + + /// Internal implementation of `append_attribute`. + /// + /// # Panics + /// + /// Panics if the given node ID is invalid (i.e. not used or root node). + fn append_attribute_impl(&mut self, node_id: NodeId, v: AttributeValue) { assert_ne!(node_id, self.root_id, "Root node should have no attributes"); let node = self.arena.get_mut(node_id.raw()).expect("Invalid node ID"); node.data.append_attribute(v) From a06e5ac540bfbc3769122a548e78c530f522299b Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 17:59:22 +0900 Subject: [PATCH 26/60] Use simple type conversion in writer tests --- tests/writer-binary-v7400.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/writer-binary-v7400.rs b/tests/writer-binary-v7400.rs index bc2e463..9ae6db9 100644 --- a/tests/writer-binary-v7400.rs +++ b/tests/writer-binary-v7400.rs @@ -5,7 +5,7 @@ mod tests { use std::{cell::RefCell, io::Cursor, iter, rc::Rc}; use fbxcel::{ - low::{v7400::AttributeValue, FbxVersion}, + low::FbxVersion, pull_parser::{ any::{from_seekable_reader, AnyParser}, v7400::{Attributes, Event, Parser}, @@ -124,13 +124,13 @@ mod tests { // Node 1: With attributes, with children. // Node 1-x: With attributes, without children. let node1_id = tree.append_new(tree.root().node_id(), "Node1"); - tree.append_attribute(node1_id, AttributeValue::Bool(true)); + tree.append_attribute(node1_id, true); let node1_1_id = tree.append_new(node1_id, "Node1-0"); - tree.append_attribute(node1_1_id, AttributeValue::I32(42)); - tree.append_attribute(node1_1_id, AttributeValue::F64(3.14)); + tree.append_attribute(node1_1_id, 42i32); + tree.append_attribute(node1_1_id, 3.14f64); let node1_2_id = tree.append_new(node1_id, "Node1-1"); - tree.append_attribute(node1_2_id, AttributeValue::Binary(vec![1, 2, 4, 8, 16])); - tree.append_attribute(node1_2_id, AttributeValue::String("Hello, world".into())); + tree.append_attribute(node1_2_id, &[1u8, 2, 4, 8, 16] as &[_]); + tree.append_attribute(node1_2_id, "Hello, world"); } let mut dest = Vec::new(); From 0cb1174a2bbaf6e0759f3908dfd02040087c7bc6 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 17:35:07 +0900 Subject: [PATCH 27/60] Update CHANGELOG --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dd9888..b52fdf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Documents are improved a little. * Manual tree construction support is added. * FBX binary writer is added. +* `low::v7400::AttributeValue` implements `From<_>` for some types. * Now rustc-1.34 or later is required. + To use `std::convert::{TryFrom, TryInto}`. @@ -16,6 +17,12 @@ * FBX binary writer is added. + `writer::v7400::binary` contains FBX binary writer stuff. + This can be enabled by `writer` feature. +* `low::v7400::AttributeValue` implements `From<_>` for some types. + + Primitive types: `bool`, `i16`, `i32`, `i64`, `f32`, `f64`. + + Vector types: `Vec`, `Vec`, `Vec`, `Vec`, `Vec`, + `Vec`. + + Slice types: `&[bool]`, `&[i32]`, `&[i64]`, `&[f32]`, `&[f64]`, `&[u8]`. + + Special types: `String`, `&str`. ### Non-breaking change * Now rustc-1.34 or later is required. From 22594d43a2940efb719b937cf294ce4915b33398 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 18:29:41 +0900 Subject: [PATCH 28/60] Implement strict comparation --- src/low/v7400/attribute/value.rs | 30 ++++++++++++++++++++ src/tree/v7400.rs | 12 ++++++++ src/tree/v7400/node/handle.rs | 48 ++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/src/low/v7400/attribute/value.rs b/src/low/v7400/attribute/value.rs index 8a85386..848f326 100644 --- a/src/low/v7400/attribute/value.rs +++ b/src/low/v7400/attribute/value.rs @@ -222,6 +222,36 @@ impl AttributeValue { get_binary_or_type, "Returns the reference to the inner binary data, if available.\n\nReturns `Err(type)` on type mismatch.", } + + /// Compares attribute values strictly. + /// + /// "Strictly" means, `f32` and `f64` values are compared bitwise. + pub fn strict_eq(&self, other: &Self) -> bool { + use AttributeValue::*; + + match (self, other) { + (Bool(l), Bool(r)) => l == r, + (I16(l), I16(r)) => l == r, + (I32(l), I32(r)) => l == r, + (I64(l), I64(r)) => l == r, + (F32(l), F32(r)) => l.to_bits() == r.to_bits(), + (F64(l), F64(r)) => l.to_bits() == r.to_bits(), + (ArrBool(l), ArrBool(r)) => l == r, + (ArrI32(l), ArrI32(r)) => l == r, + (ArrI64(l), ArrI64(r)) => l == r, + (ArrF32(l), ArrF32(r)) => l + .iter() + .map(|v| v.to_bits()) + .eq(r.iter().map(|v| v.to_bits())), + (ArrF64(l), ArrF64(r)) => l + .iter() + .map(|v| v.to_bits()) + .eq(r.iter().map(|v| v.to_bits())), + (Binary(l), Binary(r)) => l == r, + (String(l), String(r)) => l == r, + _ => false, + } + } } macro_rules! impl_from { diff --git a/src/tree/v7400.rs b/src/tree/v7400.rs index aca4481..8a3f9e3 100644 --- a/src/tree/v7400.rs +++ b/src/tree/v7400.rs @@ -165,6 +165,18 @@ impl Tree { let node = self.arena.get_mut(node_id.raw()).expect("Invalid node ID"); node.data.append_attribute(v) } + + /// Compares trees strictly. + /// + /// Returns `true` if the two trees are same. + /// + /// Note that `f32` and `f64` values are compared bitwise. + /// + /// Note that this method compares tree data, not internal states of the + /// objects. + pub fn strict_eq(&self, other: &Self) -> bool { + self.root().strict_eq(&other.root()) + } } impl Default for Tree { diff --git a/src/tree/v7400/node/handle.rs b/src/tree/v7400/node/handle.rs index 23a50fc..83a4908 100644 --- a/src/tree/v7400/node/handle.rs +++ b/src/tree/v7400/node/handle.rs @@ -84,6 +84,18 @@ impl<'a> NodeHandle<'a> { .into_iter() .flat_map(|iter| iter) } + + /// Compares nodes strictly. + /// + /// Returns `true` if the two trees are same. + /// + /// Note that `f32` and `f64` values are compared bitwise. + /// + /// Note that this method compares tree data, not internal states of the + /// trees. + pub fn strict_eq(&self, other: &Self) -> bool { + nodes_strict_eq(*self, *other) + } } macro_rules! impl_related_node_accessor { @@ -121,3 +133,39 @@ impl_related_node_accessor! { /// Returns next sibling node handle if available. next_sibling; } + +/// Compares nodes strictly. +fn nodes_strict_eq(left: NodeHandle<'_>, right: NodeHandle<'_>) -> bool { + // Compare name. + if left.name() != right.name() { + return false; + } + // Compare attributes. + { + let left = left.attributes(); + let right = right.attributes(); + if left.len() != right.len() { + return false; + } + if !left.iter().zip(right).all(|(l, r)| l.strict_eq(r)) { + return false; + } + } + // Compare children. + { + let mut left = left.children(); + let mut right = right.children(); + loop { + match (left.next(), right.next()) { + (Some(l), Some(r)) => { + if !nodes_strict_eq(l, r) { + return false; + } + } + (None, None) => break, + _ => return false, + } + } + } + true +} From 2ff9918a477f46c8a87158c12148d9579840d014 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 18:32:51 +0900 Subject: [PATCH 29/60] Update CHANGELOG --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b52fdf3..9f71521 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Manual tree construction support is added. * FBX binary writer is added. * `low::v7400::AttributeValue` implements `From<_>` for some types. +* Strict equality check is added for trees, nodes, and attribute values. * Now rustc-1.34 or later is required. + To use `std::convert::{TryFrom, TryInto}`. @@ -23,6 +24,12 @@ `Vec`. + Slice types: `&[bool]`, `&[i32]`, `&[i64]`, `&[f32]`, `&[f64]`, `&[u8]`. + Special types: `String`, `&str`. +* Strict equality check is added for trees, nodes, and attribute values. + + Trees: `tree::v7400::Tree::strict_eq()`. + + Nodes: `tree::v7400::NodeHandle::strict_eq()`. + + Attributes: `low::v7400::AttributeValue::strict_eq()`. + + These checks compares `f32` and `f64` bitwise. + This means `NAN == NAN` situation is possible. ### Non-breaking change * Now rustc-1.34 or later is required. From 2eb7fddbb8c740fd876857c587a8df633e1ed152 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 18:48:16 +0900 Subject: [PATCH 30/60] Implement `From>` for `AttributeValue` --- src/low/v7400/attribute/value.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/low/v7400/attribute/value.rs b/src/low/v7400/attribute/value.rs index 848f326..b5ee365 100644 --- a/src/low/v7400/attribute/value.rs +++ b/src/low/v7400/attribute/value.rs @@ -277,6 +277,7 @@ impl_from! { direct: i32, I32 } impl_from! { direct: i64, I64 } impl_from! { direct: f32, F32 } impl_from! { direct: f64, F64 } +impl_from! { direct: Vec, ArrBool } impl_from! { direct: Vec, ArrI32 } impl_from! { direct: Vec, ArrI64 } impl_from! { direct: Vec, ArrF32 } From a5bceee4776e4c5a58533b1339b7eeea139d602b Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 17:57:56 +0900 Subject: [PATCH 31/60] Add `tree_v7400` macro --- src/tree/v7400.rs | 2 + src/tree/v7400/macros.rs | 141 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 src/tree/v7400/macros.rs diff --git a/src/tree/v7400.rs b/src/tree/v7400.rs index 8a3f9e3..e902edc 100644 --- a/src/tree/v7400.rs +++ b/src/tree/v7400.rs @@ -12,6 +12,8 @@ pub use self::{ node::{handle::NodeHandle, NodeId}, }; +mod macros; + mod error; mod loader; mod node; diff --git a/src/tree/v7400/macros.rs b/src/tree/v7400/macros.rs new file mode 100644 index 0000000..f8bf90a --- /dev/null +++ b/src/tree/v7400/macros.rs @@ -0,0 +1,141 @@ +//! Macros. + +/// Constructs a tree. +/// +/// Enabled by `tree` feature. +/// +/// # Examples +/// +/// ``` +/// # use fbxcel::tree_v7400; +/// let empty_tree = tree_v7400! {}; +/// ``` +/// +/// ``` +/// # use fbxcel::tree_v7400; +/// let tree = tree_v7400! { +/// Node0: { +/// Node0_0: {} +/// Node0_1: {} +/// } +/// Node1: { +/// // You can use trailing comma. +/// Node1_0: {}, +/// Node1_1: {}, +/// } +/// // Use parens to specify attributes by single array. +/// // Note that the expression inside parens should implement +/// // `IntoIterator`. +/// Node2: (vec!["hello".into(), "world".into(), 42i32.into()]) {} +/// // Use brackets to specify attributes one by one. +/// Node3: ["hello", "world", 3.14f32, &b"BINARY"[..]] {} +/// }; +/// ``` +#[macro_export] +macro_rules! tree_v7400 { + (@__node, $tree:ident, $parent:ident,) => {}; + (@__node, $tree:ident, $parent:ident, , $($rest:tt)*) => { + tree_v7400! { @__node, $tree, $parent, $($rest)* } + }; + + (@__node, $tree:ident, $parent:ident, + $name:ident: { + $($subtree:tt)* + } + $($rest:tt)* + ) => {{ + { + let _node = $tree.append_new($parent, stringify!($name)); + tree_v7400! { @__node, $tree, _node, $($subtree)* } + } + tree_v7400! { @__node, $tree, $parent, $($rest)* } + }}; + (@__node, $tree:ident, $parent:ident, + $name:ident: [$($attr:expr),* $(,)?] { + $($subtree:tt)* + } + $($rest:tt)* + ) => {{ + { + let _node = $tree.append_new($parent, stringify!($name)); + $( + $tree.append_attribute(_node, $attr); + )* + tree_v7400! { @__node, $tree, _node, $($subtree)* } + } + tree_v7400! { @__node, $tree, $parent, $($rest)* } + }}; + (@__node, $tree:ident, $parent:ident, + $name:ident: ($attrs:expr) { + $($subtree:tt)* + } + $($rest:tt)* + ) => {{ + { + let _node = $tree.append_new($parent, stringify!($name)); + $attrs.into_iter().for_each(|attr: $crate::low::v7400::AttributeValue| $tree.append_attribute(_node, attr)); + tree_v7400! { @__node, $tree, _node, $($subtree)* } + } + tree_v7400! { @__node, $tree, $parent, $($rest)* } + }}; + + ($($rest:tt)*) => { + { + #[allow(unused_mut)] + let mut tree = $crate::tree::v7400::Tree::default(); + let _root = tree.root().node_id(); + tree_v7400! { @__node, tree, _root, $($rest)* } + tree + } + }; +} + +#[cfg(test)] +mod tests { + #[test] + fn empty_tree() { + let _ = tree_v7400! {}; + } + + #[test] + fn empty_node() { + let _ = tree_v7400! { + Hello: {} + World: {}, + }; + } + + #[test] + fn nested_node() { + let _ = tree_v7400! { + Hello: { + Hello1: {}, + Hello2: {} + } + World: { + World1: { + World1_1: {} + World1_2: {} + } + World2: {}, + }, + }; + } + + #[test] + fn nested_node_with_attrs() { + let _ = tree_v7400! { + Hello: { + Hello1: (vec!["string".into()]) {}, + Hello2: [3.14f32, 42i64] {} + } + World: { + World1: { + World1_1: (vec!["Hello".into(), 42i32.into()]) {} + World1_2: [] {} + } + World2: {}, + }, + }; + } +} From 3828d592533c7544ce4197ccbfdf5377950a2d26 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 18:46:42 +0900 Subject: [PATCH 32/60] Ensure same macro exprs generate same trees --- tests/tree-macro-v7400.rs | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/tree-macro-v7400.rs diff --git a/tests/tree-macro-v7400.rs b/tests/tree-macro-v7400.rs new file mode 100644 index 0000000..873921e --- /dev/null +++ b/tests/tree-macro-v7400.rs @@ -0,0 +1,40 @@ +//! Tree construction macro test. +#![cfg(all(feature = "tree", feature = "writer"))] + +use fbxcel::{tree::v7400::Tree, tree_v7400}; + +#[test] +fn compare_empties() { + let tree1 = tree_v7400! {}; + let tree2 = tree_v7400! {}; + assert!(tree1.strict_eq(&tree2)); +} + +#[test] +fn compare_trees() { + fn gentree() -> Tree { + tree_v7400! { + Node0: {}, + Node1: { + Node1_0: {}, + Node1_1: {}, + Node1_2: { + Node1_2_child: {}, + Node1_2_child: {}, + }, + }, + Node2: [true, 42i16, 42i32, 42i64, 1.414f32, 3.14f64] { + Node2_0: (vec![vec![true, false].into(), vec![0i32, 42i32].into()]) {}, + Node2_1: [ + vec![std::f32::NAN, std::f32::INFINITY], + vec![std::f64::NAN, std::f64::INFINITY] + ] {}, + }, + } + } + let tree1 = gentree(); + let tree2 = gentree(); + assert!(tree1.strict_eq(&tree2)); + let empty = tree_v7400!(); + assert!(!empty.strict_eq(&tree2)); +} From 325a9662d3410e32e2b00a3aba397f20ffbbe98c Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 18:59:16 +0900 Subject: [PATCH 33/60] Ensure `tree_v7400!` generates expected tree --- tests/tree-macro-v7400.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/tree-macro-v7400.rs b/tests/tree-macro-v7400.rs index 873921e..f170f6f 100644 --- a/tests/tree-macro-v7400.rs +++ b/tests/tree-macro-v7400.rs @@ -38,3 +38,41 @@ fn compare_trees() { let empty = tree_v7400!(); assert!(!empty.strict_eq(&tree2)); } + +#[test] +fn correct_tree() { + let tree_manual = { + let mut tree = Tree::default(); + // Node 0: Without attributes, with children. + // Node 0-x: Without attributes, without children. + let node0_id = tree.append_new(tree.root().node_id(), "Node0"); + tree.append_new(node0_id, "Node0_0"); + tree.append_new(node0_id, "Node0_1"); + + // Node 1: With attributes, with children. + // Node 1-x: With attributes, without children. + let node1_id = tree.append_new(tree.root().node_id(), "Node1"); + tree.append_attribute(node1_id, true); + let node1_0_id = tree.append_new(node1_id, "Node1_0"); + tree.append_attribute(node1_0_id, 42i32); + tree.append_attribute(node1_0_id, 3.14f64); + let node1_1_id = tree.append_new(node1_id, "Node1_1"); + tree.append_attribute(node1_1_id, &[1u8, 2, 4, 8, 16] as &[_]); + tree.append_attribute(node1_1_id, "Hello, world"); + + tree + }; + + let tree_macro = tree_v7400! { + Node0: { + Node0_0: {}, + Node0_1: {}, + }, + Node1: [true] { + Node1_0: (vec![42i32.into(), 3.14f64.into()]) {} + Node1_1: [&[1u8, 2, 4, 8, 16][..], "Hello, world"] {} + }, + }; + + assert!(tree_manual.strict_eq(&tree_macro)); +} From 40a0406c1701b6b3cd4390435c832cac8f81f464 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 19:02:42 +0900 Subject: [PATCH 34/60] Use tree macro in other tests --- tests/writer-binary-v7400.rs | 41 ++++++++++++++---------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/tests/writer-binary-v7400.rs b/tests/writer-binary-v7400.rs index 9ae6db9..4802cae 100644 --- a/tests/writer-binary-v7400.rs +++ b/tests/writer-binary-v7400.rs @@ -111,27 +111,18 @@ mod tests { #[cfg(feature = "writer")] #[test] fn tree_write_7500() -> Result<(), Box> { - use fbxcel::tree::v7400::Tree; - - let mut tree = Tree::default(); - { - // Node 0: Without attributes, with children. - // Node 0-x: Without attributes, without children. - let node0_id = tree.append_new(tree.root().node_id(), "Node0"); - tree.append_new(node0_id, "Node0-0"); - tree.append_new(node0_id, "Node0-1"); - - // Node 1: With attributes, with children. - // Node 1-x: With attributes, without children. - let node1_id = tree.append_new(tree.root().node_id(), "Node1"); - tree.append_attribute(node1_id, true); - let node1_1_id = tree.append_new(node1_id, "Node1-0"); - tree.append_attribute(node1_1_id, 42i32); - tree.append_attribute(node1_1_id, 3.14f64); - let node1_2_id = tree.append_new(node1_id, "Node1-1"); - tree.append_attribute(node1_2_id, &[1u8, 2, 4, 8, 16] as &[_]); - tree.append_attribute(node1_2_id, "Hello, world"); - } + use fbxcel::tree_v7400; + + let tree = tree_v7400! { + Node0: { + Node0_0: {}, + Node0_1: {}, + }, + Node1: [true] { + Node1_0: (vec![42i32.into(), 3.14f64.into()]) {} + Node1_1: [&[1u8, 2, 4, 8, 16][..], "Hello, world"] {} + }, + }; let mut dest = Vec::new(); let cursor = Cursor::new(&mut dest); @@ -164,12 +155,12 @@ mod tests { assert_eq!(attrs.total_count(), 0); } { - let attrs = expect_node_start(&mut parser, "Node0-0")?; + let attrs = expect_node_start(&mut parser, "Node0_0")?; assert_eq!(attrs.total_count(), 0); } expect_node_end(&mut parser)?; { - let attrs = expect_node_start(&mut parser, "Node0-1")?; + let attrs = expect_node_start(&mut parser, "Node0_1")?; assert_eq!(attrs.total_count(), 0); } expect_node_end(&mut parser)?; @@ -179,12 +170,12 @@ mod tests { assert_eq!(attrs.total_count(), 1); } { - let attrs = expect_node_start(&mut parser, "Node1-0")?; + let attrs = expect_node_start(&mut parser, "Node1_0")?; assert_eq!(attrs.total_count(), 2); } expect_node_end(&mut parser)?; { - let attrs = expect_node_start(&mut parser, "Node1-1")?; + let attrs = expect_node_start(&mut parser, "Node1_1")?; assert_eq!(attrs.total_count(), 2); } expect_node_end(&mut parser)?; From d744eea283fb59b5974fc0ca73feef0038d04f56 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 19:05:53 +0900 Subject: [PATCH 35/60] Remove redundant module nest --- tests/writer-binary-v7400.rs | 399 +++++++++++++++++------------------ 1 file changed, 197 insertions(+), 202 deletions(-) diff --git a/tests/writer-binary-v7400.rs b/tests/writer-binary-v7400.rs index 4802cae..43ea0ce 100644 --- a/tests/writer-binary-v7400.rs +++ b/tests/writer-binary-v7400.rs @@ -1,228 +1,223 @@ //! Writer test. - -#[cfg(feature = "writer")] -mod tests { - use std::{cell::RefCell, io::Cursor, iter, rc::Rc}; - - use fbxcel::{ - low::FbxVersion, - pull_parser::{ - any::{from_seekable_reader, AnyParser}, - v7400::{Attributes, Event, Parser}, - Error as ParseError, ParserSource, - }, - writer::v7400::binary::{FbxFooter, Writer}, +#![cfg(feature = "writer")] + +use std::{cell::RefCell, io::Cursor, iter, rc::Rc}; + +use fbxcel::{ + low::FbxVersion, + pull_parser::{ + any::{from_seekable_reader, AnyParser}, + v7400::{Attributes, Event, Parser}, + Error as ParseError, ParserSource, + }, + writer::v7400::binary::{FbxFooter, Writer}, +}; + +const MAGIC: &[u8] = b"Kaydara FBX Binary \x00\x1a\x00"; + +const CUSTOM_UNKNOWN1: [u8; 16] = [ + 0xff, 0xbe, 0xad, 0x0c, 0xdb, 0xca, 0xd9, 0x68, 0xb7, 0x76, 0xf5, 0x84, 0x13, 0xf2, 0x21, 0x70, +]; + +const UNKNOWN3: [u8; 16] = [ + 0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 0x29, 0x0b, +]; + +#[test] +fn empty_write_v7400() -> Result<(), Box> { + let mut dest = Vec::new(); + let cursor = Cursor::new(&mut dest); + let writer = Writer::new(cursor, FbxVersion::V7_4)?; + let footer = FbxFooter { + unknown1: Some(&CUSTOM_UNKNOWN1), + padding_len: Default::default(), + unknown2: None, + unknown3: None, }; + writer.finalize_and_flush(&footer)?; - const MAGIC: &[u8] = b"Kaydara FBX Binary \x00\x1a\x00"; - - const CUSTOM_UNKNOWN1: [u8; 16] = [ - 0xff, 0xbe, 0xad, 0x0c, 0xdb, 0xca, 0xd9, 0x68, 0xb7, 0x76, 0xf5, 0x84, 0x13, 0xf2, 0x21, - 0x70, - ]; - - const UNKNOWN3: [u8; 16] = [ - 0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 0x29, - 0x0b, - ]; - - #[test] - fn empty_write_v7400() -> Result<(), Box> { - let mut dest = Vec::new(); - let cursor = Cursor::new(&mut dest); - let writer = Writer::new(cursor, FbxVersion::V7_4)?; - let footer = FbxFooter { - unknown1: Some(&CUSTOM_UNKNOWN1), - padding_len: Default::default(), - unknown2: None, - unknown3: None, - }; - writer.finalize_and_flush(&footer)?; - - let expected = { - let raw_ver = 7400u32; - let mut vec = Vec::new(); - // Header. - { - // Magic. - vec.extend(MAGIC); - // Version. - vec.extend(&raw_ver.to_le_bytes()); - } - // No nodes. + let expected = { + let raw_ver = 7400u32; + let mut vec = Vec::new(); + // Header. + { + // Magic. + vec.extend(MAGIC); + // Version. + vec.extend(&raw_ver.to_le_bytes()); + } + // No nodes. + { + // End of implicit root. { - // End of implicit root. - { - vec.extend(iter::repeat(0).take(4 * 3 + 1)); - } + vec.extend(iter::repeat(0).take(4 * 3 + 1)); } - // Footer. + } + // Footer. + { + // Footer: unknown1. + vec.extend(&CUSTOM_UNKNOWN1); + // Footer: padding. { - // Footer: unknown1. - vec.extend(&CUSTOM_UNKNOWN1); - // Footer: padding. - { - let len = vec.len().wrapping_neg() % 16; - assert_eq!((vec.len() + len) % 16, 0); - vec.extend(iter::repeat(0).take(len)); - } - // Footer: unknown2. - vec.extend(&[0; 4]); - // Footer: FBX version. - vec.extend(&raw_ver.to_le_bytes()); - // Footer: 120 zeroes. - vec.extend(iter::repeat(0).take(120)); - // Footer: unknown3. - vec.extend(&UNKNOWN3); + let len = vec.len().wrapping_neg() % 16; + assert_eq!((vec.len() + len) % 16, 0); + vec.extend(iter::repeat(0).take(len)); } - vec - }; - - assert_eq!(dest.len() % 16, 0); - assert_eq!(dest, expected); - - let mut parser = match from_seekable_reader(Cursor::new(dest))? { - AnyParser::V7400(parser) => parser, - _ => panic!("Generated data should be parsable with v7400 parser"), - }; - let warnings = Rc::new(RefCell::new(Vec::new())); - parser.set_warning_handler({ - let warnings = warnings.clone(); - move |warning, _pos| { - warnings.borrow_mut().push(warning); - Ok(()) - } - }); - assert_eq!(parser.fbx_version(), FbxVersion::V7_4); - - { - let footer_res = expect_fbx_end(&mut parser)?; - let footer = footer_res?; - assert_eq!(footer.unknown1, CUSTOM_UNKNOWN1); - assert_eq!(footer.unknown2, [0u8; 4]); - assert_eq!(footer.unknown3, UNKNOWN3); + // Footer: unknown2. + vec.extend(&[0; 4]); + // Footer: FBX version. + vec.extend(&raw_ver.to_le_bytes()); + // Footer: 120 zeroes. + vec.extend(iter::repeat(0).take(120)); + // Footer: unknown3. + vec.extend(&UNKNOWN3); } + vec + }; - assert_eq!(warnings.borrow().len(), 0); + assert_eq!(dest.len() % 16, 0); + assert_eq!(dest, expected); + + let mut parser = match from_seekable_reader(Cursor::new(dest))? { + AnyParser::V7400(parser) => parser, + _ => panic!("Generated data should be parsable with v7400 parser"), + }; + let warnings = Rc::new(RefCell::new(Vec::new())); + parser.set_warning_handler({ + let warnings = warnings.clone(); + move |warning, _pos| { + warnings.borrow_mut().push(warning); + Ok(()) + } + }); + assert_eq!(parser.fbx_version(), FbxVersion::V7_4); - Ok(()) + { + let footer_res = expect_fbx_end(&mut parser)?; + let footer = footer_res?; + assert_eq!(footer.unknown1, CUSTOM_UNKNOWN1); + assert_eq!(footer.unknown2, [0u8; 4]); + assert_eq!(footer.unknown3, UNKNOWN3); } - #[cfg(feature = "writer")] - #[test] - fn tree_write_7500() -> Result<(), Box> { - use fbxcel::tree_v7400; - - let tree = tree_v7400! { - Node0: { - Node0_0: {}, - Node0_1: {}, - }, - Node1: [true] { - Node1_0: (vec![42i32.into(), 3.14f64.into()]) {} - Node1_1: [&[1u8, 2, 4, 8, 16][..], "Hello, world"] {} - }, - }; - - let mut dest = Vec::new(); - let cursor = Cursor::new(&mut dest); - let mut writer = Writer::new(cursor, FbxVersion::V7_5)?; - let footer = FbxFooter { - unknown1: Some(&CUSTOM_UNKNOWN1), - padding_len: Default::default(), - unknown2: None, - unknown3: None, - }; - writer.write_tree(&tree)?; - writer.finalize_and_flush(&footer)?; - - let mut parser = match from_seekable_reader(Cursor::new(dest))? { - AnyParser::V7400(parser) => parser, - _ => panic!("Generated data should be parsable with v7400 parser"), - }; - let warnings = Rc::new(RefCell::new(Vec::new())); - parser.set_warning_handler({ - let warnings = warnings.clone(); - move |warning, _pos| { - warnings.borrow_mut().push(warning); - Ok(()) - } - }); - assert_eq!(parser.fbx_version(), FbxVersion::V7_5); + assert_eq!(warnings.borrow().len(), 0); - { - let attrs = expect_node_start(&mut parser, "Node0")?; - assert_eq!(attrs.total_count(), 0); - } - { - let attrs = expect_node_start(&mut parser, "Node0_0")?; - assert_eq!(attrs.total_count(), 0); - } - expect_node_end(&mut parser)?; - { - let attrs = expect_node_start(&mut parser, "Node0_1")?; - assert_eq!(attrs.total_count(), 0); - } - expect_node_end(&mut parser)?; - expect_node_end(&mut parser)?; - { - let attrs = expect_node_start(&mut parser, "Node1")?; - assert_eq!(attrs.total_count(), 1); - } - { - let attrs = expect_node_start(&mut parser, "Node1_0")?; - assert_eq!(attrs.total_count(), 2); - } - expect_node_end(&mut parser)?; - { - let attrs = expect_node_start(&mut parser, "Node1_1")?; - assert_eq!(attrs.total_count(), 2); - } - expect_node_end(&mut parser)?; - expect_node_end(&mut parser)?; + Ok(()) +} - { - let footer_res = expect_fbx_end(&mut parser)?; - let footer = footer_res?; - assert_eq!(footer.unknown1, CUSTOM_UNKNOWN1); - assert_eq!(footer.unknown2, [0u8; 4]); - assert_eq!(footer.unknown3, UNKNOWN3); +#[cfg(feature = "tree")] +#[test] +fn tree_write_7500() -> Result<(), Box> { + use fbxcel::tree_v7400; + + let tree = tree_v7400! { + Node0: { + Node0_0: {}, + Node0_1: {}, + }, + Node1: [true] { + Node1_0: (vec![42i32.into(), 3.14f64.into()]) {} + Node1_1: [&[1u8, 2, 4, 8, 16][..], "Hello, world"] {} + }, + }; + + let mut dest = Vec::new(); + let cursor = Cursor::new(&mut dest); + let mut writer = Writer::new(cursor, FbxVersion::V7_5)?; + let footer = FbxFooter { + unknown1: Some(&CUSTOM_UNKNOWN1), + padding_len: Default::default(), + unknown2: None, + unknown3: None, + }; + writer.write_tree(&tree)?; + writer.finalize_and_flush(&footer)?; + + let mut parser = match from_seekable_reader(Cursor::new(dest))? { + AnyParser::V7400(parser) => parser, + _ => panic!("Generated data should be parsable with v7400 parser"), + }; + let warnings = Rc::new(RefCell::new(Vec::new())); + parser.set_warning_handler({ + let warnings = warnings.clone(); + move |warning, _pos| { + warnings.borrow_mut().push(warning); + Ok(()) } + }); + assert_eq!(parser.fbx_version(), FbxVersion::V7_5); - assert_eq!(warnings.borrow().len(), 0); + { + let attrs = expect_node_start(&mut parser, "Node0")?; + assert_eq!(attrs.total_count(), 0); + } + { + let attrs = expect_node_start(&mut parser, "Node0_0")?; + assert_eq!(attrs.total_count(), 0); + } + expect_node_end(&mut parser)?; + { + let attrs = expect_node_start(&mut parser, "Node0_1")?; + assert_eq!(attrs.total_count(), 0); + } + expect_node_end(&mut parser)?; + expect_node_end(&mut parser)?; + { + let attrs = expect_node_start(&mut parser, "Node1")?; + assert_eq!(attrs.total_count(), 1); + } + { + let attrs = expect_node_start(&mut parser, "Node1_0")?; + assert_eq!(attrs.total_count(), 2); + } + expect_node_end(&mut parser)?; + { + let attrs = expect_node_start(&mut parser, "Node1_1")?; + assert_eq!(attrs.total_count(), 2); + } + expect_node_end(&mut parser)?; + expect_node_end(&mut parser)?; - Ok(()) + { + let footer_res = expect_fbx_end(&mut parser)?; + let footer = footer_res?; + assert_eq!(footer.unknown1, CUSTOM_UNKNOWN1); + assert_eq!(footer.unknown2, [0u8; 4]); + assert_eq!(footer.unknown3, UNKNOWN3); } - fn expect_node_start<'a, R: ParserSource + std::fmt::Debug>( - parser: &'a mut Parser, - name: &str, - ) -> Result, Box> { - match parser.next_event()? { - Event::StartNode(node) => { - assert_eq!(node.name(), name); - Ok(node.attributes()) - } - ev => panic!("Unexpected event: {:?}", ev), + assert_eq!(warnings.borrow().len(), 0); + + Ok(()) +} + +fn expect_node_start<'a, R: ParserSource + std::fmt::Debug>( + parser: &'a mut Parser, + name: &str, +) -> Result, Box> { + match parser.next_event()? { + Event::StartNode(node) => { + assert_eq!(node.name(), name); + Ok(node.attributes()) } + ev => panic!("Unexpected event: {:?}", ev), } +} - fn expect_node_end( - parser: &mut Parser, - ) -> Result<(), Box> { - match parser.next_event()? { - Event::EndNode => Ok(()), - ev => panic!("Unexpected event: {:?}", ev), - } +fn expect_node_end( + parser: &mut Parser, +) -> Result<(), Box> { + match parser.next_event()? { + Event::EndNode => Ok(()), + ev => panic!("Unexpected event: {:?}", ev), } +} - fn expect_fbx_end( - parser: &mut Parser, - ) -> Result, ParseError>, Box> - { - match parser.next_event()? { - Event::EndFbx(footer_res) => Ok(footer_res), - ev => panic!("Unexpected event: {:?}", ev), - } +fn expect_fbx_end( + parser: &mut Parser, +) -> Result, ParseError>, Box> { + match parser.next_event()? { + Event::EndFbx(footer_res) => Ok(footer_res), + ev => panic!("Unexpected event: {:?}", ev), } } From 7e7765097a1679c37160aee950b9fbf787a5d192 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Mon, 22 Apr 2019 19:09:11 +0900 Subject: [PATCH 36/60] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f71521..4e598ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * FBX binary writer is added. * `low::v7400::AttributeValue` implements `From<_>` for some types. * Strict equality check is added for trees, nodes, and attribute values. +* `tree_v7400!` macro is added to construct tree easily. * Now rustc-1.34 or later is required. + To use `std::convert::{TryFrom, TryInto}`. @@ -30,6 +31,9 @@ + Attributes: `low::v7400::AttributeValue::strict_eq()`. + These checks compares `f32` and `f64` bitwise. This means `NAN == NAN` situation is possible. +* `tree_v7400!` macro is added to construct tree easily. + + Enabled by `tree` feature. + + See documentation. ### Non-breaking change * Now rustc-1.34 or later is required. From 19863d32813caaacd8604133a6179897adec6d8c Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Wed, 24 Apr 2019 00:35:53 +0900 Subject: [PATCH 37/60] Remove unnecessary `use` from doctests --- src/writer/v7400/binary.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/writer/v7400/binary.rs b/src/writer/v7400/binary.rs index 6257c1f..43115dc 100644 --- a/src/writer/v7400/binary.rs +++ b/src/writer/v7400/binary.rs @@ -38,7 +38,7 @@ //! ``` //! use fbxcel::{ //! low::{v7400::ArrayAttributeEncoding, FbxVersion}, -//! writer::v7400::binary::{FbxFooter, Writer} +//! writer::v7400::binary::Writer, //! }; //! # let mut sink = std::io::Cursor::new(Vec::new()); //! let mut writer = Writer::new(sink, FbxVersion::V7_4)?; @@ -73,7 +73,7 @@ //! ``` //! use fbxcel::{ //! low::{v7400::ArrayAttributeEncoding, FbxVersion}, -//! writer::v7400::binary::{FbxFooter, Writer} +//! writer::v7400::binary::Writer, //! }; //! # let mut sink = std::io::Cursor::new(Vec::new()); //! let mut writer = Writer::new(sink, FbxVersion::V7_4)?; From 1ea349af368f0ddb472e61c2eb79c4005b5f528e Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Wed, 24 Apr 2019 12:05:13 +0900 Subject: [PATCH 38/60] Return inner sink on writer finalization --- src/writer/v7400/binary.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/writer/v7400/binary.rs b/src/writer/v7400/binary.rs index 43115dc..14f0335 100644 --- a/src/writer/v7400/binary.rs +++ b/src/writer/v7400/binary.rs @@ -366,21 +366,23 @@ impl Writer { Ok(()) } - /// Finalizes the FBX binary. + /// Finalizes the FBX binary and returns the inner sink. /// /// You may want to use [`finalize_and_flush()`]. /// /// [`finalize_and_flush()`]: #method.finalize_and_flush - pub fn finalize(mut self, footer: &FbxFooter) -> Result<()> { - self.finalize_impl(footer) + pub fn finalize(mut self, footer: &FbxFooter) -> Result { + self.finalize_impl(footer)?; + + Ok(self.sink) } - /// Finalizes the FBX binary and flushes. - pub fn finalize_and_flush(mut self, footer: &FbxFooter) -> Result<()> { + /// Finalizes the FBX binary, and returns the inner sink after flushing. + pub fn finalize_and_flush(mut self, footer: &FbxFooter) -> Result { self.finalize_impl(footer)?; self.sink.flush()?; - Ok(()) + Ok(self.sink) } /// Internal implementation of `finalize()` and `finalize_and_flush()`. From 2a21206be17b3dd8bff09ab146497badedd454c5 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Thu, 25 Apr 2019 16:21:30 +0900 Subject: [PATCH 39/60] Add `write_v7400_binary!` macro --- src/writer/v7400/binary.rs | 2 + src/writer/v7400/binary/macros.rs | 199 ++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 src/writer/v7400/binary/macros.rs diff --git a/src/writer/v7400/binary.rs b/src/writer/v7400/binary.rs index 14f0335..cc2cb2e 100644 --- a/src/writer/v7400/binary.rs +++ b/src/writer/v7400/binary.rs @@ -112,6 +112,8 @@ pub use self::{ footer::{FbxFooter, FbxFooterPaddingLength}, }; +mod macros; + mod attributes; mod error; mod footer; diff --git a/src/writer/v7400/binary/macros.rs b/src/writer/v7400/binary/macros.rs new file mode 100644 index 0000000..cd90f18 --- /dev/null +++ b/src/writer/v7400/binary/macros.rs @@ -0,0 +1,199 @@ +//! Macros. + +/// Drives the given writer to write the given tree as FBX binary, and returns +/// the `fbxcel::writer::v7400::binary::Result<()>`. +/// +/// Enabled by `writer` feature. +/// +/// # Examples +/// +/// ``` +/// # use fbxcel::write_v7400_binary; +/// use fbxcel::{low::FbxVersion, writer::v7400::binary::Writer}; +/// let mut writer = Writer::new(std::io::Cursor::new(Vec::new()), FbxVersion::V7_4)?; +/// +/// write_v7400_binary!( +/// writer=writer, +/// tree={ +/// Node0: { +/// Node0_0: {} +/// Node0_1: {} +/// } +/// Node1: { +/// // You can use trailing comma. +/// Node1_0: {}, +/// Node1_1: {}, +/// } +/// // Use parens to specify attributes by single array. +/// // Note that the expression inside parens should implement +/// // `IntoIterator`. +/// Node2: (vec!["hello".into(), "world".into(), 42i32.into()]) {} +/// // Use brackets to specify attributes one by one. +/// Node3: ["hello", "world", 3.14f32, &b"BINARY"[..]] {} +/// }, +/// )?; +/// let _buf = writer.finalize_and_flush(&Default::default())?; +/// # Ok::<_, Box>(()) +/// ``` +#[macro_export] +macro_rules! write_v7400_binary { + ( + writer=$writer:expr, + tree={$($tree:tt)*}, + ) => {{ + let mut f = || -> $crate::writer::v7400::binary::Result<()> { + let _writer = &mut $writer; + write_v7400_binary! { @__node, _writer, $($tree)* }; + Ok(()) + }; + f() + }}; + + + (@__node, $writer:ident,) => {}; + (@__node, $writer:ident, , $($tree:tt)*) => { + write_v7400_binary! { @__node, $writer, $($tree)* } + }; + + (@__node, $writer:ident, + $name:ident: { + $($subtree:tt)* + } + $($rest:tt)* + ) => {{ + $writer.new_node(stringify!($name))?; + write_v7400_binary! { @__node, $writer, $($subtree)* } + $writer.close_node()?; + write_v7400_binary! { @__node, $writer, $($rest)* } + }}; + (@__node, $writer:ident, + $name:ident: [$($attr:expr),* $(,)?] { + $($subtree:tt)* + } + $($rest:tt)* + ) => {{ + let mut _attrs = $writer.new_node(stringify!($name))?; + $({ + let attr = $attr; + write_v7400_binary!(@__attr, _attrs, attr.into())?; + })* + write_v7400_binary! { @__node, $writer, $($subtree)* } + $writer.close_node()?; + write_v7400_binary! { @__node, $writer, $($rest)* } + }}; + (@__node, $writer:ident, + $name:ident: ($attrs:expr) { + $($subtree:tt)* + } + $($rest:tt)* + ) => {{ + let mut _attrs = $writer.new_node(stringify!($name))?; + $attrs.into_iter().try_for_each(|attr: $crate::low::v7400::AttributeValue| { + write_v7400_binary!(@__attr, _attrs, attr.into()) + })?; + write_v7400_binary! { @__node, $writer, $($subtree)* } + $writer.close_node()?; + write_v7400_binary! { @__node, $writer, $($rest)* } + }}; + + (@__attr, $attrs:ident, $attr:expr) => {{ + use $crate::low::v7400::AttributeValue::*; + match $attr { + Bool(v) => $attrs.append_bool(v), + I16(v) => $attrs.append_i16(v), + I32(v) => $attrs.append_i32(v), + I64(v) => $attrs.append_i64(v), + F32(v) => $attrs.append_f32(v), + F64(v) => $attrs.append_f64(v), + ArrBool(v) => $attrs.append_arr_bool_from_iter(None, v), + ArrI32(v) => $attrs.append_arr_i32_from_iter(None, v), + ArrI64(v) => $attrs.append_arr_i64_from_iter(None, v), + ArrF32(v) => $attrs.append_arr_f32_from_iter(None, v), + ArrF64(v) => $attrs.append_arr_f64_from_iter(None, v), + Binary(v) => $attrs.append_binary_direct(&v), + String(v) => $attrs.append_string_direct(&v), + } + }}; +} + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use crate::{ + low::FbxVersion, + writer::v7400::binary::{Result, Writer}, + }; + + #[test] + fn empty_writer() -> Result<()> { + let mut writer = Writer::new(Cursor::new(Vec::new()), FbxVersion::V7_4)?; + write_v7400_binary!(writer = writer, tree = {},)?; + let _buf = writer.finalize_and_flush(&Default::default())?; + + Ok(()) + } + + #[test] + fn empty_node() -> Result<()> { + let mut writer = Writer::new(Cursor::new(Vec::new()), FbxVersion::V7_4)?; + write_v7400_binary!( + writer=writer, + tree={ + Hello: {} + World: {}, + }, + )?; + let _buf = writer.finalize_and_flush(&Default::default())?; + + Ok(()) + } + + #[test] + fn nested_node() -> Result<()> { + let mut writer = Writer::new(Cursor::new(Vec::new()), FbxVersion::V7_4)?; + write_v7400_binary!( + writer=writer, + tree={ + Hello: { + Hello1: {}, + Hello2: {} + } + World: { + World1: { + World1_1: {} + World1_2: {} + } + World2: {}, + }, + }, + )?; + let _buf = writer.finalize_and_flush(&Default::default())?; + + Ok(()) + } + + #[test] + fn nested_node_with_attrs() -> Result<()> { + let mut writer = Writer::new(Cursor::new(Vec::new()), FbxVersion::V7_4)?; + write_v7400_binary!( + writer=writer, + tree={ + Hello: { + Hello1: (vec!["string".into()]) {}, + Hello2: [3.14f32, 42i64] {} + } + World: { + World1: { + World1_1: (vec!["Hello".into(), 42i32.into()]) {} + World1_2: [] {} + } + World2: {}, + }, + }, + )?; + let _buf = writer.finalize_and_flush(&Default::default())?; + + Ok(()) + } +} From 22e9aa8be6ca15e619df5370de7f6f1e2f7578ec Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Thu, 25 Apr 2019 17:18:22 +0900 Subject: [PATCH 40/60] Add a simple test for `write_v7400_binary!` --- tests/writer-binary-v7400.rs | 77 ++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/tests/writer-binary-v7400.rs b/tests/writer-binary-v7400.rs index 43ea0ce..992777c 100644 --- a/tests/writer-binary-v7400.rs +++ b/tests/writer-binary-v7400.rs @@ -191,6 +191,83 @@ fn tree_write_7500() -> Result<(), Box> { Ok(()) } +#[test] +fn macro_v7400_idempotence() -> Result<(), Box> { + use fbxcel::write_v7400_binary; + + let version = FbxVersion::V7_4; + let mut writer = Writer::new(std::io::Cursor::new(Vec::new()), version)?; + + write_v7400_binary!( + writer=writer, + tree={ + Node0: { + Node0_0: {}, + Node0_1: {}, + }, + Node1: [true] { + Node1_0: (vec![42i32.into(), 3.14f64.into()]) {} + Node1_1: [&[1u8, 2, 4, 8, 16][..], "Hello, world"] {} + }, + }, + )?; + let bin = writer.finalize_and_flush(&Default::default())?.into_inner(); + + let mut parser = match from_seekable_reader(Cursor::new(bin))? { + AnyParser::V7400(parser) => parser, + _ => panic!("Generated data should be parsable with v7400 parser"), + }; + let warnings = Rc::new(RefCell::new(Vec::new())); + parser.set_warning_handler({ + let warnings = warnings.clone(); + move |warning, _pos| { + warnings.borrow_mut().push(warning); + Ok(()) + } + }); + assert_eq!(parser.fbx_version(), version); + + { + let attrs = expect_node_start(&mut parser, "Node0")?; + assert_eq!(attrs.total_count(), 0); + } + { + let attrs = expect_node_start(&mut parser, "Node0_0")?; + assert_eq!(attrs.total_count(), 0); + } + expect_node_end(&mut parser)?; + { + let attrs = expect_node_start(&mut parser, "Node0_1")?; + assert_eq!(attrs.total_count(), 0); + } + expect_node_end(&mut parser)?; + expect_node_end(&mut parser)?; + { + let attrs = expect_node_start(&mut parser, "Node1")?; + assert_eq!(attrs.total_count(), 1); + } + { + let attrs = expect_node_start(&mut parser, "Node1_0")?; + assert_eq!(attrs.total_count(), 2); + } + expect_node_end(&mut parser)?; + { + let attrs = expect_node_start(&mut parser, "Node1_1")?; + assert_eq!(attrs.total_count(), 2); + } + expect_node_end(&mut parser)?; + expect_node_end(&mut parser)?; + + { + let footer_res = expect_fbx_end(&mut parser)?; + assert!(footer_res.is_ok()); + } + + assert_eq!(warnings.borrow().len(), 0); + + Ok(()) +} + fn expect_node_start<'a, R: ParserSource + std::fmt::Debug>( parser: &'a mut Parser, name: &str, From 3c198443e71486815ec9208d6c1b26b192410fde Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Thu, 25 Apr 2019 17:23:49 +0900 Subject: [PATCH 41/60] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e598ed..b089c75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * `low::v7400::AttributeValue` implements `From<_>` for some types. * Strict equality check is added for trees, nodes, and attribute values. * `tree_v7400!` macro is added to construct tree easily. +* `write_v7400_binary!` macro is added to write tree easily. * Now rustc-1.34 or later is required. + To use `std::convert::{TryFrom, TryInto}`. @@ -34,6 +35,9 @@ * `tree_v7400!` macro is added to construct tree easily. + Enabled by `tree` feature. + See documentation. +* `write_v7400_binary!` macro is added to write tree easily. + + Enabled by `writer` feature. + + See documentation. ### Non-breaking change * Now rustc-1.34 or later is required. From 78f439914e748f4b397705163e49a30c1bcf15f7 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Thu, 25 Apr 2019 18:16:05 +0900 Subject: [PATCH 42/60] Implement `Tree::debug_tree` to pretty-print tree Default `Debug` implementation for `Tree` is hard to read because it dumps arena and interned strings. `Tree::debug_tree` returns human-readable dump of the tree structure. --- src/tree/v7400.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/tree/v7400.rs b/src/tree/v7400.rs index e902edc..39199b2 100644 --- a/src/tree/v7400.rs +++ b/src/tree/v7400.rs @@ -1,5 +1,7 @@ //! FBX data tree for v7.4 or later. +use std::fmt; + use indextree::Arena; use string_interner::StringInterner; @@ -179,6 +181,13 @@ impl Tree { pub fn strict_eq(&self, other: &Self) -> bool { self.root().strict_eq(&other.root()) } + + /// Pretty-print the tree for debugging purpose. + /// + /// Be careful, this output format may change in future. + pub fn debug_tree<'a>(&'a self) -> impl fmt::Debug + 'a { + DebugTree { tree: self } + } } impl Default for Tree { @@ -195,3 +204,52 @@ impl Default for Tree { } } } + +/// A simple wrapper for pretty-printing tree. +struct DebugTree<'a> { + /// Tree. + tree: &'a Tree, +} + +impl fmt::Debug for DebugTree<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let v = DebugNodeHandle { + node: self.tree.root(), + }; + v.fmt(f) + } +} + +/// A simple wrapper for pretty-printing node. +struct DebugNodeHandle<'a> { + /// Node. + node: NodeHandle<'a>, +} + +impl fmt::Debug for DebugNodeHandle<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Node") + .field("name", &self.node.name()) + .field("attributes", &self.node.attributes()) + .field("children", &DebugNodeHandleChildren { node: self.node }) + .finish() + } +} + +/// A simple wrapper for pretty-printing children. +struct DebugNodeHandleChildren<'a> { + /// Parent node. + node: NodeHandle<'a>, +} + +impl fmt::Debug for DebugNodeHandleChildren<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_list() + .entries( + self.node + .children() + .map(|child| DebugNodeHandle { node: child }), + ) + .finish() + } +} From 7c9ed884258ac04df5ec9789a236b8b2596b6dea Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Thu, 25 Apr 2019 18:40:29 +0900 Subject: [PATCH 43/60] Update CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b089c75..e87f4a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Strict equality check is added for trees, nodes, and attribute values. * `tree_v7400!` macro is added to construct tree easily. * `write_v7400_binary!` macro is added to write tree easily. +* `tree::v7400::Tree::debug_tree()` is added. * Now rustc-1.34 or later is required. + To use `std::convert::{TryFrom, TryInto}`. @@ -38,6 +39,11 @@ * `write_v7400_binary!` macro is added to write tree easily. + Enabled by `writer` feature. + See documentation. +* `tree::v7400::Tree::debug_tree()` is added. + * This returns pretty-printable object of the tree. + * It dumps human-readable tree structure. + * Default `Debug` implementation for `Tree` is hard to read because it dumps + arena and interned string table. ### Non-breaking change * Now rustc-1.34 or later is required. From 63d9eee83d40f6d5b61328492e4080e4250619ef Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Thu, 25 Apr 2019 19:52:56 +0900 Subject: [PATCH 44/60] Use helpers for writer test and assert attributes --- tests/v7400/mod.rs | 1 + tests/v7400/writer.rs | 47 ++++++++++++++ tests/writer-binary-v7400.rs | 116 ++++++++++++++--------------------- 3 files changed, 94 insertions(+), 70 deletions(-) create mode 100644 tests/v7400/mod.rs create mode 100644 tests/v7400/writer.rs diff --git a/tests/v7400/mod.rs b/tests/v7400/mod.rs new file mode 100644 index 0000000..d3baa81 --- /dev/null +++ b/tests/v7400/mod.rs @@ -0,0 +1 @@ +pub mod writer; diff --git a/tests/v7400/writer.rs b/tests/v7400/writer.rs new file mode 100644 index 0000000..76d1524 --- /dev/null +++ b/tests/v7400/writer.rs @@ -0,0 +1,47 @@ +#![cfg(feature = "writer")] + +use fbxcel::pull_parser::{ + v7400::{Attributes, Event, Parser}, + Error as ParseError, ParserSource, +}; + +pub const MAGIC: &[u8] = b"Kaydara FBX Binary \x00\x1a\x00"; + +pub const CUSTOM_UNKNOWN1: [u8; 16] = [ + 0xff, 0xbe, 0xad, 0x0c, 0xdb, 0xca, 0xd9, 0x68, 0xb7, 0x76, 0xf5, 0x84, 0x13, 0xf2, 0x21, 0x70, +]; + +pub const UNKNOWN3: [u8; 16] = [ + 0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 0x29, 0x0b, +]; + +pub fn expect_node_start<'a, R: ParserSource + std::fmt::Debug>( + parser: &'a mut Parser, + name: &str, +) -> Result, Box> { + match parser.next_event()? { + Event::StartNode(node) => { + assert_eq!(node.name(), name); + Ok(node.attributes()) + } + ev => panic!("Unexpected event: {:?}", ev), + } +} + +pub fn expect_node_end( + parser: &mut Parser, +) -> Result<(), Box> { + match parser.next_event()? { + Event::EndNode => Ok(()), + ev => panic!("Unexpected event: {:?}", ev), + } +} + +pub fn expect_fbx_end( + parser: &mut Parser, +) -> Result, ParseError>, Box> { + match parser.next_event()? { + Event::EndFbx(footer_res) => Ok(footer_res), + ev => panic!("Unexpected event: {:?}", ev), + } +} diff --git a/tests/writer-binary-v7400.rs b/tests/writer-binary-v7400.rs index 992777c..88e5a38 100644 --- a/tests/writer-binary-v7400.rs +++ b/tests/writer-binary-v7400.rs @@ -4,25 +4,21 @@ use std::{cell::RefCell, io::Cursor, iter, rc::Rc}; use fbxcel::{ - low::FbxVersion, + low::{v7400::AttributeValue, FbxVersion}, pull_parser::{ any::{from_seekable_reader, AnyParser}, - v7400::{Attributes, Event, Parser}, - Error as ParseError, ParserSource, + v7400::attribute::loaders::DirectLoader, }, writer::v7400::binary::{FbxFooter, Writer}, }; -const MAGIC: &[u8] = b"Kaydara FBX Binary \x00\x1a\x00"; - -const CUSTOM_UNKNOWN1: [u8; 16] = [ - 0xff, 0xbe, 0xad, 0x0c, 0xdb, 0xca, 0xd9, 0x68, 0xb7, 0x76, 0xf5, 0x84, 0x13, 0xf2, 0x21, 0x70, -]; +use self::v7400::writer::{ + expect_fbx_end, expect_node_end, expect_node_start, CUSTOM_UNKNOWN1, MAGIC, UNKNOWN3, +}; -const UNKNOWN3: [u8; 16] = [ - 0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0xe3, 0x75, 0x8f, 0x29, 0x0b, -]; +mod v7400; +/// Compares expected binary and binary generated with events. #[test] fn empty_write_v7400() -> Result<(), Box> { let mut dest = Vec::new(); @@ -105,33 +101,28 @@ fn empty_write_v7400() -> Result<(), Box> { Ok(()) } -#[cfg(feature = "tree")] +/// Compares expected binary and binary generated with events. #[test] fn tree_write_7500() -> Result<(), Box> { - use fbxcel::tree_v7400; - - let tree = tree_v7400! { - Node0: { - Node0_0: {}, - Node0_1: {}, - }, - Node1: [true] { - Node1_0: (vec![42i32.into(), 3.14f64.into()]) {} - Node1_1: [&[1u8, 2, 4, 8, 16][..], "Hello, world"] {} - }, - }; + use fbxcel::write_v7400_binary; let mut dest = Vec::new(); let cursor = Cursor::new(&mut dest); let mut writer = Writer::new(cursor, FbxVersion::V7_5)?; - let footer = FbxFooter { - unknown1: Some(&CUSTOM_UNKNOWN1), - padding_len: Default::default(), - unknown2: None, - unknown3: None, - }; - writer.write_tree(&tree)?; - writer.finalize_and_flush(&footer)?; + write_v7400_binary!( + writer=writer, + tree={ + Node0: { + Node0_0: {}, + Node0_1: {}, + }, + Node1: [true] { + Node1_0: (vec![42i32.into(), 3.14f64.into()]) {} + Node1_1: [&[1u8, 2, 4, 8, 16][..], "Hello, world"] {} + }, + }, + )?; + writer.finalize_and_flush(&Default::default())?; let mut parser = match from_seekable_reader(Cursor::new(dest))? { AnyParser::V7400(parser) => parser, @@ -180,10 +171,7 @@ fn tree_write_7500() -> Result<(), Box> { { let footer_res = expect_fbx_end(&mut parser)?; - let footer = footer_res?; - assert_eq!(footer.unknown1, CUSTOM_UNKNOWN1); - assert_eq!(footer.unknown2, [0u8; 4]); - assert_eq!(footer.unknown3, UNKNOWN3); + assert!(footer_res.is_ok()); } assert_eq!(warnings.borrow().len(), 0); @@ -243,16 +231,35 @@ fn macro_v7400_idempotence() -> Result<(), Box> { expect_node_end(&mut parser)?; expect_node_end(&mut parser)?; { - let attrs = expect_node_start(&mut parser, "Node1")?; + let mut attrs = expect_node_start(&mut parser, "Node1")?; + assert_eq!( + attrs.load_next(DirectLoader)?, + Some(AttributeValue::from(true)) + ); assert_eq!(attrs.total_count(), 1); } { - let attrs = expect_node_start(&mut parser, "Node1_0")?; + let mut attrs = expect_node_start(&mut parser, "Node1_0")?; + assert_eq!( + attrs.load_next(DirectLoader)?, + Some(AttributeValue::from(42i32)) + ); + assert!(attrs + .load_next(DirectLoader)? + .map_or(false, |attr| attr.strict_eq(&3.14f64.into()))); assert_eq!(attrs.total_count(), 2); } expect_node_end(&mut parser)?; { - let attrs = expect_node_start(&mut parser, "Node1_1")?; + let mut attrs = expect_node_start(&mut parser, "Node1_1")?; + assert_eq!( + attrs.load_next(DirectLoader)?, + Some(AttributeValue::from(vec![1u8, 2, 4, 8, 16])) + ); + assert_eq!( + attrs.load_next(DirectLoader)?, + Some(AttributeValue::from("Hello, world")) + ); assert_eq!(attrs.total_count(), 2); } expect_node_end(&mut parser)?; @@ -267,34 +274,3 @@ fn macro_v7400_idempotence() -> Result<(), Box> { Ok(()) } - -fn expect_node_start<'a, R: ParserSource + std::fmt::Debug>( - parser: &'a mut Parser, - name: &str, -) -> Result, Box> { - match parser.next_event()? { - Event::StartNode(node) => { - assert_eq!(node.name(), name); - Ok(node.attributes()) - } - ev => panic!("Unexpected event: {:?}", ev), - } -} - -fn expect_node_end( - parser: &mut Parser, -) -> Result<(), Box> { - match parser.next_event()? { - Event::EndNode => Ok(()), - ev => panic!("Unexpected event: {:?}", ev), - } -} - -fn expect_fbx_end( - parser: &mut Parser, -) -> Result, ParseError>, Box> { - match parser.next_event()? { - Event::EndFbx(footer_res) => Ok(footer_res), - ev => panic!("Unexpected event: {:?}", ev), - } -} From 1f5c311a7055afbb8598080712051195000f1a00 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Fri, 26 Apr 2019 01:30:54 +0900 Subject: [PATCH 45/60] Move unit tests for tree into `src/` --- src/tree/v7400/macros.rs | 77 ++++++++++++++++++++++++++++++++++++-- tests/tree-macro-v7400.rs | 78 --------------------------------------- 2 files changed, 74 insertions(+), 81 deletions(-) delete mode 100644 tests/tree-macro-v7400.rs diff --git a/src/tree/v7400/macros.rs b/src/tree/v7400/macros.rs index f8bf90a..ae93ecc 100644 --- a/src/tree/v7400/macros.rs +++ b/src/tree/v7400/macros.rs @@ -92,13 +92,17 @@ macro_rules! tree_v7400 { #[cfg(test)] mod tests { + use crate::tree::v7400::Tree; + #[test] - fn empty_tree() { - let _ = tree_v7400! {}; + fn empty_trees_eq() { + let tree1 = tree_v7400! {}; + let tree2 = tree_v7400! {}; + assert!(tree1.strict_eq(&tree2)); } #[test] - fn empty_node() { + fn empty_nodes() { let _ = tree_v7400! { Hello: {} World: {}, @@ -138,4 +142,71 @@ mod tests { }, }; } + + #[test] + fn compare_complex_trees() { + fn gentree() -> Tree { + tree_v7400! { + Node0: {}, + Node1: { + Node1_0: {}, + Node1_1: {}, + Node1_2: { + Node1_2_child: {}, + Node1_2_child: {}, + }, + }, + Node2: [true, 42i16, 42i32, 42i64, 1.414f32, 3.14f64] { + Node2_0: (vec![vec![true, false].into(), vec![0i32, 42i32].into()]) {}, + Node2_1: [ + vec![std::f32::NAN, std::f32::INFINITY], + vec![std::f64::NAN, std::f64::INFINITY] + ] {}, + }, + } + } + let tree1 = gentree(); + let tree2 = gentree(); + assert!(tree1.strict_eq(&tree2)); + let empty = tree_v7400!(); + assert!(!empty.strict_eq(&tree2)); + } + + #[test] + fn correct_tree() { + let tree_manual = { + let mut tree = Tree::default(); + // Node 0: Without attributes, with children. + // Node 0-x: Without attributes, without children. + let node0_id = tree.append_new(tree.root().node_id(), "Node0"); + tree.append_new(node0_id, "Node0_0"); + tree.append_new(node0_id, "Node0_1"); + + // Node 1: With attributes, with children. + // Node 1-x: With attributes, without children. + let node1_id = tree.append_new(tree.root().node_id(), "Node1"); + tree.append_attribute(node1_id, true); + let node1_0_id = tree.append_new(node1_id, "Node1_0"); + tree.append_attribute(node1_0_id, 42i32); + tree.append_attribute(node1_0_id, 3.14f64); + let node1_1_id = tree.append_new(node1_id, "Node1_1"); + tree.append_attribute(node1_1_id, &[1u8, 2, 4, 8, 16] as &[_]); + tree.append_attribute(node1_1_id, "Hello, world"); + + tree + }; + + let tree_macro = tree_v7400! { + Node0: { + Node0_0: {}, + Node0_1: {}, + }, + Node1: [true] { + Node1_0: (vec![42i32.into(), 3.14f64.into()]) {} + Node1_1: [&[1u8, 2, 4, 8, 16][..], "Hello, world"] {} + }, + }; + + assert!(tree_manual.strict_eq(&tree_macro)); + } } diff --git a/tests/tree-macro-v7400.rs b/tests/tree-macro-v7400.rs deleted file mode 100644 index f170f6f..0000000 --- a/tests/tree-macro-v7400.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Tree construction macro test. -#![cfg(all(feature = "tree", feature = "writer"))] - -use fbxcel::{tree::v7400::Tree, tree_v7400}; - -#[test] -fn compare_empties() { - let tree1 = tree_v7400! {}; - let tree2 = tree_v7400! {}; - assert!(tree1.strict_eq(&tree2)); -} - -#[test] -fn compare_trees() { - fn gentree() -> Tree { - tree_v7400! { - Node0: {}, - Node1: { - Node1_0: {}, - Node1_1: {}, - Node1_2: { - Node1_2_child: {}, - Node1_2_child: {}, - }, - }, - Node2: [true, 42i16, 42i32, 42i64, 1.414f32, 3.14f64] { - Node2_0: (vec![vec![true, false].into(), vec![0i32, 42i32].into()]) {}, - Node2_1: [ - vec![std::f32::NAN, std::f32::INFINITY], - vec![std::f64::NAN, std::f64::INFINITY] - ] {}, - }, - } - } - let tree1 = gentree(); - let tree2 = gentree(); - assert!(tree1.strict_eq(&tree2)); - let empty = tree_v7400!(); - assert!(!empty.strict_eq(&tree2)); -} - -#[test] -fn correct_tree() { - let tree_manual = { - let mut tree = Tree::default(); - // Node 0: Without attributes, with children. - // Node 0-x: Without attributes, without children. - let node0_id = tree.append_new(tree.root().node_id(), "Node0"); - tree.append_new(node0_id, "Node0_0"); - tree.append_new(node0_id, "Node0_1"); - - // Node 1: With attributes, with children. - // Node 1-x: With attributes, without children. - let node1_id = tree.append_new(tree.root().node_id(), "Node1"); - tree.append_attribute(node1_id, true); - let node1_0_id = tree.append_new(node1_id, "Node1_0"); - tree.append_attribute(node1_0_id, 42i32); - tree.append_attribute(node1_0_id, 3.14f64); - let node1_1_id = tree.append_new(node1_id, "Node1_1"); - tree.append_attribute(node1_1_id, &[1u8, 2, 4, 8, 16] as &[_]); - tree.append_attribute(node1_1_id, "Hello, world"); - - tree - }; - - let tree_macro = tree_v7400! { - Node0: { - Node0_0: {}, - Node0_1: {}, - }, - Node1: [true] { - Node1_0: (vec![42i32.into(), 3.14f64.into()]) {} - Node1_1: [&[1u8, 2, 4, 8, 16][..], "Hello, world"] {} - }, - }; - - assert!(tree_manual.strict_eq(&tree_macro)); -} From c3d1166a1876c2baa0e5c022fbe75b7ab991cf1e Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Fri, 26 Apr 2019 02:34:20 +0900 Subject: [PATCH 46/60] Move some `use` statements to toplevel --- tests/writer-binary-v7400.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/writer-binary-v7400.rs b/tests/writer-binary-v7400.rs index 88e5a38..e1902c2 100644 --- a/tests/writer-binary-v7400.rs +++ b/tests/writer-binary-v7400.rs @@ -9,6 +9,7 @@ use fbxcel::{ any::{from_seekable_reader, AnyParser}, v7400::attribute::loaders::DirectLoader, }, + write_v7400_binary, writer::v7400::binary::{FbxFooter, Writer}, }; @@ -104,8 +105,6 @@ fn empty_write_v7400() -> Result<(), Box> { /// Compares expected binary and binary generated with events. #[test] fn tree_write_7500() -> Result<(), Box> { - use fbxcel::write_v7400_binary; - let mut dest = Vec::new(); let cursor = Cursor::new(&mut dest); let mut writer = Writer::new(cursor, FbxVersion::V7_5)?; @@ -181,8 +180,6 @@ fn tree_write_7500() -> Result<(), Box> { #[test] fn macro_v7400_idempotence() -> Result<(), Box> { - use fbxcel::write_v7400_binary; - let version = FbxVersion::V7_4; let mut writer = Writer::new(std::io::Cursor::new(Vec::new()), version)?; From 5d77b24ed4dbfecdc41fd6bedd73e7a633559d5b Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Fri, 26 Apr 2019 02:41:09 +0900 Subject: [PATCH 47/60] Make tests module name and doc comment more detailed --- ...writer-binary-v7400.rs => write-and-parse-v7400-binary.rs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename tests/{writer-binary-v7400.rs => write-and-parse-v7400-binary.rs} (98%) diff --git a/tests/writer-binary-v7400.rs b/tests/write-and-parse-v7400-binary.rs similarity index 98% rename from tests/writer-binary-v7400.rs rename to tests/write-and-parse-v7400-binary.rs index e1902c2..e064b3b 100644 --- a/tests/writer-binary-v7400.rs +++ b/tests/write-and-parse-v7400-binary.rs @@ -1,4 +1,4 @@ -//! Writer test. +//! Writer and parser test. #![cfg(feature = "writer")] use std::{cell::RefCell, io::Cursor, iter, rc::Rc}; @@ -104,7 +104,7 @@ fn empty_write_v7400() -> Result<(), Box> { /// Compares expected binary and binary generated with events. #[test] -fn tree_write_7500() -> Result<(), Box> { +fn tree_write_v7500() -> Result<(), Box> { let mut dest = Vec::new(); let cursor = Cursor::new(&mut dest); let mut writer = Writer::new(cursor, FbxVersion::V7_5)?; From a8ae57e2f955a443553ce1f348d1367b9beb9dee Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Fri, 26 Apr 2019 02:56:03 +0900 Subject: [PATCH 48/60] Add an idempotence test for write and parse --- tests/write-tree-and-parse-v7400-binary.rs | 56 ++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/write-tree-and-parse-v7400-binary.rs diff --git a/tests/write-tree-and-parse-v7400-binary.rs b/tests/write-tree-and-parse-v7400-binary.rs new file mode 100644 index 0000000..193bf86 --- /dev/null +++ b/tests/write-tree-and-parse-v7400-binary.rs @@ -0,0 +1,56 @@ +//! Tests for writer, tree, and parser. +#![cfg(all(feature = "tree", feature = "writer"))] + +use std::{cell::RefCell, io::Cursor, rc::Rc}; + +use fbxcel::{ + low::FbxVersion, + pull_parser::any::{from_seekable_reader, AnyParser}, + tree::v7400::Loader as TreeLoader, + tree_v7400, + writer::v7400::binary::Writer, +}; + +/// Construct tree, export it to binary, parse it and construct tree, and +/// compare them. +#[test] +fn tree_write_parse_idempotence_v7500() -> Result<(), Box> { + // Construct tree. + let tree1 = tree_v7400! { + Node0: { + Node0_0: {}, + Node0_1: {}, + }, + Node1: [true] { + Node1_0: (vec![42i32.into(), 3.14f64.into()]) {} + Node1_1: [&[1u8, 2, 4, 8, 16][..], "Hello, world"] {} + }, + }; + + let mut writer = Writer::new(Cursor::new(Vec::new()), FbxVersion::V7_5)?; + writer.write_tree(&tree1)?; + let bin = writer.finalize_and_flush(&Default::default())?.into_inner(); + + let mut parser = match from_seekable_reader(Cursor::new(bin))? { + AnyParser::V7400(parser) => parser, + _ => panic!("Generated data should be parsable with v7400 parser"), + }; + let warnings = Rc::new(RefCell::new(Vec::new())); + parser.set_warning_handler({ + let warnings = warnings.clone(); + move |warning, _pos| { + warnings.borrow_mut().push(warning); + Ok(()) + } + }); + assert_eq!(parser.fbx_version(), FbxVersion::V7_5); + + let (tree2, footer_res) = TreeLoader::new().load(&mut parser)?; + + assert_eq!(warnings.borrow().len(), 0); + assert!(footer_res.is_ok()); + + assert!(tree1.strict_eq(&tree2)); + + Ok(()) +} From 238b580891b7a3e97517d4dee995caf77ee92fc2 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Fri, 26 Apr 2019 03:03:43 +0900 Subject: [PATCH 49/60] Ensure not skipping implicit root node --- src/pull_parser/v7400/parser.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pull_parser/v7400/parser.rs b/src/pull_parser/v7400/parser.rs index 0589289..be90e31 100644 --- a/src/pull_parser/v7400/parser.rs +++ b/src/pull_parser/v7400/parser.rs @@ -421,8 +421,10 @@ impl Parser { /// // Do something here. /// // Something done. /// let depth = parser.current_depth(); - /// parser.skip_current_node().expect("Failed to skip current node"); - /// assert_eq!(parser.current_depth(), depth - 1); + /// if depth > 0 { + /// parser.skip_current_node().expect("Failed to skip current node"); + /// assert_eq!(parser.current_depth(), depth - 1); + /// } /// ``` /// /// `parser.skip_current_node()` is almost same as the code below, except From abe0762f14f36848940c93bad72e02beb18bd240 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Fri, 26 Apr 2019 03:05:25 +0900 Subject: [PATCH 50/60] Use consistent doc comment form --- src/pull_parser/position.rs | 4 ++-- src/pull_parser/v7400/parser.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pull_parser/position.rs b/src/pull_parser/position.rs index 37d0c71..3532516 100644 --- a/src/pull_parser/position.rs +++ b/src/pull_parser/position.rs @@ -28,7 +28,7 @@ impl SyntacticPosition { self.byte_pos } - /// Beginning the byte position of the node or attribute. + /// Returns the beginning byte position of the node or attribute. pub fn component_byte_pos(&self) -> u64 { self.component_byte_pos } @@ -41,7 +41,7 @@ impl SyntacticPosition { &self.node_path } - /// Node attribute index (if the position points an attribute). + /// Returns the node attribute index (if the position points an attribute). pub fn attribute_index(&self) -> Option { self.attribute_index } diff --git a/src/pull_parser/v7400/parser.rs b/src/pull_parser/v7400/parser.rs index be90e31..6cebab4 100644 --- a/src/pull_parser/v7400/parser.rs +++ b/src/pull_parser/v7400/parser.rs @@ -370,7 +370,7 @@ impl Parser { Ok(EventKind::StartNode) } - /// Skip unread attribute of the current node, if remains. + /// Skips unread attribute of the current node, if remains. /// /// If there are no unread attributes, this method simply do nothing. fn skip_unread_attributes(&mut self) -> Result<()> { @@ -391,7 +391,7 @@ impl Parser { self.state.health = Health::Aborted(pos); } - /// Ignore events until the current node closes. + /// Ignores events until the current node closes. /// /// This discards parser events until the [`EndNode`] event for the current /// node is read. From 99e79becf04642711516f5a0516a80fe41063870 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Fri, 26 Apr 2019 03:10:22 +0900 Subject: [PATCH 51/60] Make a doc comment more detailed --- src/pull_parser/v7400/parser.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/pull_parser/v7400/parser.rs b/src/pull_parser/v7400/parser.rs index 6cebab4..24ed73d 100644 --- a/src/pull_parser/v7400/parser.rs +++ b/src/pull_parser/v7400/parser.rs @@ -83,6 +83,14 @@ impl Parser { /// Sets the warning handler. /// + /// The warning handler will receive warnings and their [syntactic + /// position]s each time the warnings happen. + /// + /// If the handler returned `Ok(())`, the warning is considered non-critical + /// and parsing can be continued. + /// If the handler returned `Err(_)`, the warning is considered critical, + /// and the parsing cannot be continued. + /// /// # Examples /// /// ```no_run @@ -100,6 +108,8 @@ impl Parser { /// Ok(()) /// }); /// ``` + /// + /// [syntactic position]: ../struct.SyntacticPosition.html pub fn set_warning_handler(&mut self, warning_handler: F) where F: 'static + FnMut(Warning, &SyntacticPosition) -> Result<()>, From 1943c810667997c768fd5dca0034cfa22c9d10bb Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Fri, 26 Apr 2019 16:21:49 +0900 Subject: [PATCH 52/60] Update lowest supported Rust version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 834d546..e4bef92 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build Status](https://travis-ci.org/lo48576/fbxcel.svg?branch=develop)](https://travis-ci.org/lo48576/fbxcel) [![Latest version](https://img.shields.io/crates/v/fbxcel.svg)](https://crates.io/crates/fbxcel) [![Documentation](https://docs.rs/fbxcel/badge.svg)](https://docs.rs/fbxcel) -![Minimum rustc version: 1.33](https://img.shields.io/badge/rustc-1.33+-lightgray.svg) +![Minimum rustc version: 1.34](https://img.shields.io/badge/rustc-1.34+-lightgray.svg) `fbxcel` is an FBX library for Rust programming language. @@ -34,7 +34,7 @@ Currently there is no plan to support FBX ASCII format. ## Rust version -Latest stable compiler (currently 1.33) is supported. +Latest stable compiler (currently 1.34) is supported. ## License From d6b6dabf64a9bedc4bbbe7df0b54f6a22ce46380 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Fri, 26 Apr 2019 16:07:21 +0900 Subject: [PATCH 53/60] Add CI test for lowest supported Rust version --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 7117962..2fce870 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,5 +3,6 @@ cache: cargo rust: - stable - beta + - 1.34.0 notifications: email: false From d5e296ffa8590487636ec2c0b9eb41bfb0242482 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Fri, 26 Apr 2019 16:13:45 +0900 Subject: [PATCH 54/60] Run CI tests with all features enabled --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 2fce870..886caf7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,5 +4,8 @@ rust: - stable - beta - 1.34.0 +script: + - cargo build --verbose --all-features + - cargo test --verbose --all-features notifications: email: false From ccb4fb9c1752d38e6fd046eb488ccff548f710d7 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Tue, 4 Jun 2019 15:59:58 +0900 Subject: [PATCH 55/60] Use edition 2018 idiom * Specify lifetime parameters explicitly. * Specify `dyn` explicitly for boxed trait objects --- src/tree/v7400.rs | 6 +++--- src/writer/v7400/binary.rs | 6 +++--- src/writer/v7400/binary/attributes.rs | 6 +++--- src/writer/v7400/binary/attributes/array.rs | 2 +- src/writer/v7400/binary/error.rs | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/tree/v7400.rs b/src/tree/v7400.rs index 39199b2..fbcea37 100644 --- a/src/tree/v7400.rs +++ b/src/tree/v7400.rs @@ -212,7 +212,7 @@ struct DebugTree<'a> { } impl fmt::Debug for DebugTree<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let v = DebugNodeHandle { node: self.tree.root(), }; @@ -227,7 +227,7 @@ struct DebugNodeHandle<'a> { } impl fmt::Debug for DebugNodeHandle<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Node") .field("name", &self.node.name()) .field("attributes", &self.node.attributes()) @@ -243,7 +243,7 @@ struct DebugNodeHandleChildren<'a> { } impl fmt::Debug for DebugNodeHandleChildren<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_list() .entries( self.node diff --git a/src/writer/v7400/binary.rs b/src/writer/v7400/binary.rs index cc2cb2e..798fc82 100644 --- a/src/writer/v7400/binary.rs +++ b/src/writer/v7400/binary.rs @@ -373,14 +373,14 @@ impl Writer { /// You may want to use [`finalize_and_flush()`]. /// /// [`finalize_and_flush()`]: #method.finalize_and_flush - pub fn finalize(mut self, footer: &FbxFooter) -> Result { + pub fn finalize(mut self, footer: &FbxFooter<'_>) -> Result { self.finalize_impl(footer)?; Ok(self.sink) } /// Finalizes the FBX binary, and returns the inner sink after flushing. - pub fn finalize_and_flush(mut self, footer: &FbxFooter) -> Result { + pub fn finalize_and_flush(mut self, footer: &FbxFooter<'_>) -> Result { self.finalize_impl(footer)?; self.sink.flush()?; @@ -388,7 +388,7 @@ impl Writer { } /// Internal implementation of `finalize()` and `finalize_and_flush()`. - fn finalize_impl(&mut self, footer: &FbxFooter) -> Result<()> { + fn finalize_impl(&mut self, footer: &FbxFooter<'_>) -> Result<()> { if !self.open_nodes.is_empty() { return Err(Error::UnclosedNode(self.open_nodes.len())); } diff --git a/src/writer/v7400/binary/attributes.rs b/src/writer/v7400/binary/attributes.rs index 124fe42..0f9b81a 100644 --- a/src/writer/v7400/binary/attributes.rs +++ b/src/writer/v7400/binary/attributes.rs @@ -118,7 +118,7 @@ macro_rules! impl_arr_from_iter { iter: impl IntoIterator>, ) -> Result<()> where - E: Into>, + E: Into>, { array::write_array_attr_result_iter( self, @@ -340,7 +340,7 @@ impl<'a, W: Write + Seek> AttributesWriter<'a, W> { iter: impl IntoIterator>, ) -> Result<()> where - E: Into>, + E: Into>, { let header_pos = self.initialize_special(AttributeType::Binary)?; @@ -387,7 +387,7 @@ impl<'a, W: Write + Seek> AttributesWriter<'a, W> { iter: impl IntoIterator>, ) -> Result<()> where - E: Into>, + E: Into>, { let header_pos = self.initialize_special(AttributeType::String)?; diff --git a/src/writer/v7400/binary/attributes/array.rs b/src/writer/v7400/binary/attributes/array.rs index d279359..4bb9815 100644 --- a/src/writer/v7400/binary/attributes/array.rs +++ b/src/writer/v7400/binary/attributes/array.rs @@ -68,7 +68,7 @@ pub(crate) fn write_array_header( /// Writes the given array attribute. pub(crate) fn write_array_attr_result_iter>( - writer: &mut AttributesWriter, + writer: &mut AttributesWriter<'_, W>, ty: AttributeType, encoding: Option, iter: impl IntoIterator>, diff --git a/src/writer/v7400/binary/error.rs b/src/writer/v7400/binary/error.rs index 3f754d4..b06fb4a 100644 --- a/src/writer/v7400/binary/error.rs +++ b/src/writer/v7400/binary/error.rs @@ -46,7 +46,7 @@ impl error::Error for Error { } impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::AttributeTooLong(v) => write!(f, "Node attribute is too long: {} bytes", v), Error::Compression(e) => write!(f, "Compression error: {}", e), @@ -95,7 +95,7 @@ impl error::Error for CompressionError { } impl fmt::Display for CompressionError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { CompressionError::Zlib(e) => write!(f, "Zlib compression error: {}", e), } From 48dde3bc29d749ddda72b14f71756007b33198ce Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Fri, 9 Aug 2019 11:01:15 +0900 Subject: [PATCH 56/60] Bump indextree to 4.0.0 API of `indextree` is not exposed from `fbxcel`, so this is non-breaking change for `fbxcel`. --- Cargo.toml | 2 +- src/tree/v7400.rs | 22 +++++----------------- src/tree/v7400/loader.rs | 4 +--- src/tree/v7400/node/handle.rs | 4 ++-- 4 files changed, 9 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index be7f3f0..67fb934 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ writer = [] [dependencies] byteorder = "1" -indextree = { version = "3", optional = true } +indextree = { version = "4", optional = true } libflate = "0.1" log = "0.4.4" string-interner = { version = "0.7", optional = true, default-features = false } diff --git a/src/tree/v7400.rs b/src/tree/v7400.rs index fbcea37..f80c852 100644 --- a/src/tree/v7400.rs +++ b/src/tree/v7400.rs @@ -92,10 +92,7 @@ impl Tree { pub fn append_new(&mut self, parent: NodeId, name: &str) -> NodeId { let name_sym = self.node_names.get_or_intern(name); let new_child = self.arena.new_node(NodeData::new(name_sym, Vec::new())); - parent - .raw() - .append(new_child, &mut self.arena) - .expect("Should never fail"); + parent.raw().append(new_child, &mut self.arena); NodeId::new(new_child) } @@ -108,10 +105,7 @@ impl Tree { pub fn prepend_new(&mut self, parent: NodeId, name: &str) -> NodeId { let name_sym = self.node_names.get_or_intern(name); let new_child = self.arena.new_node(NodeData::new(name_sym, Vec::new())); - parent - .raw() - .prepend(new_child, &mut self.arena) - .expect("Should never fail"); + parent.raw().prepend(new_child, &mut self.arena); NodeId::new(new_child) } @@ -125,10 +119,7 @@ impl Tree { assert_ne!(sibling, self.root_id, "Root node should have no siblings"); let name_sym = self.node_names.get_or_intern(name); let new_child = self.arena.new_node(NodeData::new(name_sym, Vec::new())); - sibling - .raw() - .insert_after(new_child, &mut self.arena) - .expect("Should never fail"); + sibling.raw().insert_after(new_child, &mut self.arena); NodeId::new(new_child) } @@ -142,10 +133,7 @@ impl Tree { assert_ne!(sibling, self.root_id, "Root node should have no siblings"); let name_sym = self.node_names.get_or_intern(name); let new_child = self.arena.new_node(NodeData::new(name_sym, Vec::new())); - sibling - .raw() - .insert_before(new_child, &mut self.arena) - .expect("Should never fail"); + sibling.raw().insert_before(new_child, &mut self.arena); NodeId::new(new_child) } @@ -167,7 +155,7 @@ impl Tree { fn append_attribute_impl(&mut self, node_id: NodeId, v: AttributeValue) { assert_ne!(node_id, self.root_id, "Root node should have no attributes"); let node = self.arena.get_mut(node_id.raw()).expect("Invalid node ID"); - node.data.append_attribute(v) + node.get_mut().append_attribute(v) } /// Compares trees strictly. diff --git a/src/tree/v7400/loader.rs b/src/tree/v7400/loader.rs index edbede7..6edfab4 100644 --- a/src/tree/v7400/loader.rs +++ b/src/tree/v7400/loader.rs @@ -121,9 +121,7 @@ impl Loader { }; // Set the parent. - parent.raw().append(current.raw(), &mut self.arena).expect( - "Should never fail: The newly created node should always be successfully appended", - ); + parent.raw().append(current.raw(), &mut self.arena); trace!( "Successfully added a new child {:?} to the parent {:?}", diff --git a/src/tree/v7400/node/handle.rs b/src/tree/v7400/node/handle.rs index 83a4908..2877ece 100644 --- a/src/tree/v7400/node/handle.rs +++ b/src/tree/v7400/node/handle.rs @@ -51,7 +51,7 @@ impl<'a> NodeHandle<'a> { /// Returns the node name symbol. pub(crate) fn name_sym(&self) -> NodeNameSym { - self.node().data.name_sym() + self.node().get().name_sym() } /// Returns the node name. @@ -61,7 +61,7 @@ impl<'a> NodeHandle<'a> { /// Returns the node attributes. pub fn attributes(&self) -> &'a [AttributeValue] { - self.node().data.attributes() + self.node().get().attributes() } /// Returns an iterator of children with the given name. From 8b8090a54fb2c8ae2c0a997e11074707d1abafb6 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Fri, 26 Apr 2019 16:32:41 +0900 Subject: [PATCH 57/60] Bump version --- CHANGELOG.md | 5 ++++- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e87f4a7..a216293 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [Unreleased] +## [0.4.4] + * Documents are improved a little. * Manual tree construction support is added. * FBX binary writer is added. @@ -207,7 +209,8 @@ Totally rewritten. -[Unreleased]: +[Unreleased]: +[0.4.4]: [0.4.3]: [0.4.2]: [0.4.1]: diff --git a/Cargo.toml b/Cargo.toml index 67fb934..cc8345e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fbxcel" -version = "0.4.3" +version = "0.4.4" authors = ["YOSHIOKA Takuma "] edition = "2018" license = "MIT OR Apache-2.0" From d7207e8aba5eef29fccde24e81b90fa9b3c8da4b Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Fri, 26 Apr 2019 16:34:23 +0900 Subject: [PATCH 58/60] Add description of `writer` feature --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index e4bef92..66fc8b3 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ If you want to interpret and render FBX data, use * Pull parser for FBX binary (`pull_parser` module) + FBX 7.4 and 7.5 is explicitly supported. +* Writer for FBX binary (`writer` module) + + FBX 7.4 and 7.5 is explicitly supported. + + This is optional and enabled by `writer` feature. * Types and functions for low-level FBX tree access + This is optional and enabled by `tree` feature. + Provides arena-based tree type and read-only access to nodes. From 27a5174a131c32cca5daa92a61d3c36734db574d Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Fri, 26 Apr 2019 17:50:57 +0900 Subject: [PATCH 59/60] Organize CHANGELOG --- CHANGELOG.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a216293..1ac2219 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,14 @@ ## [0.4.4] * Documents are improved a little. -* Manual tree construction support is added. +* Manual tree construction (without using parser) is now supported. + + You can add nodes and attributes manually to the tree at runtime. + + You can describe the tree using `tree_v7400!` macro at compile time. * FBX binary writer is added. -* `low::v7400::AttributeValue` implements `From<_>` for some types. -* Strict equality check is added for trees, nodes, and attribute values. -* `tree_v7400!` macro is added to construct tree easily. -* `write_v7400_binary!` macro is added to write tree easily. -* `tree::v7400::Tree::debug_tree()` is added. +* Tiny improvements: + + `low::v7400::AttributeValue` implements `From<_>` for some types. + + Strict equality check is added for trees, nodes, and attribute values. + + `tree::v7400::Tree::debug_tree()` is added. * Now rustc-1.34 or later is required. + To use `std::convert::{TryFrom, TryInto}`. @@ -20,9 +21,13 @@ + Methods to add new nodes and attributes are added. + Complete modification is not yet supported, for example modifying already added attributes or removing nodes. + * `tree_v7400!` macro is added to construct tree easily. + See documentation for detail. * FBX binary writer is added. + `writer::v7400::binary` contains FBX binary writer stuff. + This can be enabled by `writer` feature. + + `write_v7400_binary!` macro is also added. + See the documentation for detail. * `low::v7400::AttributeValue` implements `From<_>` for some types. + Primitive types: `bool`, `i16`, `i32`, `i64`, `f32`, `f64`. + Vector types: `Vec`, `Vec`, `Vec`, `Vec`, `Vec`, @@ -35,12 +40,6 @@ + Attributes: `low::v7400::AttributeValue::strict_eq()`. + These checks compares `f32` and `f64` bitwise. This means `NAN == NAN` situation is possible. -* `tree_v7400!` macro is added to construct tree easily. - + Enabled by `tree` feature. - + See documentation. -* `write_v7400_binary!` macro is added to write tree easily. - + Enabled by `writer` feature. - + See documentation. * `tree::v7400::Tree::debug_tree()` is added. * This returns pretty-printable object of the tree. * It dumps human-readable tree structure. From 206b2b1ebfb85ce65bb8d20a7965723cf7fe39b9 Mon Sep 17 00:00:00 2001 From: YOSHIOKA Takuma Date: Fri, 26 Apr 2019 18:15:29 +0900 Subject: [PATCH 60/60] Add commit hashes --- CHANGELOG.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ac2219..f510d79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,30 +17,33 @@ + To use `std::convert::{TryFrom, TryInto}`. ### Added -* Manual tree construction support is added. +* Manual tree construction support is added (64f70b051c30, 39c4fabad119). + Methods to add new nodes and attributes are added. + Complete modification is not yet supported, for example modifying already added attributes or removing nodes. * `tree_v7400!` macro is added to construct tree easily. See documentation for detail. -* FBX binary writer is added. +* FBX binary writer is added (e1cb2a232d19, 33d9ac3a589c, d5dc779c0bd4, + 6cddca849a4f, 8c84359d2578). + `writer::v7400::binary` contains FBX binary writer stuff. + This can be enabled by `writer` feature. + `write_v7400_binary!` macro is also added. See the documentation for detail. -* `low::v7400::AttributeValue` implements `From<_>` for some types. +* `low::v7400::AttributeValue` implements `From<_>` for some types + (a54226534a73, 6546d62fd38a). + Primitive types: `bool`, `i16`, `i32`, `i64`, `f32`, `f64`. + Vector types: `Vec`, `Vec`, `Vec`, `Vec`, `Vec`, `Vec`. + Slice types: `&[bool]`, `&[i32]`, `&[i64]`, `&[f32]`, `&[f64]`, `&[u8]`. + Special types: `String`, `&str`. -* Strict equality check is added for trees, nodes, and attribute values. +* Strict equality check is added for trees, nodes, and attribute values + (8784d7609d8e). + Trees: `tree::v7400::Tree::strict_eq()`. + Nodes: `tree::v7400::NodeHandle::strict_eq()`. + Attributes: `low::v7400::AttributeValue::strict_eq()`. + These checks compares `f32` and `f64` bitwise. This means `NAN == NAN` situation is possible. -* `tree::v7400::Tree::debug_tree()` is added. +* `tree::v7400::Tree::debug_tree()` is added (4524b4dc4a99). * This returns pretty-printable object of the tree. * It dumps human-readable tree structure. * Default `Debug` implementation for `Tree` is hard to read because it dumps