Browse Source

NativeFile: Implement buffering.

Miku AuahDark 7 months ago
parent
commit
88fb547a58
2 changed files with 211 additions and 29 deletions
  1. 206 29
      src/modules/filesystem/NativeFile.cpp
  2. 5 0
      src/modules/filesystem/NativeFile.h

+ 206 - 29
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 <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>
@@ -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<int64>(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;
+
+		// 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()
@@ -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";
 	}
 }

+ 5 - 0
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