From dffff4b5ad0424857edb649fa7480d1601806570 Mon Sep 17 00:00:00 2001
From: Patrick Wuttke
Date: Mon, 9 Feb 2026 10:17:29 +0100
Subject: [PATCH] Fixed FileStream move semantics.
---
source/mijin/io/stream.cpp | 92 +++++++++++++++++++++++---------------
source/mijin/io/stream.hpp | 51 ++++++++++++++++-----
2 files changed, 96 insertions(+), 47 deletions(-)
diff --git a/source/mijin/io/stream.cpp b/source/mijin/io/stream.cpp
index 57148db..f4886b6 100644
--- a/source/mijin/io/stream.cpp
+++ b/source/mijin/io/stream.cpp
@@ -313,16 +313,36 @@ StreamError Stream::copyTo(Stream& other)
return StreamError::SUCCESS;
}
-FileStream::~FileStream()
+FileStream::FileStream(FileStream&& other) MIJIN_NOEXCEPT
+ : handle_(std::exchange(other.handle_, nullptr)), mode_(other.mode_), length_(other.length_)
{
- if (handle) {
+
+}
+
+FileStream::~FileStream() noexcept
+{
+ if (handle_) {
close();
}
}
-StreamError FileStream::open(const char* path, FileOpenMode mode_)
+FileStream& FileStream::operator=(FileStream&& other) MIJIN_NOEXCEPT
{
- mode = mode_;
+ if (this != &other)
+ {
+ if (handle_) {
+ close();
+ }
+ handle_ = std::exchange(other.handle_, nullptr);
+ mode_ = other.mode_;
+ length_ = other.length_;
+ }
+ return *this;
+}
+
+StreamError FileStream::open(const char* path, FileOpenMode mode)
+{
+ mode_ = mode;
const char* modeStr; // NOLINT(cppcoreguidelines-init-variables)
switch (mode_)
@@ -343,18 +363,18 @@ StreamError FileStream::open(const char* path, FileOpenMode mode_)
MIJIN_FATAL("Invalid value for mode.");
return StreamError::UNKNOWN_ERROR;
}
- 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)
+ 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) {
+ if (!handle_) {
return StreamError::IO_ERROR;
}
- int result = std::fseek(handle, 0, SEEK_END);
+ int result = std::fseek(handle_, 0, SEEK_END);
MIJIN_ASSERT(result == 0, "fseek failed.");
- length = std::ftell(handle);
- result = std::fseek(handle, 0, SEEK_SET);
+ length_ = std::ftell(handle_);
+ result = std::fseek(handle_, 0, SEEK_SET);
MIJIN_ASSERT(result == 0, "fseek failed.");
return StreamError::SUCCESS;
@@ -362,19 +382,19 @@ StreamError FileStream::open(const char* path, FileOpenMode mode_)
void FileStream::close()
{
- MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
- [[maybe_unused]] const int result = std::fclose(handle); // NOLINT(cppcoreguidelines-owning-memory)
+ MIJIN_ASSERT(handle_ != nullptr, "FileStream is not open.");
+ [[maybe_unused]] const int result = std::fclose(handle_); // NOLINT(cppcoreguidelines-owning-memory)
MIJIN_ASSERT(result == 0, "fclose failed.");
- handle = nullptr;
+ handle_ = nullptr;
}
StreamError FileStream::readRaw(std::span buffer, const ReadOptions& options, std::size_t* outBytesRead)
{
- MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
- MIJIN_ASSERT(mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE, "Cannot read from this stream");
+ MIJIN_ASSERT(handle_ != nullptr, "FileStream is not open.");
+ MIJIN_ASSERT(mode_ == FileOpenMode::READ || mode_ == FileOpenMode::READ_WRITE, "Cannot read from this stream");
- const std::size_t readBytes = std::fread(buffer.data(), 1, buffer.size(), handle);
- if (std::ferror(handle)) {
+ const std::size_t readBytes = std::fread(buffer.data(), 1, buffer.size(), handle_);
+ if (std::ferror(handle_)) {
return StreamError::IO_ERROR;
}
if (!options.partial && readBytes < buffer.size()) {
@@ -395,28 +415,28 @@ StreamError FileStream::readRaw(std::span buffer, const ReadOption
StreamError FileStream::writeRaw(std::span buffer)
{
- MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
- MIJIN_ASSERT(mode == FileOpenMode::WRITE || mode == FileOpenMode::READ_WRITE || mode == FileOpenMode::APPEND,
+ MIJIN_ASSERT(handle_ != nullptr, "FileStream is not open.");
+ MIJIN_ASSERT(mode_ == FileOpenMode::WRITE || mode_ == FileOpenMode::READ_WRITE || mode_ == FileOpenMode::APPEND,
"Cannot write to this stream");
- const std::size_t written = std::fwrite(buffer.data(), 1, buffer.size(), handle);
- if (written != buffer.size() || std::ferror(handle)) {
+ 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(length, std::ftell(handle));
+ length_ = std::max(length_, std::ftell(handle_));
return StreamError::SUCCESS;
}
std::size_t FileStream::tell()
{
- MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
- return std::ftell(handle);
+ MIJIN_ASSERT(handle_ != nullptr, "FileStream is not open.");
+ return std::ftell(handle_);
}
StreamError FileStream::seek(std::intptr_t pos, SeekMode seekMode)
{
- MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
+ MIJIN_ASSERT(handle_ != nullptr, "FileStream is not open.");
int origin; // NOLINT(cppcoreguidelines-init-variables)
switch (seekMode)
{
@@ -433,8 +453,8 @@ StreamError FileStream::seek(std::intptr_t pos, SeekMode seekMode)
MIJIN_ERROR("Invalid value passed as seekMode!");
return StreamError::UNKNOWN_ERROR;
}
- const int result = std::fseek(handle, static_cast(pos), origin);
- if (result != 0 || std::ferror(handle)) {
+ const int result = std::fseek(handle_, static_cast(pos), origin);
+ if (result != 0 || std::ferror(handle_)) {
return StreamError::IO_ERROR;
}
return StreamError::SUCCESS;
@@ -442,30 +462,30 @@ StreamError FileStream::seek(std::intptr_t pos, SeekMode seekMode)
void FileStream::flush()
{
- [[maybe_unused]] const int result = std::fflush(handle);
+ [[maybe_unused]] const int result = std::fflush(handle_);
MIJIN_ASSERT(result == 0, "fflush failed.");
}
bool FileStream::isAtEnd()
{
- MIJIN_ASSERT(handle != nullptr, "FileStream is not open.");
+ MIJIN_ASSERT(handle_ != nullptr, "FileStream is not open.");
- (void) std::fgetc(handle);
- if (std::feof(handle)) {
+ (void) std::fgetc(handle_);
+ if (std::feof(handle_)) {
return true;
}
- [[maybe_unused]] const int result = std::fseek(handle, -1, SEEK_CUR);
+ [[maybe_unused]] const int result = std::fseek(handle_, -1, SEEK_CUR);
MIJIN_ASSERT(result == 0, "fseek failed.");
return false;
}
StreamFeatures FileStream::getFeatures()
{
- if (handle)
+ if (handle_)
{
return {
- .read = (mode == FileOpenMode::READ || mode == FileOpenMode::READ_WRITE),
- .write = (mode == FileOpenMode::WRITE || mode == FileOpenMode::APPEND || mode == FileOpenMode::READ_WRITE),
+ .read = (mode_ == FileOpenMode::READ || mode_ == FileOpenMode::READ_WRITE),
+ .write = (mode_ == FileOpenMode::WRITE || mode_ == FileOpenMode::APPEND || mode_ == FileOpenMode::READ_WRITE),
.tell = true,
.seek = true,
.readOptions = {
diff --git a/source/mijin/io/stream.hpp b/source/mijin/io/stream.hpp
index 8893ba0..072905a 100644
--- a/source/mijin/io/stream.hpp
+++ b/source/mijin/io/stream.hpp
@@ -306,18 +306,24 @@ public:
class FileStream : public Stream
{
private:
- std::FILE* handle = nullptr; // TODO: wrap in gsl::owner<>
- FileOpenMode mode;
- std::size_t length = 0;
+ std::FILE* handle_ = nullptr; // TODO: wrap in gsl::owner<>
+ FileOpenMode mode_;
+ std::size_t length_ = 0;
public:
- ~FileStream() override;
+ FileStream() = default;
+ FileStream(const FileStream&) = delete;
+ FileStream(FileStream&& other) MIJIN_NOEXCEPT;
+ ~FileStream() noexcept override;
- StreamError open(const char* path, FileOpenMode mode_);
- inline StreamError open(const std::string& path, FileOpenMode mode_) {
- return open(path.c_str(), mode_);
+ FileStream& operator=(const FileStream&) = delete;
+ FileStream& operator=(FileStream&& other) MIJIN_NOEXCEPT;
+
+ 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; }
+ [[nodiscard]] inline bool isOpen() const { return handle_ != nullptr; }
// Stream overrides
StreamError readRaw(std::span buffer, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr) override;
@@ -337,22 +343,45 @@ private:
bool canWrite_ = false;
public:
void openRW(std::span data);
+ template
+ void openRW(const MixinMemoryView& memoryView) {
+ openRW(memoryView.makeSpan());
+ }
template
void openRO(std::span data) {
openROImpl(data.data(), data.size_bytes());
}
template
- inline void openRO(std::basic_string_view stringView) {
+ void openRO(std::basic_string_view stringView) {
openROImpl(stringView.data(), stringView.size() * sizeof(TChar));
}
+ template
+ void openRO(const MixinMemoryView& memoryView) {
+ openRO(memoryView.makeSpan());
+ }
void close();
- [[nodiscard]] inline bool isOpen() const { return data_.data() != nullptr; }
- [[nodiscard]] inline std::size_t availableBytes() const
+ [[nodiscard]] bool isOpen() const { return data_.data() != nullptr; }
+ [[nodiscard]] std::size_t availableBytes() const
{
MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
return data_.size() - pos_;
}
+ [[nodiscard]]
+ std::span data() const
+ {
+ MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
+ MIJIN_ASSERT(canWrite_, "MemoryStream is read-only.");
+ return data_;
+ }
+
+ [[nodiscard]]
+ std::span constData() const
+ {
+ MIJIN_ASSERT(isOpen(), "MemoryStream is not open.");
+ return data_;
+ }
+
// Stream overrides
StreamError readRaw(std::span buffer, const ReadOptions& options = {}, std::size_t* outBytesRead = nullptr) override;
StreamError writeRaw(std::span buffer) override;