Add write buffering to Windows SDL_IOStream (#12424) (#14794)

This commit is contained in:
Edgar San Martin, Jr.
2026-01-12 14:39:06 -05:00
committed by GitHub
parent dd52dd8995
commit 7c9ae67706
+132 -23
View File
@@ -70,6 +70,9 @@ typedef struct IOStreamWindowsData
void *data; void *data;
size_t size; size_t size;
size_t left; size_t left;
void *write_data;
size_t write_pos;
bool writable;
bool append; bool append;
bool autoclose; bool autoclose;
} IOStreamWindowsData; } IOStreamWindowsData;
@@ -80,7 +83,8 @@ typedef struct IOStreamWindowsData
#define INVALID_SET_FILE_POINTER 0xFFFFFFFF #define INVALID_SET_FILE_POINTER 0xFFFFFFFF
#endif #endif
#define READAHEAD_BUFFER_SIZE 1024 #define READAHEAD_BUFFER_SIZE 1024
#define WRITEBEHIND_BUFFER_SIZE 1024
static HANDLE SDLCALL windows_file_open(const char *filename, const char *mode) static HANDLE SDLCALL windows_file_open(const char *filename, const char *mode)
{ {
@@ -150,6 +154,41 @@ static HANDLE SDLCALL windows_file_open(const char *filename, const char *mode)
return h; return h;
} }
static bool windows_flush_write_buffer(IOStreamWindowsData *iodata,
SDL_IOStatus *status)
{
if (iodata->write_pos == 0) {
return true; // Nothing to flush
}
// In append mode, seek to EOF before writing
if (iodata->append) {
LARGE_INTEGER windowsoffset;
windowsoffset.QuadPart = 0;
if (!SetFilePointerEx(iodata->h, windowsoffset,
&windowsoffset, FILE_END)) {
if (status) {
*status = SDL_IO_STATUS_ERROR;
}
WIN_SetError("Error seeking in datastream");
return false;
}
}
DWORD bytes;
if (!WriteFile(iodata->h, iodata->write_data,
(DWORD)iodata->write_pos, &bytes, NULL)) {
if (status) {
*status = SDL_IO_STATUS_ERROR;
}
WIN_SetError("Error writing to datastream");
return false;
}
iodata->write_pos = 0;
return true;
}
static Sint64 SDLCALL windows_file_size(void *userdata) static Sint64 SDLCALL windows_file_size(void *userdata)
{ {
IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata; IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata;
@@ -168,6 +207,10 @@ static Sint64 SDLCALL windows_file_seek(void *userdata, Sint64 offset, SDL_IOWhe
DWORD windowswhence; DWORD windowswhence;
LARGE_INTEGER windowsoffset; LARGE_INTEGER windowsoffset;
if (!windows_flush_write_buffer(iodata, NULL)) {
return -1;
}
// FIXME: We may be able to satisfy the seek within buffered data // FIXME: We may be able to satisfy the seek within buffered data
if ((whence == SDL_IO_SEEK_CUR) && (iodata->left)) { if ((whence == SDL_IO_SEEK_CUR) && (iodata->left)) {
offset -= iodata->left; offset -= iodata->left;
@@ -204,6 +247,10 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size,
size_t read_ahead; size_t read_ahead;
DWORD bytes; DWORD bytes;
if (!windows_flush_write_buffer(iodata, status)) {
return 0;
}
if (iodata->left > 0) { if (iodata->left > 0) {
void *data = (char *)iodata->data + void *data = (char *)iodata->data +
iodata->size - iodata->size -
@@ -270,11 +317,20 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size,
static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status) static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status)
{ {
IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata; IOStreamWindowsData *iodata = (IOStreamWindowsData *)userdata;
DWORD bytes; const Uint8 *src = (const Uint8 *)ptr;
size_t remaining = size;
size_t total_written = 0;
if (!iodata->writable) {
*status = SDL_IO_STATUS_READONLY;
return 0;
}
// Invalidate read-ahead buffer if it has data
if (iodata->left) { if (iodata->left) {
if (!SetFilePointer(iodata->h, -(LONG)iodata->left, NULL, FILE_CURRENT)) { if (!SetFilePointer(iodata->h, -(LONG)iodata->left,
NULL, FILE_CURRENT)) {
*status = SDL_IO_STATUS_ERROR; *status = SDL_IO_STATUS_ERROR;
WIN_SetError("Error seeking in datastream"); WIN_SetError("Error seeking in datastream");
return 0; return 0;
@@ -282,30 +338,66 @@ static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t
iodata->left = 0; iodata->left = 0;
} }
// if in append mode, we must go to the EOF before write // For large writes, flush buffer and write directly
if (iodata->append) { if (size >= WRITEBEHIND_BUFFER_SIZE) {
LARGE_INTEGER windowsoffset; if (!windows_flush_write_buffer(iodata, status)) {
windowsoffset.QuadPart = 0;
if (!SetFilePointerEx(iodata->h, windowsoffset, &windowsoffset, FILE_END)) {
*status = SDL_IO_STATUS_ERROR;
WIN_SetError("Error seeking in datastream");
return 0; return 0;
} }
// In append mode, seek to EOF before direct write
if (iodata->append) {
LARGE_INTEGER windowsoffset;
windowsoffset.QuadPart = 0;
if (!SetFilePointerEx(iodata->h, windowsoffset,
&windowsoffset, FILE_END)) {
*status = SDL_IO_STATUS_ERROR;
WIN_SetError("Error seeking in datastream");
return 0;
}
}
DWORD bytes;
if (!WriteFile(iodata->h, ptr, (DWORD)size, &bytes, NULL)) {
*status = SDL_IO_STATUS_ERROR;
WIN_SetError("Error writing to datastream");
return 0;
} else if (bytes == 0 && size > 0) {
*status = SDL_IO_STATUS_NOT_READY;
}
return bytes;
}
// Buffer small writes
while (remaining > 0) {
size_t space = WRITEBEHIND_BUFFER_SIZE - iodata->write_pos;
size_t to_buffer = SDL_min(remaining, space);
SDL_memcpy((char *)iodata->write_data + iodata->write_pos,
src, to_buffer);
iodata->write_pos += to_buffer;
src += to_buffer;
remaining -= to_buffer;
total_written += to_buffer;
if (iodata->write_pos == WRITEBEHIND_BUFFER_SIZE) {
if (!windows_flush_write_buffer(iodata, status)) {
return total_written;
}
}
} }
if (!WriteFile(iodata->h, ptr, (DWORD)size, &bytes, NULL)) { return total_written;
*status = SDL_IO_STATUS_ERROR;
WIN_SetError("Error writing to datastream");
return 0;
} else if (bytes == 0 && size > 0) {
*status = SDL_IO_STATUS_NOT_READY;
}
return bytes;
} }
static bool SDLCALL windows_file_flush(void *userdata, SDL_IOStatus *status) static bool SDLCALL windows_file_flush(void *userdata, SDL_IOStatus *status)
{ {
IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata; IOStreamWindowsData *iodata = (IOStreamWindowsData *)userdata;
if (!windows_flush_write_buffer(iodata, status)) {
return false;
}
// Sync to disk
if (!FlushFileBuffers(iodata->h)) { if (!FlushFileBuffers(iodata->h)) {
return WIN_SetError("Error flushing datastream"); return WIN_SetError("Error flushing datastream");
} }
@@ -314,16 +406,23 @@ static bool SDLCALL windows_file_flush(void *userdata, SDL_IOStatus *status)
static bool SDLCALL windows_file_close(void *userdata) static bool SDLCALL windows_file_close(void *userdata)
{ {
IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata; IOStreamWindowsData *iodata = (IOStreamWindowsData *)userdata;
bool result = true;
if (!windows_flush_write_buffer(iodata, NULL)) {
result = false;
}
if (iodata->h != INVALID_HANDLE_VALUE) { if (iodata->h != INVALID_HANDLE_VALUE) {
if (iodata->autoclose) { if (iodata->autoclose) {
CloseHandle(iodata->h); CloseHandle(iodata->h);
} }
iodata->h = INVALID_HANDLE_VALUE; // to be sure iodata->h = INVALID_HANDLE_VALUE;
} }
SDL_free(iodata->data); SDL_free(iodata->data);
SDL_free(iodata->write_data);
SDL_free(iodata); SDL_free(iodata);
return true; return result;
} }
SDL_IOStream *SDL_IOFromHandle(HANDLE handle, const char *mode, bool autoclose) SDL_IOStream *SDL_IOFromHandle(HANDLE handle, const char *mode, bool autoclose)
@@ -348,6 +447,9 @@ SDL_IOStream *SDL_IOFromHandle(HANDLE handle, const char *mode, bool autoclose)
iface.close = windows_file_close; iface.close = windows_file_close;
iodata->h = handle; iodata->h = handle;
iodata->writable = (SDL_strchr(mode, 'w') != NULL) ||
(SDL_strchr(mode, 'a') != NULL) ||
(SDL_strchr(mode, '+') != NULL);
iodata->append = (SDL_strchr(mode, 'a') != NULL); iodata->append = (SDL_strchr(mode, 'a') != NULL);
iodata->autoclose = autoclose; iodata->autoclose = autoclose;
@@ -357,6 +459,13 @@ SDL_IOStream *SDL_IOFromHandle(HANDLE handle, const char *mode, bool autoclose)
return NULL; return NULL;
} }
iodata->write_data = (char *)SDL_malloc(WRITEBEHIND_BUFFER_SIZE);
if (!iodata->write_data) {
iface.close(iodata);
return NULL;
}
iodata->write_pos = 0;
SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata); SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata);
if (!iostr) { if (!iostr) {
iface.close(iodata); iface.close(iodata);