#include "sweepstore/utils/file_handle.h" #include "sweepstore/utils/timing.h" // Constructor - just stores path and mode, actual stream is created per-thread SweepstoreFileHandle::SweepstoreFileHandle(const std::string& p, std::ios::openmode mode) : path(p) , openMode(mode) #ifdef WITH_UNREAL { IPlatformFile& platformFile = FPlatformFileManager::Get().GetPlatformFile(); // Map std::ios flags to Unreal flags bool read = (mode & std::ios::in) != 0; bool write = (mode & std::ios::out) != 0; if (read && write) { unrealHandle = platformFile.OpenReadWrite(*FString(path.c_str()), true); } else if (write) { unrealHandle = platformFile.OpenWrite(*FString(path.c_str()), false, false); } else { unrealHandle = platformFile.OpenRead(*FString(path.c_str()), false); } if (!unrealHandle) { throw std::runtime_error("Failed to open file: " + path); } } #else { // Thread-local streams created on demand in getThreadStream() } #endif #ifndef WITH_UNREAL // Get or create the fstream for this thread std::fstream& SweepstoreFileHandle::getThreadStream() { auto it = streamCache.find(path); if (it == streamCache.end() || !it->second || !it->second->is_open()) { // Create new stream for this thread auto stream = std::make_unique(path, openMode); if (!stream->is_open()) { throw std::runtime_error("Failed to open file: " + path); } streamCache[path] = std::move(stream); return *streamCache[path]; } return *it->second; } const std::fstream& SweepstoreFileHandle::getThreadStream() const { // Use const_cast to reuse the non-const version return const_cast(this)->getThreadStream(); } #endif // isOpen bool SweepstoreFileHandle::isOpen() const { #ifdef WITH_UNREAL return unrealHandle != nullptr; #else auto it = streamCache.find(path); return it != streamCache.end() && it->second && it->second->is_open(); #endif } // close void SweepstoreFileHandle::close() { #ifdef WITH_UNREAL if (unrealHandle) { delete unrealHandle; unrealHandle = nullptr; } #else // Close this thread's stream if it exists auto it = streamCache.find(path); if (it != streamCache.end() && it->second && it->second->is_open()) { it->second->close(); } #endif } #if defined(_WIN32) || defined(WITH_UNREAL) // flush void SweepstoreFileHandle::flush() { #ifdef WITH_UNREAL if (unrealHandle) { unrealHandle->Flush(); } #else // Windows-specific implementation for guaranteed flush to disk auto& stream = getThreadStream(); stream.flush(); // On Windows, also call sync to push to OS buffers // Then open a Windows HANDLE to the same file and call FlushFileBuffers // This is more reliable than trying to extract the HANDLE from fstream HANDLE h = CreateFileA( path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (h != INVALID_HANDLE_VALUE) { FlushFileBuffers(h); CloseHandle(h); } #endif } // readSeek void SweepstoreFileHandle::readSeek(std::streampos pos, std::ios::seekdir dir) { #ifdef WITH_UNREAL // Unreal doesn't have separate read/write pointers, so just seek int64 unrealPos = static_cast(pos); if (dir == std::ios::beg) { unrealHandle->Seek(unrealPos); } else if (dir == std::ios::cur) { unrealHandle->Seek(unrealHandle->Tell() + unrealPos); } else if (dir == std::ios::end) { unrealHandle->SeekFromEnd(unrealPos); } #else // Windows - simplified to only seek read pointer auto& stream = getThreadStream(); stream.seekg(pos, dir); if (stream.fail()) { stream.clear(); } #endif } // writeSeek void SweepstoreFileHandle::writeSeek(std::streampos pos, std::ios::seekdir dir) { #ifdef WITH_UNREAL // Same as readSeek for Unreal readSeek(pos, dir); #else // Windows - simplified to only seek write pointer auto& stream = getThreadStream(); stream.seekp(pos, dir); if (stream.fail()) { stream.clear(); } #endif } // readBytes void SweepstoreFileHandle::readBytes(char* buffer, std::streamsize size) { #ifdef WITH_UNREAL unrealHandle->Read(reinterpret_cast(buffer), size); #else // Windows auto& stream = getThreadStream(); stream.read(buffer, size); #endif } // writeBytes void SweepstoreFileHandle::writeBytes(const char* buffer, std::streamsize size) { SWEEPSTORE_TIME_FUNCTION(); #ifdef WITH_UNREAL unrealHandle->Write(reinterpret_cast(buffer), size); unrealHandle->Flush(); // Unreal requires explicit flush #else // Windows auto& stream = getThreadStream(); stream.write(buffer, size); #endif } #endif // _WIN32 || WITH_UNREAL