athena/include/athena/DNA.hpp

153 lines
5.4 KiB
C++

#pragma once
/* BIG FAT WARNING!!!
*
* The type-structure of this file is expected to remain consistent for 'atdna'
* Any changes to the types or namespacing must be reflected in 'atdna/main.cpp'
*/
#include <memory>
#include <string>
#include <vector>
#include <sys/types.h>
#include "athena/DNAOp.hpp"
#include "athena/Global.hpp"
#include "athena/IStreamReader.hpp"
#include "athena/IStreamWriter.hpp"
using namespace std::literals;
namespace athena::io {
/**
* @brief Base DNA class used against 'atdna'
* @tparam DNAE Default-endianness for contained DNA values
*
* Athena bundles a build-tool called 'atdna'. This tool functions
* just like the 'clang' compiler, except it emits a full .cpp implementation
* with all read/write calls necessary to marshal the DNA structure to/from
* a streamed medium
*/
template <Endian DNAE>
struct DNA {
/**
* @brief Designated byte-order used for serializing fields
*/
static constexpr Endian DNAEndian = DNAE;
/**
* @brief Template type signaling atdna to capture the value where it's used
* @tparam T The type of the value. Can be any numeric type or atVec* type
* @tparam VE Endianness of the value
*/
template <typename T, Endian VE = DNAE>
using Value = T;
/**
* @brief Template type wrapping std::vector and signaling atdna to manipulate it where it's used
* @tparam T The type of contained elements. Can be any numeric type, atVec* type, or another DNA subclass
* @tparam cntVar C++ expression wrapped in DNA_COUNT macro to determine number of elements for vector
* @tparam VE Endianness of the contained values
*/
template <typename T, size_t cntVar, Endian VE = DNAE>
using Vector = std::vector<T>;
/**
* @brief Template type wrapping std::unique_ptr<atUint8[]> and signaling atdna to read a
* raw byte-buffer where it's used
* @tparam sizeVar C++ expression wrapped in DNA_COUNT macro to determine number of bytes for buffer
*/
template <size_t sizeVar>
using Buffer = std::unique_ptr<atUint8[]>;
/**
* @brief Template type wrapping std::string and signaling atdna to read string data where it's used
* @tparam sizeVar C++ expression wrapped in DNA_COUNT macro to determine number of characters for string
* -1 literal indicates null-terminated string
*/
template <atInt32 sizeVar = -1>
using String = std::string;
/**
* @brief Template type wrapping std::wstring and signaling atdna to read wstring data where it's used
* @tparam sizeVar C++ expression wrapped in DNA_COUNT macro to determine number of characters for wstring
* -1 literal indicates null-terminated wstring
*/
template <atInt32 sizeVar = -1, Endian VE = DNAE>
using WString = std::wstring;
/**
* @brief Meta Template signaling atdna to insert a stream seek where it's used
* @tparam offset C++ expression wrapped in DNA_COUNT macro to determine number of bytes to seek
* @tparam direction SeekOrigin to seek relative to
*/
template <off_t offset, SeekOrigin direction>
struct Seek {};
/**
* @brief Meta Template signaling atdna to insert an aligning stream seek where it's used
* @tparam align Number of bytes to align to
*/
template <size_t align>
struct Align {};
/**
* @brief Meta Template preventing atdna from emitting read/write implementations
*/
struct Delete {};
/* Bring fundamental operations into DNA subclasses for easier per-op overrides */
using Read = athena::io::Read<PropType::None>;
using Write = athena::io::Write<PropType::None>;
using BinarySize = athena::io::BinarySize<PropType::None>;
using PropCount = athena::io::PropCount<PropType::None>;
using ReadYaml = athena::io::ReadYaml<PropType::None>;
using WriteYaml = athena::io::WriteYaml<PropType::None>;
};
/**
* @brief Virtual DNA wrapper for subclasses that utilize virtual method calls
* @tparam DNAE Default-endianness for contained DNA values
*
* Typically, static template-based DNA resolution is sufficient; however, formats
* with a tree of variously-typed data structures would benefit from having a vtable.
*
* Note: It's not recommended to implement these directly. Instead, use the AT_DECL_DNA or
* AT_DECL_EXPLCIT_DNA macro in the subclasses. Specializing the Enumerate method
* is the proper way to override individual I/O operations. Owners of the virtualized
* subclass will typically use a unique_ptr to reference the data; specializing their own
* Enumerate methods as such:
*
* template <> void MySubclass::Enumerate<Read>(typename Read::StreamT& r)
* { (Do stuff with `r`) }
*/
template <Endian DNAE>
struct DNAV : DNA<DNAE> {
virtual ~DNAV() = default;
virtual void read(athena::io::IStreamReader& r) = 0;
virtual void write(athena::io::IStreamWriter& w) const = 0;
virtual void binarySize(size_t& s) const = 0;
virtual std::string_view DNATypeV() const = 0;
};
template <Endian DNAE>
struct DNAVYaml : DNAV<DNAE> {
virtual ~DNAVYaml() = default;
void read(athena::io::IStreamReader& r) override = 0;
void write(athena::io::IStreamWriter& w) const override = 0;
void binarySize(size_t& s) const override = 0;
virtual void read(athena::io::YAMLDocReader& r) = 0;
virtual void write(athena::io::YAMLDocWriter& w) const = 0;
};
/** Macro to supply count variable to atdna and mute it for other compilers */
#ifdef __clang__
#define AT_DNA_COUNT(...) sizeof(__VA_ARGS__)
#else
#define AT_DNA_COUNT(...) 0
#endif
} // namespace athena::io