#pragma once #if !defined(MIJIN_IO_STREAM_HPP_INCLUDED) #define MIJIN_IO_STREAM_HPP_INCLUDED 1 #include #include #include #include #include #include #include "../container/typeless_buffer.hpp" namespace mijin { // // public defines // // // public constants // // // public types // enum class SeekMode { ABSOLUTE, RELATIVE, RELATIVE_TO_END }; struct StreamFeatures { bool read : 1 = false; bool write : 1 = false; bool tell : 1 = false; bool seek : 1 = false; }; enum class FileOpenMode { READ, WRITE, APPEND, READ_WRITE }; enum class [[nodiscard]] StreamError { SUCCESS, IO_ERROR, NOT_SUPPORTED, UNKNOWN_ERROR }; class Stream { public: virtual ~Stream() = default; public: virtual StreamError readRaw(std::span buffer, bool partial = false, std::size_t* outBytesRead = nullptr) = 0; virtual StreamError writeRaw(std::span buffer) = 0; virtual std::size_t tell() = 0; virtual StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) = 0; virtual void flush(); virtual bool isAtEnd() = 0; virtual StreamFeatures getFeatures() = 0; inline StreamError readRaw(void* outData, std::size_t bytes, bool partial = false, std::size_t* outBytesRead = nullptr) { std::uint8_t* ptr = static_cast(outData); return readRaw(std::span(ptr, ptr + bytes), partial, outBytesRead); } template inline StreamError readRaw(TRange& range, bool partial = false, std::size_t* outBytesRead = nullptr) { const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t); return readRaw(&*range.begin(), bytes, partial, outBytesRead); } inline StreamError writeRaw(const void* data, std::size_t bytes) { const std::uint8_t* ptr = static_cast(data); return writeRaw(std::span(ptr, ptr + bytes)); } template inline StreamError writeRaw(const TRange& range) { const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t); return writeRaw(&*range.begin(), bytes); } template inline StreamError read(T& value) { return readRaw(&value, sizeof(T)); } template inline StreamError readSpan(T& values) { auto asSpan = std::span(values); return readRaw(asSpan.data(), asSpan.size_bytes()); } template inline StreamError readSpan(TItBegin&& begin, TItEnd&& end) { auto asSpan = std::span(std::forward(begin), std::forward(end)); return readRaw(asSpan.data(), asSpan.size_bytes()); } template inline StreamError write(const T& value) { return writeRaw(&value, sizeof(T)); } template inline StreamError writeSpan(const T& values) { auto asSpan = std::span(values); return writeRaw(asSpan.data(), asSpan.size_bytes()); } template inline StreamError writeSpan(TItBegin&& begin, TItEnd&& end) { return writeSpan(std::span(std::forward(begin), std::forward(end))); } StreamError readString(std::string& outString); StreamError writeString(std::string_view str); StreamError getTotalLength(std::size_t& outLength); StreamError readRest(TypelessBuffer& outBuffer); template StreamError readAsString(std::basic_string& outString); }; class FileStream : public Stream { private: std::FILE* handle = nullptr; // TODO: wrap in gsl::owner<> FileOpenMode mode; std::size_t length = 0; public: ~FileStream() override; StreamError open(const char* path, FileOpenMode mode_); inline StreamError open(const std::string& path, FileOpenMode mode_) { return open(path.c_str(), mode_); } void close(); [[nodiscard]] inline bool isOpen() const { return handle != nullptr; } // Stream overrides StreamError readRaw(std::span buffer, bool partial = false, std::size_t* outBytesRead = nullptr) override; StreamError writeRaw(std::span buffer) override; std::size_t tell() override; StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) override; void flush() override; bool isAtEnd() override; StreamFeatures getFeatures() override; }; class MemoryStream : public Stream { private: std::span data_; std::size_t pos_ = 0; bool canWrite_ = false; public: void openRW(std::span data); void openRO(std::span data); void close(); [[nodiscard]] inline bool isOpen() const { return data_.data() != nullptr; } [[nodiscard]] inline std::size_t availableBytes() const { assert(isOpen()); return data_.size() - pos_; } // Stream overrides StreamError readRaw(std::span buffer, bool partial = false, std::size_t* outBytesRead = nullptr) override; StreamError writeRaw(std::span buffer) override; std::size_t tell() override; StreamError seek(std::intptr_t pos, SeekMode seekMode = SeekMode::ABSOLUTE) override; bool isAtEnd() override; StreamFeatures getFeatures() override; }; // // public functions // template StreamError Stream::readAsString(std::basic_string& outString) { static_assert(sizeof(TChar) == 1, "Can only read to 8-bit character types (char, unsigned char or char8_t"); // first try to allocate everything at once std::size_t length = 0; if (StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS) { MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?"); length -= tell(); outString.resize(length); if (StreamError error = readRaw(outString.data(), length); error != StreamError::SUCCESS) { return error; } return StreamError::SUCCESS; } // could not determine the size, read chunk-wise static constexpr std::size_t CHUNK_SIZE = 4096; std::array chunk; outString.clear(); while (!isAtEnd()) { std::size_t bytesRead = 0; if (StreamError error = readRaw(chunk, true, &bytesRead); error != StreamError::SUCCESS) { return error; } outString.append(chunk.data(), chunk.data() + bytesRead); } return StreamError::SUCCESS; } } // namespace mijin #endif // !defined(MIJIN_IO_STREAM_HPP_INCLUDED)