244 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			244 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
 | 
						|
#pragma once
 | 
						|
 | 
						|
#if !defined(MIJIN_IO_STREAM_HPP_INCLUDED)
 | 
						|
#define MIJIN_IO_STREAM_HPP_INCLUDED 1
 | 
						|
 | 
						|
#include <cassert>
 | 
						|
#include <cstdint>
 | 
						|
#include <optional>
 | 
						|
#include <ranges>
 | 
						|
#include <span>
 | 
						|
#include <string>
 | 
						|
#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<std::uint8_t> buffer, bool partial = false, std::size_t* outBytesRead = nullptr) = 0;
 | 
						|
    virtual StreamError writeRaw(std::span<const std::uint8_t> 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<std::uint8_t*>(outData);
 | 
						|
        return readRaw(std::span(ptr, ptr + bytes), partial, outBytesRead);
 | 
						|
    }
 | 
						|
 | 
						|
    template<std::ranges::contiguous_range TRange>
 | 
						|
    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<TRange>);
 | 
						|
        return readRaw(&*range.begin(), bytes, partial, outBytesRead);
 | 
						|
    }
 | 
						|
 | 
						|
    inline StreamError writeRaw(const void* data, std::size_t bytes)
 | 
						|
    {
 | 
						|
        const std::uint8_t* ptr = static_cast<const std::uint8_t*>(data);
 | 
						|
        return writeRaw(std::span(ptr, ptr + bytes));
 | 
						|
    }
 | 
						|
 | 
						|
    template<std::ranges::contiguous_range TRange>
 | 
						|
    inline StreamError writeRaw(const TRange& range)
 | 
						|
    {
 | 
						|
        const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t<TRange>);
 | 
						|
        return writeRaw(&*range.begin(), bytes);
 | 
						|
    }
 | 
						|
 | 
						|
    template<typename T>
 | 
						|
    inline StreamError read(T& value)
 | 
						|
    {
 | 
						|
        return readRaw(&value, sizeof(T));
 | 
						|
    }
 | 
						|
 | 
						|
    template<typename T>
 | 
						|
    inline StreamError readSpan(T& values)
 | 
						|
    {
 | 
						|
        auto asSpan = std::span(values);
 | 
						|
        return readRaw(asSpan.data(), asSpan.size_bytes());
 | 
						|
    }
 | 
						|
 | 
						|
    template<typename TItBegin, typename TItEnd>
 | 
						|
    inline StreamError readSpan(TItBegin&& begin, TItEnd&& end)
 | 
						|
    {
 | 
						|
        auto asSpan = std::span(std::forward<TItBegin>(begin), std::forward<TItEnd>(end));
 | 
						|
        return readRaw(asSpan.data(), asSpan.size_bytes());
 | 
						|
    }
 | 
						|
 | 
						|
    template<typename T>
 | 
						|
    inline StreamError write(const T& value)
 | 
						|
    {
 | 
						|
        return writeRaw(&value, sizeof(T));
 | 
						|
    }
 | 
						|
 | 
						|
    template<typename T>
 | 
						|
    inline StreamError writeSpan(const T& values)
 | 
						|
    {
 | 
						|
        auto asSpan = std::span(values);
 | 
						|
        return writeRaw(asSpan.data(), asSpan.size_bytes());
 | 
						|
    }
 | 
						|
 | 
						|
    template<typename TItBegin, typename TItEnd>
 | 
						|
    inline StreamError writeSpan(TItBegin&& begin, TItEnd&& end)
 | 
						|
    {
 | 
						|
        return writeSpan(std::span(std::forward<TItBegin>(begin), std::forward<TItEnd>(end)));
 | 
						|
    }
 | 
						|
 | 
						|
    StreamError readString(std::string& outString);
 | 
						|
    StreamError writeString(std::string_view str);
 | 
						|
 | 
						|
    StreamError getTotalLength(std::size_t& outLength);
 | 
						|
    StreamError readRest(TypelessBuffer& outBuffer);
 | 
						|
 | 
						|
    template<typename TChar = char>
 | 
						|
    StreamError readAsString(std::basic_string<TChar>& 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<std::uint8_t> buffer, bool partial = false, std::size_t* outBytesRead = nullptr) override;
 | 
						|
    StreamError writeRaw(std::span<const std::uint8_t> 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<std::uint8_t> data_;
 | 
						|
    std::size_t pos_ = 0;
 | 
						|
    bool canWrite_ = false;
 | 
						|
public:
 | 
						|
    void openRW(std::span<std::uint8_t> data);
 | 
						|
    void openRO(std::span<const std::uint8_t> 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<std::uint8_t> buffer, bool partial = false, std::size_t* outBytesRead = nullptr) override;
 | 
						|
    StreamError writeRaw(std::span<const std::uint8_t> 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<typename TChar>
 | 
						|
StreamError Stream::readAsString(std::basic_string<TChar>& 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<TChar, CHUNK_SIZE> 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)
 |