#include "engine/framework/io/binary.h"
#include
#include
#include
#include
#include
#if defined(__unix__) || defined(__APPLE__)
#include
#include
#include
#include
#elif defined(_WIN32)
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include
#endif
namespace engine::io {
namespace {
template
std::vector read_typed_file(const std::filesystem::path & path) {
std::ifstream input(path, std::ios::binary);
if (!input) {
throw std::runtime_error("failed to open binary file: " + path.string());
}
input.seekg(0, std::ios::end);
const auto size = static_cast(input.tellg());
input.seekg(0, std::ios::beg);
if (size % sizeof(T) != 0) {
throw std::runtime_error("binary file has invalid size for requested element type: " + path.string());
}
std::vector values(size / sizeof(T));
input.read(reinterpret_cast(values.data()), static_cast<:streamsize>(size));
if (!input) {
throw std::runtime_error("failed to read binary file: " + path.string());
}
return values;
}
} // namespace
BinaryBlob::~BinaryBlob() {
reset();
}
BinaryBlob::BinaryBlob(std::vector<:byte> owned) noexcept
: owned_(std::move(owned)) {}
BinaryBlob::BinaryBlob(const std::byte * mapped, size_t size) noexcept
: mapped_(mapped),
size_(size) {}
BinaryBlob::BinaryBlob(BinaryBlob && other) noexcept
: owned_(std::move(other.owned_)),
mapped_(other.mapped_),
size_(other.size_) {
other.mapped_ = nullptr;
other.size_ = 0;
}
BinaryBlob & BinaryBlob::operator=(BinaryBlob && other) noexcept {
if (this == &other) {
return *this;
}
reset();
owned_ = std::move(other.owned_);
mapped_ = other.mapped_;
size_ = other.size_;
other.mapped_ = nullptr;
other.size_ = 0;
return *this;
}
const std::byte * BinaryBlob::data() const noexcept {
return mapped_ != nullptr ? mapped_ : owned_.data();
}
size_t BinaryBlob::size() const noexcept {
return mapped_ != nullptr ? size_ : owned_.size();
}
bool BinaryBlob::empty() const noexcept {
return size() == 0;
}
void BinaryBlob::discard_range(size_t offset, size_t size) const noexcept {
#if defined(__unix__) || defined(__APPLE__)
if (mapped_ == nullptr || size == 0 || offset >= size_) {
return;
}
const size_t end = std::min(offset + size, size_);
const long page_size_long = sysconf(_SC_PAGESIZE);
if (page_size_long <= 0) {
return;
}
const size_t page_size = static_cast(page_size_long);
const size_t aligned_begin = offset - (offset % page_size);
const size_t aligned_end = ((end + page_size - 1) / page_size) * page_size;
const size_t clamped_end = std::min(aligned_end, size_);
if (clamped_end > aligned_begin) {
void * address = const_cast<:byte>(mapped_) + static_cast<:ptrdiff_t>(aligned_begin);
const size_t length = clamped_end - aligned_begin;
#if defined(MADV_DONTNEED)
(void) madvise(address, length, MADV_DONTNEED);
#elif defined(POSIX_MADV_DONTNEED)
(void) posix_madvise(address, length, POSIX_MADV_DONTNEED);
#endif
}
#else
(void) offset;
(void) size;
#endif
}
void BinaryBlob::reset() noexcept {
#if defined(__unix__) || defined(__APPLE__)
if (mapped_ != nullptr) {
munmap(const_cast<:byte>(mapped_), size_);
mapped_ = nullptr;
size_ = 0;
}
#elif defined(_WIN32)
if (mapped_ != nullptr) {
UnmapViewOfFile(mapped_);
mapped_ = nullptr;
size_ = 0;
}
#else
mapped_ = nullptr;
size_ = 0;
#endif
owned_.clear();
owned_.shrink_to_fit();
}
BinaryBlob read_binary_blob(const std::filesystem::path & path) {
#if defined(__unix__) || defined(__APPLE__)
const int fd = open(path.c_str(), O_RDONLY);
if (fd >= 0) {
struct stat st {};
if (fstat(fd, &st) == 0 && st.st_size >= 0) {
const size_t size = static_cast(st.st_size);
if (size == 0) {
close(fd);
return BinaryBlob();
}
void * mapped = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
if (mapped != MAP_FAILED) {
return BinaryBlob(static_cast(mapped), size);
}
} else {
close(fd);
}
}
#elif defined(_WIN32)
const HANDLE file = CreateFileW(
path.c_str(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (file != INVALID_HANDLE_VALUE) {
LARGE_INTEGER file_size {};
if (GetFileSizeEx(file, &file_size) != 0 &&
file_size.QuadPart >= 0 &&
static_cast(file_size.QuadPart) <=
static_cast(
std::numeric_limits::max())) {
const size_t size = static_cast(file_size.QuadPart);
if (size == 0) {
CloseHandle(file);
return BinaryBlob();
}
const HANDLE mapping = CreateFileMappingW(
file, nullptr, PAGE_READONLY, 0, 0, nullptr);
if (mapping != nullptr) {
const void * mapped = MapViewOfFile(
mapping, FILE_MAP_READ, 0, 0, 0);
CloseHandle(mapping);
CloseHandle(file);
if (mapped != nullptr) {
return BinaryBlob(
static_cast(mapped), size);
}
} else {
CloseHandle(file);
}
} else {
CloseHandle(file);
}
}
#endif
std::ifstream input(path, std::ios::binary);
if (!input) {
throw std::runtime_error("failed to open binary file: " + path.string());
}
input.seekg(0, std::ios::end);
const auto size = static_cast(input.tellg());
input.seekg(0, std::ios::beg);
std::vector<:byte> bytes(size);
if (size > 0) {
input.read(reinterpret_cast(bytes.data()), static_cast<:streamsize>(size));
if (!input) {
throw std::runtime_error("failed to read binary file: " + path.string());
}
}
return BinaryBlob(std::move(bytes));
}
std::vector<:byte> read_binary_file(const std::filesystem::path & path) {
auto blob = read_binary_blob(path);
std::vector<:byte> bytes(blob.size());
if (!bytes.empty()) {
std::memcpy(bytes.data(), blob.data(), blob.size());
}
return bytes;
}
std::vector read_f32_file(const std::filesystem::path & path) {
return read_typed_file(path);
}
std::vector read_i32_file(const std::filesystem::path & path) {
return read_typed_file(path);
}
} // namespace engine::io