/* -*- tab-width: 4; -*- */ /* vi: set sw=2 ts=4 expandtab: */ /* * Copyright 2021 Paolo Jovon. * SPDX-License-Identifier: Apache-2.0 */ /** * @internal * @file * @~English * * @brief A ktxStream that wraps a C++ std::streambuf. * * Modified from Paolo's original to make it a template so that the reference to std::streambuf can * be passed either as a `unique_ptr` or a regular pointer. As our use is to pass the streambuf from * an istream, potentially that from std::cin, or an istringstream that is not being not allocated by `new` * we can't use a `unique_ptr`. i.e. can't attempt to delete the stringbuf when done. Also the * destructor now destructs the stream, if not already destructed. * * @author Paolo Jovon * @author Mark Callow */ #include #include #include static std::ostream cnull(0); static std::ostream& logstream = cnull; //static std::ostream logstream = std::cerr; /// @brief Template for a ktxStream that wraps a C++ std::streambuf. /// /// The template supports 2 ways of referencing the underlying std::streambuf: /// as a @c unique_ptr or as a regular pointer. For the former case, the /// @c unique_ptr std::streambuf and StreambufStream should be created /// like this: /// @code /// // Can use stringbuf or any class derived from it. /// auto filebuf = std::make_unique(); /// StreambufStream>ktxStream( /// std::move(filebuf), /// ios::in); /// @endcode /// /// template class StreambufStream { // Doubt this will ever get triggered static_assert(sizeof(char) == sizeof(uint8_t), "Chars are != 1 byte in this platform"); public: StreambufStream(T streambuf, std::ios::openmode seek_mode = std::ios::in | std::ios::out) : _streambuf{std::move(streambuf)} , _seek_mode{seek_mode} , _stream{std::make_unique()} , _destructed{false} { initialize_stream(); } StreambufStream(const StreambufStream&) = delete; StreambufStream &operator=(const StreambufStream&) = delete; StreambufStream(StreambufStream&&) = delete; StreambufStream &operator=(StreambufStream&&) = delete; virtual ~StreambufStream() { if (!_destructed) stream()->destruct(stream()); } inline ktxStream* stream() const { return _stream.get(); } std::streambuf* streambuf() const; inline std::ios::openmode seek_mode() const { return _seek_mode; } inline void seek_mode(std::ios::openmode newmode) { _seek_mode = newmode; } inline bool destructed() const { return _destructed; } protected: void initialize_stream() { _stream->type = eStreamTypeCustom; _stream->closeOnDestruct = false; auto& custom_ptr = _stream->data.custom_ptr; custom_ptr.address = this; custom_ptr.allocatorAddress = nullptr; // N/A custom_ptr.size = 0; // N/A _stream->read = read; _stream->skip = skip; _stream->write = write; _stream->getpos = getpos; _stream->setpos = setpos; _stream->getsize = getsize; _stream->destruct = destruct; } // C++ streambuf overrides // ktxStream vtable implementations inline static StreambufStream* parent(ktxStream *str) { return reinterpret_cast(str->data.custom_ptr.address); } static KTX_error_code read(ktxStream* str, void* dst, ktx_size_t count) { auto self = parent(str); if (count == 0) { return KTX_SUCCESS; } logstream << "\t read: " << count << 'B' << std::endl; const auto stdcount = std::streamsize(count); const std::streamsize nread = self->_streambuf->sgetn(reinterpret_cast(dst), stdcount); return (nread == stdcount) ? KTX_SUCCESS : KTX_FILE_UNEXPECTED_EOF; } static KTX_error_code skip(ktxStream* str, ktx_size_t count) { auto self = parent(str); if (count == 0) { return KTX_SUCCESS; } logstream << "\t skip: " << count << 'B' << std::endl; const std::streampos curpos = self->_streambuf->pubseekoff(0, std::ios::cur, self->_seek_mode); const std::streampos newpos = self->_streambuf->pubseekoff(std::streamoff(count), std::ios::cur, self->_seek_mode); return (curpos > newpos) ? KTX_SUCCESS : KTX_FILE_SEEK_ERROR; } static KTX_error_code write(ktxStream* str, const void* src, ktx_size_t size, ktx_size_t count) { auto self = parent(str); if (size == 0 || count == 0) { return KTX_SUCCESS; } logstream << "\t write: " << count << "*" << size << "B" << std::endl; const auto ntotal = std::streamsize(size * count); const std::streamsize nput = self->_streambuf->sputn(reinterpret_cast(src), ntotal); return (nput == ntotal) ? KTX_SUCCESS : KTX_FILE_WRITE_ERROR; } static KTX_error_code getpos(ktxStream* str, ktx_off_t *offset) { auto self = parent(str); *offset = ktx_off_t(self->_streambuf->pubseekoff(0, std::ios::cur, self->_seek_mode)); logstream << "\tgetpos: " << *offset << std::endl; return KTX_SUCCESS; } static KTX_error_code setpos(ktxStream* str, ktx_off_t offset) { auto self = parent(str); const auto newpos = std::streamoff(offset); const std::streampos setpos = self->_streambuf->pubseekoff(newpos, std::ios::beg, self->_seek_mode); logstream << "\tsetpos: " << offset << std::endl; return (setpos == newpos) ? KTX_SUCCESS : KTX_FILE_SEEK_ERROR; } static KTX_error_code getsize(ktxStream* str, ktx_size_t* size) { auto self = parent(str); const std::streampos oldpos = self->_streambuf->pubseekoff(0, std::ios::cur, self->_seek_mode); *size = ktx_size_t(self->_streambuf->pubseekoff(0, std::ios::end)); const std::streampos newpos = self->_streambuf->pubseekoff(oldpos, std::ios::beg, self->_seek_mode); logstream << "\t size: " << *size << 'B' << std::endl; return (oldpos == newpos) ? KTX_SUCCESS : KTX_FILE_SEEK_ERROR; } static void destruct(ktxStream* str) { auto self = parent(str); self->_destructed = true; } T _streambuf; std::ios::openmode _seek_mode; std::unique_ptr _stream; // ktxTexture?_CreateFromStream destructs the ktxStream when finished, if // KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT was passed. This variable tracks // if the ktxStream's destructor has been called. bool _destructed; }; // I have not yet found a way to do this inside the template definition. // However `inline` should prevent any multiple definition errors. template<> inline std::streambuf* StreambufStream::streambuf() const { return _streambuf; } template<> inline std::streambuf* StreambufStream>::streambuf() const { return _streambuf.get(); }