Skip to content

Instantly share code, notes, and snippets.

@W4RH4WK
Created December 2, 2023 13:14
Show Gist options
  • Save W4RH4WK/adaffc5d73ec1ee012b49f1ec1d27c06 to your computer and use it in GitHub Desktop.
Save W4RH4WK/adaffc5d73ec1ee012b49f1ec1d27c06 to your computer and use it in GitHub Desktop.
binary io
#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