From 88fb547a58868dd7847a9c992ae5a4201b63fc2f Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Sat, 4 Jan 2025 16:49:33 +0800 Subject: [PATCH] NativeFile: Implement buffering. --- src/modules/filesystem/NativeFile.cpp | 235 ++++++++++++++++++++++---- src/modules/filesystem/NativeFile.h | 5 + 2 files changed, 211 insertions(+), 29 deletions(-) diff --git a/src/modules/filesystem/NativeFile.cpp b/src/modules/filesystem/NativeFile.cpp index 83eaa80be..1bb070edc 100644 --- a/src/modules/filesystem/NativeFile.cpp +++ b/src/modules/filesystem/NativeFile.cpp @@ -20,21 +20,9 @@ // LOVE #include "NativeFile.h" -#include "common/utf8.h" -#ifdef LOVE_ANDROID -#include "common/android.h" -#endif - -// Assume POSIX or Visual Studio. -#include -#include - -#ifdef LOVE_WINDOWS -#include -#else -#include // POSIX. -#endif +// C +#include // SDL #include @@ -48,8 +36,10 @@ NativeFile::NativeFile(const std::string &filename, Mode mode) : filename(filename) , file(nullptr) , mode(MODE_CLOSED) +, buffer(nullptr) , bufferMode(BUFFER_NONE) , bufferSize(0) +, bufferUsed(0) { if (!open(mode)) throw love::Exception("Could not open file at path %s", filename.c_str()); @@ -59,8 +49,10 @@ NativeFile::NativeFile(const NativeFile &other) : filename(other.filename) , file(nullptr) , mode(MODE_CLOSED) +, buffer(nullptr) , bufferMode(other.bufferMode) , bufferSize(other.bufferSize) +, bufferUsed(0) { if (!open(other.mode)) throw love::Exception("Could not open file at path %s", filename.c_str()); @@ -95,18 +87,31 @@ bool NativeFile::open(Mode newmode) mode = newmode; + if (!setupBuffering(bufferMode, bufferSize)) + { + SDL_CloseIO(file); + file = nullptr; + mode = MODE_CLOSED; + throw love::Exception("Could not open file %s: cannot setup buffering", filename.c_str()); + } + return file != nullptr; } bool NativeFile::close() { - if (file == nullptr || !SDL_CloseIO(file)) + if (file == nullptr) return false; + bool success = flush(); + success = SDL_CloseIO(file) && success; + // Regardless whetever SDL_CloseIO succeeded or failed, the `file` + // pointer is no longer valid. mode = MODE_CLOSED; file = nullptr; + setupBuffering(BUFFER_NONE, 0); - return true; + return success; } bool NativeFile::isOpen() const @@ -116,8 +121,11 @@ bool NativeFile::isOpen() const int64 NativeFile::getSize() { + if (!file) + return -1; + int64 size = SDL_GetIOSize(file); - return std::max(size, -1LL); + return std::max(size, -1); } int64 NativeFile::read(void *dst, int64 size) @@ -128,8 +136,12 @@ int64 NativeFile::read(void *dst, int64 size) if (size < 0) throw love::Exception("Invalid read size."); - size_t read = SDL_ReadIO(file, dst, (size_t) size); + // Are we using buffers? + if (buffer) + return bufferedRead(dst, size); + // No buffering. + size_t read = SDL_ReadIO(file, dst, (size_t) size); return (int64) read; } @@ -141,17 +153,60 @@ bool NativeFile::write(const void *data, int64 size) if (size < 0) throw love::Exception("Invalid write size."); - int64 written = SDL_WriteIO(file, data, (size_t) size); + if (buffer) + { + if (!bufferedWrite(data, size)) + return false; - return written == size; + // There's newline? force flush + if (bufferMode == BUFFER_LINE && memchr(data, '\n', size) != nullptr) + { + if (!flush()) + return false; + } + } + else + return SDL_WriteIO(file, data, (size_t) size) == (size_t) size; + + return true; } bool NativeFile::flush() { - if (!file || (mode != MODE_WRITE && mode != MODE_APPEND)) - throw love::Exception("File is not opened for writing."); + switch (mode) + { + case MODE_READ: + { + if (buffer) + { + // Seek to already consumed buffer + if (SDL_SeekIO(file, (size_t) (bufferUsed - bufferSize), SDL_IO_SEEK_CUR) < 0) + return false; - return SDL_FlushIO(file); + // Mark as depleted + bufferUsed = bufferSize; + } + + return true; + } + case MODE_WRITE: + case MODE_APPEND: + { + if (buffer && bufferUsed > 0) + { + size_t written = SDL_WriteIO(file, buffer, (size_t) bufferUsed); + memmove(buffer, buffer + written, (size_t) bufferSize - written); + bufferUsed = std::max(bufferUsed - (int64) written, 0); + } + + return SDL_FlushIO(file); + } + default: + throw love::Exception("Invalid file mode."); + } + + // Make sure compiler doesn't emit warnings. + return true; } bool NativeFile::isEOF() @@ -164,7 +219,23 @@ int64 NativeFile::tell() if (file == nullptr) return -1; - return SDL_TellIO(file); + int64 offset = 0; + if (buffer) + { + switch (mode) + { + case MODE_READ: + // Note: We want offset be negative for reading + offset = bufferUsed - bufferSize; + break; + case MODE_WRITE: + case MODE_APPEND: + offset = bufferUsed; + break; + } + } + + return SDL_TellIO(file) + offset; } bool NativeFile::seek(int64 pos, SeekOrigin origin) @@ -172,24 +243,64 @@ bool NativeFile::seek(int64 pos, SeekOrigin origin) if (file == nullptr) return false; + if (mode == MODE_APPEND) + // FIXME: PhysFS "append" allows the user to + // seek the write pointer, but it's not possible + // to do so with standard fopen-style modes. + return false; + SDL_IOWhence whence = SDL_IO_SEEK_SET; if (origin == SEEKORIGIN_CURRENT) whence = SDL_IO_SEEK_CUR; else if (origin == SEEKORIGIN_END) whence = SDL_IO_SEEK_END; - return SDL_SeekIO(file, pos, whence) >= 0; + if (mode == MODE_READ && whence == SDL_IO_SEEK_SET && buffer) + { + // Retain the buffer if it's forward. + // TODO: Handle SDL_IO_SEEK_CUR. + int64 offset = pos - tell(); + if (offset >= 0 && (offset + bufferUsed) < bufferSize) + { + // Seek success + bufferUsed += offset; + return true; + } + + // Note: We don't handle backward seek because the + // contents past `bufferUsed` is not necessarily valid. + } + + // If the read is buffered, the flush() will ensure + // the read pointer is in correct place before doing seek. + return flush() && (SDL_SeekIO(file, pos, whence) >= 0); } bool NativeFile::setBuffer(BufferMode bufmode, int64 size) { if (size < 0) return false; + else if (sizeof(uintptr_t) == 4 && size > 0x80000000LL) + // Safeguards against 32-bit integer truncation? + return false; if (bufmode == BUFFER_NONE) size = 0; + else if (size == 0) + // This should do for now + size = BUFSIZ; - // FIXME: SDL doesn't have option to set buffering. + // If there's no file handle, we'll setup the buffering later in open() + if (file) + { + // Ideally we don't want to flush if user request larger buffer size + // but the added complexity is not worth it for now. + if (!flush()) + return false; + + if (!setupBuffering(bufmode, size)) + return false; + } bufferMode = bufmode; bufferSize = size; @@ -213,6 +324,75 @@ File::Mode NativeFile::getMode() const return mode; } +bool NativeFile::setupBuffering(BufferMode mode, int64 bufferSize) +{ + int8 *newbuf = nullptr; + if (mode != BUFFER_NONE) + { + newbuf = new (std::nothrow) int8[(size_t) bufferSize]; + if (newbuf == nullptr) + return false; + } + + delete[] buffer; + buffer = newbuf; + bufferUsed = this->mode == MODE_READ ? bufferSize : 0; + return true; +} + +int64 NativeFile::bufferedRead(void *dst, int64 size) +{ + int8 *ptr = (int8 *) dst; + int64 readed = 0; + + while (size > 0) + { + int64 available = bufferSize - bufferUsed; + + if (available > 0) + { + // There's leftover buffers. + size_t copy = (size_t) std::min(size, available); + memcpy(ptr, buffer + (size_t) bufferUsed, copy); + + ptr += copy; + size -= (int64) copy; + bufferUsed += (int64) copy; + readed += copy; + } + else + { + // Buffer is empty. Fill it. + size_t ureaded = SDL_ReadIO(file, buffer, (size_t) bufferSize); + bufferUsed = bufferSize - (int64) ureaded; + + if (ureaded == 0) + break; + + if (bufferUsed > 0) + // Shift the buffer so code above can properly index it + memmove(buffer + bufferUsed, buffer, ureaded); + } + } + + return readed; +} + +bool NativeFile::bufferedWrite(const void *data, int64 size) +{ + if ((size + bufferUsed) < bufferSize) + { + // Entire data fits in the buffer. + memcpy(buffer + bufferUsed, data, size); + bufferUsed += size; + return true; + } + + // Could overflow. Write directly. + size_t writeSize = (size_t) size; + return flush() && (SDL_WriteIO(file, data, writeSize) == writeSize); +} + const char *NativeFile::getModeString(Mode mode) { switch (mode) @@ -225,9 +405,6 @@ const char *NativeFile::getModeString(Mode mode) case File::MODE_WRITE: return "wb"; case File::MODE_APPEND: - // Note: PhysFS "append" allows the user to - // seek the write pointer, but it's not possible - // to do so with standard fopen-style modes. return "ab"; } } diff --git a/src/modules/filesystem/NativeFile.h b/src/modules/filesystem/NativeFile.h index ba9d3c1b5..947d5b1a5 100644 --- a/src/modules/filesystem/NativeFile.h +++ b/src/modules/filesystem/NativeFile.h @@ -66,6 +66,9 @@ public: private: NativeFile(const NativeFile &other); + bool setupBuffering(BufferMode mode, int64 bufferSize); + int64 bufferedRead(void* dst, int64 size); + bool bufferedWrite(const void* data, int64 size); static const char *getModeString(Mode mode); @@ -75,8 +78,10 @@ private: Mode mode; + int8 *buffer; BufferMode bufferMode; int64 bufferSize; + int64 bufferUsed; }; // NativeFile