intial commit
This commit is contained in:
308
source/mijin/io/stream.cpp
Normal file
308
source/mijin/io/stream.cpp
Normal file
@@ -0,0 +1,308 @@
|
||||
|
||||
#include "./stream.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// internal defines
|
||||
//
|
||||
|
||||
//
|
||||
// internal constants
|
||||
//
|
||||
|
||||
//
|
||||
// internal types
|
||||
//
|
||||
|
||||
//
|
||||
// internal variables
|
||||
//
|
||||
|
||||
//
|
||||
// internal functions
|
||||
//
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
||||
void Stream::flush() {}
|
||||
|
||||
StreamError Stream::readString(std::string& outString)
|
||||
{
|
||||
std::uint32_t length; // NOLINT(cppcoreguidelines-init-variables)
|
||||
StreamError error = read(length);
|
||||
if (error != StreamError::SUCCESS) {
|
||||
return error;
|
||||
}
|
||||
|
||||
std::string result;
|
||||
result.resize(length);
|
||||
error = readSpan(result.begin(), result.end());
|
||||
if (error != StreamError::SUCCESS) {
|
||||
return error;
|
||||
}
|
||||
outString = std::move(result);
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
StreamError Stream::writeString(std::string_view str)
|
||||
{
|
||||
assert(str.length() <= std::numeric_limits<std::uint32_t>::max());
|
||||
const std::uint32_t length = static_cast<std::uint32_t>(str.length());
|
||||
StreamError error = write(length);
|
||||
if (error != StreamError::SUCCESS) {
|
||||
return error;
|
||||
}
|
||||
return writeSpan(str.begin(), str.end());
|
||||
}
|
||||
|
||||
FileStream::~FileStream()
|
||||
{
|
||||
if (handle) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
StreamError FileStream::open(const char* path, FileOpenMode mode_)
|
||||
{
|
||||
mode = mode_;
|
||||
|
||||
const char* modeStr; // NOLINT(cppcoreguidelines-init-variables)
|
||||
switch (mode_)
|
||||
{
|
||||
case FileOpenMode::READ:
|
||||
modeStr = "rb";
|
||||
break;
|
||||
case FileOpenMode::WRITE:
|
||||
modeStr = "wb";
|
||||
break;
|
||||
case FileOpenMode::APPEND:
|
||||
modeStr = "ab";
|
||||
break;
|
||||
case FileOpenMode::READ_WRITE:
|
||||
modeStr = "r+b";
|
||||
break;
|
||||
}
|
||||
handle = std::fopen(path, modeStr); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
if (!handle && mode_ == FileOpenMode::READ_WRITE) {
|
||||
handle = std::fopen(path, "w+b"); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
if (!handle) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
|
||||
int result = std::fseek(handle, 0, SEEK_END);
|
||||
assert(result == 0);
|
||||
length = std::ftell(handle);
|
||||
result = std::fseek(handle, 0, SEEK_SET);
|
||||
assert(result == 0);
|
||||
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
void FileStream::close()
|
||||
{
|
||||
assert(handle);
|
||||
const int result = std::fclose(handle); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
assert(result == 0);
|
||||
}
|
||||
|
||||
StreamError FileStream::readRaw(std::span<std::uint8_t> buffer, bool partial, std::size_t* outBytesRead)
|
||||
{
|
||||
assert(handle);
|
||||
assert(mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE);
|
||||
|
||||
const std::size_t readBytes = std::fread(buffer.data(), 1, buffer.size(), handle);
|
||||
if (std::ferror(handle)) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
if (!partial && readBytes < buffer.size()) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
if (outBytesRead != nullptr) {
|
||||
*outBytesRead = readBytes;
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
StreamError FileStream::writeRaw(std::span<const std::uint8_t> buffer)
|
||||
{
|
||||
assert(handle);
|
||||
assert(mode == FileOpenMode::WRITE || mode == FileOpenMode::APPEND || mode == FileOpenMode::READ_WRITE);
|
||||
|
||||
const std::size_t written = std::fwrite(buffer.data(), 1, buffer.size(), handle);
|
||||
if (written != buffer.size() || std::ferror(handle)) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
|
||||
length = std::max<std::size_t>(length, std::ftell(handle));
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
std::size_t FileStream::tell()
|
||||
{
|
||||
assert(handle);
|
||||
|
||||
return std::ftell(handle);
|
||||
}
|
||||
|
||||
StreamError FileStream::seek(std::intptr_t pos, SeekMode seekMode)
|
||||
{
|
||||
assert(handle);
|
||||
|
||||
int origin; // NOLINT(cppcoreguidelines-init-variables)
|
||||
switch (seekMode)
|
||||
{
|
||||
case SeekMode::ABSOLUTE:
|
||||
origin = SEEK_SET;
|
||||
break;
|
||||
case SeekMode::RELATIVE:
|
||||
origin = SEEK_CUR;
|
||||
break;
|
||||
case SeekMode::RELATIVE_TO_END:
|
||||
origin = SEEK_END;
|
||||
break;
|
||||
}
|
||||
const int result = std::fseek(handle, static_cast<long>(pos), origin);
|
||||
if (result != 0 || std::ferror(handle)) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
void FileStream::flush()
|
||||
{
|
||||
const int result = std::fflush(handle);
|
||||
assert(result == 0);
|
||||
}
|
||||
|
||||
bool FileStream::isAtEnd()
|
||||
{
|
||||
assert(handle);
|
||||
|
||||
(void) std::fgetc(handle);
|
||||
if (std::feof(handle)) {
|
||||
return true;
|
||||
}
|
||||
const int result = std::fseek(handle, -1, SEEK_CUR);
|
||||
assert(result == 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
StreamFeatures FileStream::getFeatures()
|
||||
{
|
||||
if (handle)
|
||||
{
|
||||
return {
|
||||
.read = (mode == FileOpenMode::READ),
|
||||
.write = (mode == FileOpenMode::WRITE || mode == FileOpenMode::APPEND || mode == FileOpenMode::READ_WRITE),
|
||||
.tell = true,
|
||||
.seek = true
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void MemoryStream::openRW(std::span<std::uint8_t> data)
|
||||
{
|
||||
assert(!isOpen());
|
||||
data_ = data;
|
||||
pos_ = 0;
|
||||
canWrite_ = true;
|
||||
}
|
||||
|
||||
void MemoryStream::openRO(std::span<const std::uint8_t> data)
|
||||
{
|
||||
assert(!isOpen());
|
||||
data_ = std::span<std::uint8_t>(const_cast<std::uint8_t*>(data.data()), data.size()); // NOLINT(cppcoreguidelines-pro-type-const-cast) we'll be fine
|
||||
pos_ = 0;
|
||||
canWrite_ = false;
|
||||
}
|
||||
|
||||
void MemoryStream::close()
|
||||
{
|
||||
assert(isOpen());
|
||||
data_ = {};
|
||||
}
|
||||
|
||||
StreamError MemoryStream::readRaw(std::span<std::uint8_t> buffer, bool partial, std::size_t* outBytesRead)
|
||||
{
|
||||
assert(isOpen());
|
||||
if (!partial && availableBytes() < buffer.size()) {
|
||||
return StreamError::IO_ERROR; // TODO: need more errors?
|
||||
}
|
||||
const std::size_t numBytes = std::min(buffer.size(), availableBytes());
|
||||
std::copy_n(data_.begin() + static_cast<long>(pos_), numBytes, buffer.begin());
|
||||
if (outBytesRead) {
|
||||
*outBytesRead = numBytes;
|
||||
}
|
||||
pos_ += numBytes;
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
StreamError MemoryStream::writeRaw(std::span<const std::uint8_t> buffer)
|
||||
{
|
||||
assert(isOpen());
|
||||
assert(canWrite_);
|
||||
|
||||
if (availableBytes() < buffer.size()) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
std::copy(buffer.begin(), buffer.end(), data_.begin() + static_cast<long>(pos_));
|
||||
pos_ += buffer.size();
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
std::size_t MemoryStream::tell()
|
||||
{
|
||||
assert(isOpen());
|
||||
return pos_;
|
||||
}
|
||||
|
||||
StreamError MemoryStream::seek(std::intptr_t pos, SeekMode seekMode)
|
||||
{
|
||||
assert(isOpen());
|
||||
std::intptr_t newPos = -1;
|
||||
switch (seekMode)
|
||||
{
|
||||
case SeekMode::ABSOLUTE:
|
||||
newPos = pos;
|
||||
break;
|
||||
case SeekMode::RELATIVE:
|
||||
newPos = static_cast<std::intptr_t>(pos_) + pos;
|
||||
break;
|
||||
case SeekMode::RELATIVE_TO_END:
|
||||
newPos = static_cast<std::intptr_t>(data_.size()) + pos;
|
||||
break;
|
||||
}
|
||||
if (newPos < 0 || newPos > data_.size()) {
|
||||
return StreamError::IO_ERROR;
|
||||
}
|
||||
pos_ = newPos;
|
||||
return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
bool MemoryStream::isAtEnd()
|
||||
{
|
||||
assert(isOpen());
|
||||
return pos_ == data_.size();
|
||||
}
|
||||
|
||||
StreamFeatures MemoryStream::getFeatures()
|
||||
{
|
||||
return {
|
||||
.read = true,
|
||||
.write = canWrite_,
|
||||
.tell = true,
|
||||
.seek = true
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace mijin
|
||||
181
source/mijin/io/stream.hpp
Normal file
181
source/mijin/io/stream.hpp
Normal file
@@ -0,0 +1,181 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_IO_STREAM_HPP_INCLUDED)
|
||||
#define MIJIN_IO_STREAM_HPP_INCLUDED 1
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
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 StreamError
|
||||
{
|
||||
SUCCESS,
|
||||
IO_ERROR,
|
||||
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);
|
||||
}
|
||||
|
||||
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<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);
|
||||
};
|
||||
|
||||
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_);
|
||||
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
|
||||
//
|
||||
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_IO_STREAM_HPP_INCLUDED)
|
||||
Reference in New Issue
Block a user