//----------------------------------------------------------------------------// // // // ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation // // and distributed under the MIT License (MIT). // // // // Copyright (c) Guillaume Blanc // // // // Permission is hereby granted, free of charge, to any person obtaining a // // copy of this software and associated documentation files (the "Software"), // // to deal in the Software without restriction, including without limitation // // the rights to use, copy, modify, merge, publish, distribute, sublicense, // // and/or sell copies of the Software, and to permit persons to whom the // // Software is furnished to do so, subject to the following conditions: // // // // The above copyright notice and this permission notice shall be included in // // all copies or substantial portions of the Software. // // // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // // DEALINGS IN THE SOFTWARE. // // // //----------------------------------------------------------------------------// #ifndef OZZ_OZZ_BASE_IO_ARCHIVE_H_ #define OZZ_OZZ_BASE_IO_ARCHIVE_H_ // Provides input (IArchive) and output (OArchive) serialization containers. // Archive are similar to c++ iostream. Data can be saved to a OArchive with // the << operator, or loaded from a IArchive with the >> operator. // Primitive data types are simply saved/loaded to/from archives, while struct // and class are saved/loaded through Save/Load intrusive or non-intrusive // functions: // - The intrusive function prototypes are "void Save(ozz::io::OArchive*) const" // and "void Load(ozz::io::IArchive*)". // - The non-intrusive functions allow to work on arrays of objects. They must // be implemented in ozz::io namespace, by specializing the following template // struct: // template // struct Extern { // static void Save(OArchive& _archive, const _Ty* _ty, size_t _count); // static void Load(IArchive& _archive, _Ty* _ty, size_t _count, // uint32_t _version); // }; // // Arrays of struct/class or primitive types can be saved/loaded with the // helper function ozz::io::MakeArray() that is then streamed in or out using // << and >> archive operators: archive << ozz::io::MakeArray(my_array, count); // // Versioning can be done using OZZ_IO_TYPE_VERSION macros. Type version // is saved in the OArchive, and is given back to Load functions to allow to // manually handle version modifications. Versioning can be disabled using // OZZ_IO_TYPE_NOT_VERSIONABLE like macros. It can not be re-enabled afterward. // // Objects can be assigned a tag using OZZ_IO_TYPE_TAG macros. A tag allows to // check the type of the next object to read from an archive. An automatic // assertion check is performed for each object that has a tag. It can also be // done manually to ensure an archive has the expected content. // // Endianness (big-endian or little-endian) can be specified while constructing // an output archive (ozz::io::OArchive). Input archives automatically handle // endianness conversion if the native platform endian mode differs from the // archive one. // // IArchive and OArchive expect valid streams as argument, respectively opened // for reading and writing. Archives do NOT perform error detection while // reading or writing. All errors are considered programming errors. This leads // to the following assertions on the user side: // - When writing: the stream must be big (or grow-able) enough to support the // data being written. // - When reading: Stream's tell (position in the stream) must match the object // being read. To help with this requirement, archives provide a tag mechanism // that allows to check the tag (ie the type) of the next object to read. Stream // integrity, like data corruption or file truncation, must also be validated on // the user side. #include "ozz/base/endianness.h" #include "ozz/base/io/stream.h" #include "ozz/base/platform.h" #include "ozz/base/span.h" #include #include #include "ozz/base/io/archive_traits.h" namespace ozz { namespace io { namespace internal { // Defines Tagger helper object struct. // The boolean template argument is used to automatically select a template // specialization, whether _Ty has a tag or not. template ::kTagLength != 0> struct Tagger; } // namespace internal // Implements output archive concept used to save/serialize data from a Stream. // The output endianness mode is set at construction time. It is written to the // stream to allow the IArchive to perform the required conversion to the native // endianness mode while reading. class OArchive { public: // Constructs an output archive from the Stream _stream that must be valid // and opened for writing. explicit OArchive(Stream* _stream, Endianness _endianness = GetNativeEndianness()); // Returns true if an endian swap is required while writing. bool endian_swap() const { return endian_swap_; } // Saves _size bytes of binary data from _data. size_t SaveBinary(const void* _data, size_t _size) { return stream_->Write(_data, _size); } // Class type saving. template void operator<<(const _Ty& _ty) { internal::Tagger::Write(*this); SaveVersion<_Ty>(); Extern<_Ty>::Save(*this, &_ty, 1); } // Primitive type saving. #define OZZ_IO_PRIMITIVE_TYPE(_type) \ void operator<<(_type _v) { \ _type v = endian_swap_ ? EndianSwapper<_type>::Swap(_v) : _v; \ OZZ_IF_DEBUG(size_t size =) stream_->Write(&v, sizeof(v)); \ assert(size == sizeof(v)); \ } OZZ_IO_PRIMITIVE_TYPE(char) OZZ_IO_PRIMITIVE_TYPE(int8_t) OZZ_IO_PRIMITIVE_TYPE(uint8_t) OZZ_IO_PRIMITIVE_TYPE(int16_t) OZZ_IO_PRIMITIVE_TYPE(uint16_t) OZZ_IO_PRIMITIVE_TYPE(int32_t) OZZ_IO_PRIMITIVE_TYPE(uint32_t) OZZ_IO_PRIMITIVE_TYPE(int64_t) OZZ_IO_PRIMITIVE_TYPE(uint64_t) OZZ_IO_PRIMITIVE_TYPE(bool) OZZ_IO_PRIMITIVE_TYPE(float) #undef OZZ_IO_PRIMITIVE_TYPE // Returns output stream. Stream* stream() const { return stream_; } private: template void SaveVersion() { // Compilation could fail here if the version is not defined for _Ty, or if // the .h file containing its definition is not included by the caller of // this function. if (void(0), internal::Version::kValue != 0) { uint32_t version = internal::Version::kValue; *this << version; } } // The output stream. Stream* stream_; // Endian swap state, true if a conversion is required while writing. bool endian_swap_; }; // Implements input archive concept used to load/de-serialize data to a Stream. // Endianness conversions are automatically performed according to the Archive // and the native formats. class IArchive { public: // Constructs an input archive from the Stream _stream that must be opened for // reading, at the same tell (position in the stream) as when it was passed to // the OArchive. explicit IArchive(Stream* _stream); // Returns true if an endian swap is required while reading. bool endian_swap() const { return endian_swap_; } // Loads _size bytes of binary data to _data. size_t LoadBinary(void* _data, size_t _size) { return stream_->Read(_data, _size); } // Class type loading. template void operator>>(_Ty& _ty) { // Only uses tag validation for assertions, as reading cannot fail. OZZ_IF_DEBUG(bool valid =) internal::Tagger::Validate(*this); assert(valid && "Type tag does not match archive content."); // Loads instance. uint32_t version = LoadVersion<_Ty>(); Extern<_Ty>::Load(*this, &_ty, 1, version); } // Primitive type loading. #define OZZ_IO_PRIMITIVE_TYPE(_type) \ void operator>>(_type& _v) { \ _type v; \ OZZ_IF_DEBUG(size_t size =) stream_->Read(&v, sizeof(v)); \ assert(size == sizeof(v)); \ _v = endian_swap_ ? EndianSwapper<_type>::Swap(v) : v; \ } OZZ_IO_PRIMITIVE_TYPE(char) OZZ_IO_PRIMITIVE_TYPE(int8_t) OZZ_IO_PRIMITIVE_TYPE(uint8_t) OZZ_IO_PRIMITIVE_TYPE(int16_t) OZZ_IO_PRIMITIVE_TYPE(uint16_t) OZZ_IO_PRIMITIVE_TYPE(int32_t) OZZ_IO_PRIMITIVE_TYPE(uint32_t) OZZ_IO_PRIMITIVE_TYPE(int64_t) OZZ_IO_PRIMITIVE_TYPE(uint64_t) OZZ_IO_PRIMITIVE_TYPE(bool) OZZ_IO_PRIMITIVE_TYPE(float) #undef OZZ_IO_PRIMITIVE_TYPE template bool TestTag() { // Only tagged types can be tested. If compilations fails here, it can // mean the file containing tag declaration is not included. static_assert(internal::Tag::kTagLength != 0, "Tag unknown for type."); const int tell = stream_->Tell(); bool valid = internal::Tagger::Validate(*this); stream_->Seek(tell, Stream::kSet); // Rewinds before the tag test. return valid; } // Returns input stream. Stream* stream() const { return stream_; } private: template uint32_t LoadVersion() { uint32_t version = 0; if (void(0), internal::Version::kValue != 0) { *this >> version; } return version; } // The input stream. Stream* stream_; // Endian swap state, true if a conversion is required while reading. bool endian_swap_; }; // Primitive type are not versionable. OZZ_IO_TYPE_NOT_VERSIONABLE(char) OZZ_IO_TYPE_NOT_VERSIONABLE(int8_t) OZZ_IO_TYPE_NOT_VERSIONABLE(uint8_t) OZZ_IO_TYPE_NOT_VERSIONABLE(int16_t) OZZ_IO_TYPE_NOT_VERSIONABLE(uint16_t) OZZ_IO_TYPE_NOT_VERSIONABLE(int32_t) OZZ_IO_TYPE_NOT_VERSIONABLE(uint32_t) OZZ_IO_TYPE_NOT_VERSIONABLE(int64_t) OZZ_IO_TYPE_NOT_VERSIONABLE(uint64_t) OZZ_IO_TYPE_NOT_VERSIONABLE(bool) OZZ_IO_TYPE_NOT_VERSIONABLE(float) // Default loading and saving external implementation. template struct Extern { inline static void Save(OArchive& _archive, const _Ty* _ty, size_t _count) { for (size_t i = 0; i < _count; ++i) { _ty[i].Save(_archive); } } inline static void Load(IArchive& _archive, _Ty* _ty, size_t _count, uint32_t _version) { for (size_t i = 0; i < _count; ++i) { _ty[i].Load(_archive, _version); } } }; // Wrapper for dynamic array serialization. // Must be used through ozz::io::MakeArray. namespace internal { template struct Array { OZZ_INLINE void Save(OArchive& _archive) const { ozz::io::Extern<_Ty>::Save(_archive, array, count); } OZZ_INLINE void Load(IArchive& _archive, uint32_t _version) const { ozz::io::Extern<_Ty>::Load(_archive, array, count, _version); } _Ty* array; size_t count; }; // Specialize for const _Ty which can only be saved. template struct Array { OZZ_INLINE void Save(OArchive& _archive) const { ozz::io::Extern<_Ty>::Save(_archive, array, count); } const _Ty* array; size_t count; }; // Array copies version from the type it contains. // Definition of Array of _Ty version: _Ty version. template struct Version> { enum { kValue = Version::kValue }; }; // Specializes Array Save/Load for primitive types. #define OZZ_IO_PRIMITIVE_TYPE(_type) \ template <> \ inline void Array::Save(OArchive& _archive) const { \ if (_archive.endian_swap()) { \ /* Save element by element as swapping in place the whole buffer is*/ \ /* not possible.*/ \ for (size_t i = 0; i < count; ++i) { \ _archive << array[i]; \ } \ } else { \ OZZ_IF_DEBUG(size_t size =) \ _archive.SaveBinary(array, count * sizeof(_type)); \ assert(size == count * sizeof(_type)); \ } \ } \ \ template <> \ inline void Array<_type>::Save(OArchive& _archive) const { \ if (_archive.endian_swap()) { \ /* Save element by element as swapping in place the whole buffer is*/ \ /* not possible.*/ \ for (size_t i = 0; i < count; ++i) { \ _archive << array[i]; \ } \ } else { \ OZZ_IF_DEBUG(size_t size =) \ _archive.SaveBinary(array, count * sizeof(_type)); \ assert(size == count * sizeof(_type)); \ } \ } \ \ template <> \ inline void Array<_type>::Load(IArchive& _archive, uint32_t /*_version*/) \ const { \ OZZ_IF_DEBUG(size_t size =) \ _archive.LoadBinary(array, count * sizeof(_type)); \ assert(size == count * sizeof(_type)); \ if (_archive.endian_swap()) { /*Can swap in-place.*/ \ EndianSwapper<_type>::Swap(array, count); \ } \ } OZZ_IO_PRIMITIVE_TYPE(char) OZZ_IO_PRIMITIVE_TYPE(int8_t) OZZ_IO_PRIMITIVE_TYPE(uint8_t) OZZ_IO_PRIMITIVE_TYPE(int16_t) OZZ_IO_PRIMITIVE_TYPE(uint16_t) OZZ_IO_PRIMITIVE_TYPE(int32_t) OZZ_IO_PRIMITIVE_TYPE(uint32_t) OZZ_IO_PRIMITIVE_TYPE(int64_t) OZZ_IO_PRIMITIVE_TYPE(uint64_t) OZZ_IO_PRIMITIVE_TYPE(bool) OZZ_IO_PRIMITIVE_TYPE(float) #undef OZZ_IO_PRIMITIVE_TYPE } // namespace internal // Utility function that instantiates Array wrapper. template OZZ_INLINE const internal::Array<_Ty> MakeArray(_Ty* _array, size_t _count) { const internal::Array<_Ty> array = {_array, _count}; return array; } template OZZ_INLINE const internal::Array MakeArray(const _Ty* _array, size_t _count) { const internal::Array array = {_array, _count}; return array; } template OZZ_INLINE const internal::Array<_Ty> MakeArray(span<_Ty> _array) { const internal::Array<_Ty> array = {_array.data(), _array.size()}; return array; } template OZZ_INLINE const internal::Array MakeArray(span _array) { const internal::Array array = {_array.data(), _array.size()}; return array; } template OZZ_INLINE const internal::Array<_Ty> MakeArray(_Ty (&_array)[_count]) { const internal::Array<_Ty> array = {_array, _count}; return array; } template OZZ_INLINE const internal::Array MakeArray( const _Ty (&_array)[_count]) { const internal::Array array = {_array, _count}; return array; } namespace internal { // Specialization of the Tagger helper for tagged types. template struct Tagger<_Ty, true> { static void Write(OArchive& _archive) { typedef internal::Tag Tag; OZZ_IF_DEBUG(size_t size =) _archive.SaveBinary(Tag::Get(), Tag::kTagLength); assert(size == Tag::kTagLength); } static bool Validate(IArchive& _archive) { typedef internal::Tag Tag; char buf[Tag::kTagLength]; if (Tag::kTagLength != _archive.LoadBinary(buf, Tag::kTagLength)) { return false; } const char* tag = Tag::Get(); size_t i = 0; for (; i < Tag::kTagLength && buf[i] == tag[i]; ++i) { } return i == Tag::kTagLength; } }; // Specialization of the Tagger helper for types with no tag. template struct Tagger<_Ty, false> { static void Write(OArchive& /*_archive*/) {} static bool Validate(IArchive& /*_archive*/) { return true; } }; } // namespace internal } // namespace io } // namespace ozz #endif // OZZ_OZZ_BASE_IO_ARCHIVE_H_