441 lines
18 KiB
C++
441 lines
18 KiB
C++
//----------------------------------------------------------------------------//
|
|
// //
|
|
// 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 <typename _Ty>
|
|
// 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 <stdint.h>
|
|
|
|
#include <cassert>
|
|
|
|
#include "ozz/base/endianness.h"
|
|
#include "ozz/base/io/archive_traits.h"
|
|
#include "ozz/base/io/stream.h"
|
|
#include "ozz/base/platform.h"
|
|
#include "ozz/base/span.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 <typename _Ty,
|
|
bool _HasTag = internal::Tag<const _Ty>::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 OZZ_BASE_DLL 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 <typename _Ty>
|
|
void operator<<(const _Ty& _ty) {
|
|
internal::Tagger<const _Ty>::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 <typename _Ty>
|
|
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<const _Ty>::kValue != 0) {
|
|
uint32_t version = internal::Version<const _Ty>::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 OZZ_BASE_DLL 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 <typename _Ty>
|
|
void operator>>(_Ty& _ty) {
|
|
// Only uses tag validation for assertions, as reading cannot fail.
|
|
OZZ_IF_DEBUG(bool valid =) internal::Tagger<const _Ty>::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 <typename _Ty>
|
|
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<const _Ty>::kTagLength != 0,
|
|
"Tag unknown for type.");
|
|
|
|
const int tell = stream_->Tell();
|
|
bool valid = internal::Tagger<const _Ty>::Validate(*this);
|
|
stream_->Seek(tell, Stream::kSet); // Rewinds before the tag test.
|
|
return valid;
|
|
}
|
|
|
|
// Returns input stream.
|
|
Stream* stream() const { return stream_; }
|
|
|
|
private:
|
|
template <typename _Ty>
|
|
uint32_t LoadVersion() {
|
|
uint32_t version = 0;
|
|
if (void(0), internal::Version<const _Ty>::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 <typename _Ty>
|
|
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 <typename _Ty>
|
|
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 <typename _Ty>
|
|
struct Array<const _Ty> {
|
|
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 <typename _Ty>
|
|
struct Version<const Array<_Ty>> {
|
|
enum { kValue = Version<const _Ty>::kValue };
|
|
};
|
|
|
|
// Specializes Array Save/Load for primitive types.
|
|
#define OZZ_IO_PRIMITIVE_TYPE(_type) \
|
|
template <> \
|
|
inline void Array<const _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>::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 <typename _Ty>
|
|
OZZ_INLINE const internal::Array<_Ty> MakeArray(_Ty* _array, size_t _count) {
|
|
const internal::Array<_Ty> array = {_array, _count};
|
|
return array;
|
|
}
|
|
template <typename _Ty>
|
|
OZZ_INLINE const internal::Array<const _Ty> MakeArray(const _Ty* _array,
|
|
size_t _count) {
|
|
const internal::Array<const _Ty> array = {_array, _count};
|
|
return array;
|
|
}
|
|
template <typename _Ty>
|
|
OZZ_INLINE const internal::Array<_Ty> MakeArray(span<_Ty> _array) {
|
|
const internal::Array<_Ty> array = {_array.data(), _array.size()};
|
|
return array;
|
|
}
|
|
template <typename _Ty>
|
|
OZZ_INLINE const internal::Array<const _Ty> MakeArray(span<const _Ty> _array) {
|
|
const internal::Array<const _Ty> array = {_array.data(), _array.size()};
|
|
return array;
|
|
}
|
|
template <typename _Ty, size_t _count>
|
|
OZZ_INLINE const internal::Array<_Ty> MakeArray(_Ty (&_array)[_count]) {
|
|
const internal::Array<_Ty> array = {_array, _count};
|
|
return array;
|
|
}
|
|
template <typename _Ty, size_t _count>
|
|
OZZ_INLINE const internal::Array<const _Ty> MakeArray(
|
|
const _Ty (&_array)[_count]) {
|
|
const internal::Array<const _Ty> array = {_array, _count};
|
|
return array;
|
|
}
|
|
|
|
namespace internal {
|
|
// Specialization of the Tagger helper for tagged types.
|
|
template <typename _Ty>
|
|
struct Tagger<_Ty, true> {
|
|
static void Write(OArchive& _archive) {
|
|
typedef internal::Tag<const _Ty> 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<const _Ty> 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 <typename _Ty>
|
|
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_
|