Created
December 2, 2023 13:14
-
-
Save W4RH4WK/adaffc5d73ec1ee012b49f1ec1d27c06 to your computer and use it in GitHub Desktop.
binary io
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <algorithm> | |
#include <cassert> | |
#include <cstddef> | |
#include <cstdint> | |
#include <cstdio> | |
#include <cstring> | |
#include <filesystem> | |
#include <string> | |
#include <utility> | |
#include <vector> | |
namespace ph3::io { | |
namespace detail { | |
template <typename Reader> | |
class BinaryReaderBase { | |
public: | |
template <typename T> | |
bool readValue(T& outValue) | |
{ | |
return read(&outValue, sizeof(T)) == sizeof(T); | |
} | |
template <typename T> | |
std::size_t readVector(std::vector<T>& outContainer) | |
{ | |
return readVector(outContainer, outContainer.size()); | |
} | |
template <typename T> | |
std::size_t readVector(std::vector<T>& container, std::size_t length) | |
{ | |
container.resize(length); | |
auto size = length * sizeof(T); | |
return read(container.data(), size) / sizeof(T); | |
} | |
bool readString(std::string& outString, std::size_t length) | |
{ | |
outString.clear(); | |
outString.resize(length); | |
auto result = read(outString.data(), outString.size()) == length; | |
// Shrink to first terminator | |
outString.erase(std::find(outString.begin(), outString.end(), '\0'), outString.end()); | |
return result; | |
} | |
bool readCString(std::string& outString) | |
{ | |
outString.clear(); | |
bool result = true; | |
char c = 0; | |
while ((result = readValue(c)) && c != '\0') { | |
outString.push_back(c); | |
} | |
return result; | |
} | |
template <typename Writer> | |
std::size_t copyTo(Writer&& writer) | |
{ | |
std::vector<std::byte> buffer(4096); | |
return copyWithBuffer(*static_cast<Reader*>(this), writer, buffer.data(), buffer.size()); | |
} | |
template <typename Writer> | |
std::size_t copyTo(Writer&& writer, std::size_t size) | |
{ | |
std::vector<std::byte> buffer(4096); | |
return copyWithBuffer(*static_cast<Reader*>(this), writer, size, buffer.data(), buffer.size()); | |
} | |
private: | |
std::size_t read(void* outBuffer, std::size_t size) { return static_cast<Reader*>(this)->read(outBuffer, size); } | |
}; | |
template <typename Writer> | |
class BinaryWriterBase { | |
public: | |
template <typename T> | |
bool writeValue(const T& value) | |
{ | |
return write(&value, sizeof(T)) == sizeof(T); | |
} | |
template <typename T> | |
std::size_t writeVector(const std::vector<T>& container) | |
{ | |
return writeVector(container, container.size()); | |
} | |
template <typename T> | |
std::size_t writeVector(const std::vector<T>& container, std::size_t length) | |
{ | |
auto size = length * sizeof(T); | |
return write(container.data(), size) / sizeof(T); | |
} | |
// Write a fixed length string. Null-termination not guaranteed. | |
std::size_t writeString(std::string_view view, std::size_t length) | |
{ | |
auto bytesWritten = write(view.data(), std::min(view.size(), length)); | |
for (auto i = view.size(); i < length; ++i) { | |
bytesWritten += writeValue<char>('\0'); | |
} | |
return bytesWritten; | |
} | |
// Write null-terminated string. | |
std::size_t writeCString(std::string_view view) | |
{ | |
auto bytesWritten = write(view.data(), view.size()); | |
bytesWritten += writeValue<char>('\0'); | |
return bytesWritten; | |
} | |
template <typename Reader> | |
std::size_t copyFrom(Reader&& reader) | |
{ | |
std::vector<std::byte> buffer(4096); | |
return copyWithBuffer(reader, *static_cast<Writer*>(this), buffer.data(), buffer.size()); | |
} | |
template <typename Reader> | |
std::size_t copyFrom(Reader&& reader, std::size_t size) | |
{ | |
std::vector<std::byte> buffer(4096); | |
return copyWithBuffer(reader, *static_cast<Writer*>(this), size, buffer.data(), buffer.size()); | |
} | |
private: | |
std::size_t write(const void* buffer, std::size_t size) { return static_cast<Writer*>(this)->write(buffer, size); } | |
}; | |
} // end namespace detail | |
// Copy all bytes from reader to writer using the provided buffer. | |
template <typename Reader, typename Writer> | |
std::size_t copyWithBuffer(Reader&& reader, Writer&& writer, // | |
std::byte* buffer, std::size_t bufferSize) | |
{ | |
std::size_t bytesCopied = 0; | |
while (true) { | |
auto lastRead = reader.read(buffer, bufferSize); | |
auto lastWrite = writer.write(buffer, lastRead); | |
bytesCopied += lastWrite; | |
if (lastRead != bufferSize || lastWrite != lastRead) { | |
break; | |
} | |
} | |
return bytesCopied; | |
} | |
// Copy size bytes from reader to writer using the provided buffer. | |
template <typename Reader, typename Writer> | |
std::size_t copyWithBuffer(Reader&& reader, Writer&& writer, std::size_t size, // | |
std::byte* buffer, std::size_t bufferSize) | |
{ | |
std::size_t bytesCopied = 0; | |
while (bytesCopied < size) { | |
auto lastRead = reader.read(buffer, std::min(size - bytesCopied, bufferSize)); | |
auto lastWrite = writer.write(buffer, lastRead); | |
bytesCopied += lastWrite; | |
if (lastRead != bufferSize || lastWrite != lastRead) { | |
break; | |
} | |
} | |
return bytesCopied; | |
} | |
class BinaryReader : public detail::BinaryReaderBase<BinaryReader> { | |
public: | |
BinaryReader(const std::byte* data, std::size_t size) : m_begin(data), m_end(data + size) {} | |
explicit operator bool() const { return m_begin && !isEOF(); } | |
bool isEOF() const { return m_cursor == m_end; } | |
const std::byte* data() const { return m_begin; } | |
const std::byte* cursor() const { return m_cursor; } | |
std::uint64_t size() const { return m_end - m_begin; } | |
std::uint64_t sizeRemaining() const { return m_end - m_cursor; } | |
std::intptr_t tell() const { return m_cursor - m_begin; } | |
void seek(std::int64_t offset, int origin = SEEK_SET) | |
{ | |
if (origin == SEEK_CUR) { | |
m_cursor += offset; | |
} else if (origin == SEEK_SET) { | |
m_cursor = m_begin + offset; | |
} else if (origin == SEEK_END) { | |
m_cursor = m_end + offset; | |
} | |
m_cursor = std::clamp(m_cursor, m_begin, m_end); | |
} | |
std::size_t read(void* outBuffer, std::size_t size) | |
{ | |
auto bytesRead = std::min<std::size_t>(size, sizeRemaining()); | |
std::memcpy(outBuffer, m_cursor, bytesRead); | |
m_cursor += bytesRead; | |
return bytesRead; | |
} | |
template <typename Writer> | |
std::size_t copyTo(Writer&& writer) | |
{ | |
return copyTo(std::forward<Writer>(writer), sizeRemaining()); | |
} | |
template <typename Writer> | |
std::size_t copyTo(Writer&& writer, std::size_t size) | |
{ | |
return writer.write(m_cursor, std::min(size, sizeRemaining())); | |
} | |
private: | |
const std::byte* m_begin = nullptr; | |
const std::byte* m_end = nullptr; | |
const std::byte* m_cursor = m_begin; | |
}; | |
class BinaryWriter : public detail::BinaryWriterBase<BinaryWriter> { | |
public: | |
operator BinaryReader() const { return {m_data.data(), m_data.size()}; } | |
std::byte* data() { return m_data.data(); } | |
const std::byte* data() const { return m_data.data(); } | |
std::size_t size() { return m_data.size(); } | |
std::int64_t tell() const { return m_cursor; } | |
void seek(std::int64_t offset, int origin = SEEK_SET) | |
{ | |
if (origin == SEEK_CUR) { | |
m_cursor += offset; | |
} else if (origin == SEEK_SET) { | |
m_cursor = offset; | |
} else if (origin == SEEK_END) { | |
m_cursor = m_data.size() + offset; | |
} | |
m_cursor = std::clamp<std::int64_t>(m_cursor, 0, m_data.size()); | |
} | |
std::size_t write(const void* buffer, std::size_t size) | |
{ | |
m_data.resize(std::max(m_data.size(), m_cursor + size)); | |
std::memcpy(m_data.data() + m_cursor, buffer, size); | |
m_cursor += size; | |
assert(m_cursor <= m_data.size()); | |
return size; | |
} | |
template <typename Reader> | |
std::size_t copyFrom(Reader&& reader) | |
{ | |
return copyFrom(std::forward<Reader>(reader), reader.sizeRemaining()); | |
} | |
template <typename Reader> | |
std::size_t copyFrom(Reader&& reader, std::size_t size) | |
{ | |
std::vector<std::byte> buffer(4096); | |
return copyWithBuffer(reader, *this, size, buffer.data(), buffer.size()); | |
} | |
private: | |
std::vector<std::byte> m_data; | |
std::size_t m_cursor = 0; | |
}; | |
class BinaryFileReader : public detail::BinaryReaderBase<BinaryFileReader> { | |
public: | |
explicit BinaryFileReader(const std::filesystem::path& filepath) : BinaryFileReader(filepath.string()) {} | |
explicit BinaryFileReader(const std::string& filepath) : m_file(std::fopen(filepath.c_str(), "rb")) | |
{ | |
if (m_file) { | |
seek(0, SEEK_END); | |
m_size = tell(); | |
seek(0); | |
} | |
} | |
BinaryFileReader(const BinaryFileReader&) = delete; | |
BinaryFileReader& operator=(const BinaryFileReader&) = delete; | |
BinaryFileReader(const BinaryFileReader&&) noexcept = delete; | |
BinaryFileReader& operator=(const BinaryFileReader&&) noexcept = delete; | |
~BinaryFileReader() { std::fclose(m_file); } | |
std::FILE* handle() { return m_file; } | |
explicit operator bool() const { return m_file && !hasError() && !isEOF(); } | |
bool hasError() const { return std::ferror(m_file); } | |
bool isEOF() const | |
{ | |
// Not using std::feof here as the EOF flag is only set after a read | |
// operation detected the end of the file. | |
return sizeRemaining() <= 0; | |
} | |
std::uint64_t size() const { return m_size; } | |
std::uint64_t sizeRemaining() const { return m_size - std::ftell(m_file); } | |
std::int64_t tell() const { return std::ftell(m_file); } | |
void seek(std::int64_t offset, int origin = SEEK_SET) { std::fseek(m_file, offset, origin); } | |
std::size_t read(void* outBuffer, std::size_t size) { return std::fread(outBuffer, 1, size, m_file); } | |
private: | |
std::FILE* m_file = nullptr; | |
std::uint64_t m_size = 0; | |
}; | |
class BinaryFileWriter : public detail::BinaryWriterBase<BinaryFileWriter> { | |
public: | |
explicit BinaryFileWriter(const std::filesystem::path& filepath) : BinaryFileWriter(filepath.string()) {} | |
explicit BinaryFileWriter(const std::string& filepath) : m_file(std::fopen(filepath.c_str(), "wb")) {} | |
BinaryFileWriter(const BinaryFileWriter&) = delete; | |
BinaryFileWriter& operator=(const BinaryFileWriter&) = delete; | |
BinaryFileWriter(const BinaryFileWriter&&) noexcept = delete; | |
BinaryFileWriter& operator=(const BinaryFileWriter&&) noexcept = delete; | |
~BinaryFileWriter() { std::fclose(m_file); } | |
std::FILE* handle() { return m_file; } | |
explicit operator bool() const { return m_file && !hasError(); } | |
bool hasError() const { return std::ferror(m_file); } | |
std::int64_t tell() const { return std::ftell(m_file); } | |
void seek(std::int64_t offset, int origin = SEEK_SET) { std::fseek(m_file, offset, origin); } | |
std::size_t write(const void* buffer, std::size_t size) { return std::fwrite(buffer, 1, size, m_file); } | |
private: | |
std::FILE* m_file = nullptr; | |
}; | |
} // namespace ph3::io |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment