Skip to content

Commit

Permalink
NativeFile: Implement buffering.
Browse files Browse the repository at this point in the history
  • Loading branch information
MikuAuahDark committed Jan 4, 2025
1 parent 70cc725 commit 43a3cda
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 29 deletions.
235 changes: 206 additions & 29 deletions src/modules/filesystem/NativeFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <sys/types.h>
#include <sys/stat.h>

#ifdef LOVE_WINDOWS
#include <wchar.h>
#else
#include <unistd.h> // POSIX.
#endif
// C
#include <cstring>

// SDL
#include <SDL3/SDL_iostream.h>
Expand All @@ -50,8 +38,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());
Expand All @@ -61,8 +51,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());
Expand Down Expand Up @@ -97,18 +89,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
Expand All @@ -118,8 +123,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<int64>(size, -1);
}

int64 NativeFile::read(void *dst, int64 size)
Expand All @@ -130,8 +138,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;
}

Expand All @@ -143,17 +155,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;

// 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 written == 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;

// 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<int64>(bufferUsed - (int64) written, 0);
}

return SDL_FlushIO(file);
}
default:
throw love::Exception("Invalid file mode.");
}

return SDL_FlushIO(file);
// Make sure compiler doesn't emit warnings.
return true;
}

bool NativeFile::isEOF()
Expand All @@ -166,32 +221,88 @@ 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)
{
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;
Expand All @@ -215,6 +326,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)
Expand All @@ -227,9 +407,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";
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/modules/filesystem/NativeFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ class NativeFile : public File
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);

Expand All @@ -77,8 +80,10 @@ class NativeFile : public File

Mode mode;

int8 *buffer;
BufferMode bufferMode;
int64 bufferSize;
int64 bufferUsed;

}; // NativeFile

Expand Down

0 comments on commit 43a3cda

Please sign in to comment.