rjson callers: read small JSON files whole instead of stat + chunked

Audit of every rjson / rxml call site's surrounding file I/O,
following the codecs' decoupling.  The rxml side needs nothing:
its production consumers parse buffers that arrive without the
adapter (logiqx through the scanner's budgeted incremental read,
natt from network payloads), and the filestream adapter is used
only by the sample harness and the treehash tool.  On the rjson
side, the large streaming consumers are already shaped right -
playlist, core info cache and the explore state read through
buffered (and RZIP-aware) streams with no redundant pre-open
checks, cloud sync opens tolerantly with no extra stats, and the
writer flushes in buffered chunks - but the small-file readers all
shared one wasteful pattern: stat the path (path_is_valid), open
it, let the stream adapter fstat it again for io block sizing,
then read a few hundred bytes through the chunked callback
machinery.

Five readers convert to filestream_read_file() +
rjson_open_buffer(): the runtime log (the hot one - playlist
sublabels showing per-core runtime build one of these per entry),
the disk index record, the two test input drivers, and the Remote
RetroPad core (the latter three being the reference code this
pattern kept getting copied from).  One open/size/read/close per
file, parse from memory, no callback crossings - and the
up-front stat is gone: filestream_read_file's failure covers
missing files, and path_is_valid now runs only inside the failure
branch to decide between the old quiet no-file path and the old
error message, so every log line and return value is preserved
while the common case pays one filesystem operation less.  These
files are a few hundred bytes to a few KB and always parsed in
full; holding them in memory for the parse is what the parser's
own input buffer was already doing, minus the chunking.

Verified: strict declaration-placement gates clean on the C89
TUs; the net-retropad translation unit syntax-checked standalone;
full Linux build links with HAVE_QT=1; no rjson_open_filestream
readers remain outside the write-side handles the buffered writer
still streams through.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-08-15 06:11:52 +02:00
1 parent b4eb9622df
commit 1dfed08090
5 files changed
+91 -77

No files matched your search

+17 -17
View File
@@ -341,31 +341,31 @@ static bool input_test_file_read(const char* file_path)
{
bool success = false;
ITifJSONContext context = {0};
RFILE *file = NULL;
uint8_t *file_buf = NULL;
int64_t file_len = 0;
rjson_t* parser;
/* Sanity check */
if ( (!file_path || !*file_path)
|| !path_is_valid(file_path)
)
if (!file_path || !*file_path)
return false;
/* Attempt to open test input file */
file = filestream_open(
file_path,
RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!file)
/* Read the whole file in one operation: it is tiny and always
* parsed in full, so a single open/size/read/close beats a
* pre-open stat plus the chunked callback path (which itself
* sizes the stream with an extra fstat). The stat below runs
* only to classify a failure. */
if (filestream_read_file(file_path,
(void**)&file_buf, &file_len) < 0)
{
NETRETROPAD_CORE_PREFIX(log_cb)(RETRO_LOG_ERROR,
"[Remote RetroPad] Failed to open test input file: \"%s\".\n",
file_path);
if (path_is_valid(file_path))
NETRETROPAD_CORE_PREFIX(log_cb)(RETRO_LOG_ERROR,
"[Remote RetroPad] Failed to open test input file: \"%s\".\n",
file_path);
return false;
}
/* Initialise JSON parser */
if (!(parser = rjson_open_filestream(file)))
if (!(parser = rjson_open_buffer(file_buf, (size_t)file_len)))
{
NETRETROPAD_CORE_PREFIX(log_cb)(RETRO_LOG_ERROR,
"[Remote RetroPad] Failed to create JSON parser.\n");
@@ -411,8 +411,8 @@ end:
if (context.message)
free(context.message);
/* Close log file */
filestream_close(file);
/* Release file contents */
free(file_buf);
if (last_test_step >= MAX_TEST_STEPS)
{
+19 -17
View File
@@ -106,7 +106,8 @@ static bool disk_index_file_read(disk_index_file_t *disk_index_file)
const char *file_path = NULL;
bool success = false;
DCifJSONContext context = {0};
RFILE *file = NULL;
uint8_t *file_buf = NULL;
int64_t file_len = 0;
rjson_t* parser;
/* Sanity check */
@@ -115,27 +116,28 @@ static bool disk_index_file_read(disk_index_file_t *disk_index_file)
file_path = disk_index_file->file_path;
if ( (!file_path || !*file_path)
|| !path_is_valid(file_path)
)
if (!file_path || !*file_path)
return false;
/* Attempt to open disk index file */
file = filestream_open(
file_path,
RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!file)
/* Read the whole record in one operation: these files are tiny
* and always parsed in full, so a single open/size/read/close
* beats a pre-open stat plus the chunked callback path (which
* itself sizes the stream with an extra fstat). Most content
* has no disk index record - that common case is one failed
* open, and the stat runs only to classify a failure as worth
* logging. */
if (filestream_read_file(file_path,
(void**)&file_buf, &file_len) < 0)
{
RARCH_ERR(
"[Disk index file] Failed to open disk index record file: \"%s\".\n",
file_path);
if (path_is_valid(file_path))
RARCH_ERR(
"[Disk index file] Failed to open disk index record file: \"%s\".\n",
file_path);
return false;
}
/* Initialise JSON parser */
if (!(parser = rjson_open_filestream(file)))
if (!(parser = rjson_open_buffer(file_buf, (size_t)file_len)))
{
RARCH_ERR("[Disk index file] Failed to create JSON parser.\n");
goto end;
@@ -190,8 +192,8 @@ end:
if (context.image_path)
free(context.image_path);
/* Close log file */
filestream_close(file);
/* Release file contents */
free(file_buf);
return success;
}
+18 -16
View File
@@ -187,33 +187,35 @@ static bool input_test_file_read(const char* file_path)
{
bool success = false;
KTifJSONContext context = {0};
RFILE *file = NULL;
uint8_t *file_buf = NULL;
int64_t file_len = 0;
rjson_t* parser;
/* Sanity check */
if ( (!file_path || !*file_path)
|| !path_is_valid(file_path)
)
if (!file_path || !*file_path)
{
RARCH_DBG("[Test input] No test input file supplied.\n");
return false;
}
/* Attempt to open test input file */
file = filestream_open(
file_path,
RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!file)
/* Read the whole file in one operation: it is tiny and always
* parsed in full, so a single open/size/read/close beats a
* pre-open stat plus the chunked callback path (which itself
* sizes the stream with an extra fstat). The stat below runs
* only to classify a failure. */
if (filestream_read_file(file_path,
(void**)&file_buf, &file_len) < 0)
{
RARCH_ERR("[Test input] Failed to open test input file: \"%s\".\n",
file_path);
if (!path_is_valid(file_path))
RARCH_DBG("[Test input] No test input file supplied.\n");
else
RARCH_ERR("[Test input] Failed to open test input file: \"%s\".\n",
file_path);
return false;
}
/* Initialise JSON parser */
if (!(parser = rjson_open_filestream(file)))
if (!(parser = rjson_open_buffer(file_buf, (size_t)file_len)))
{
RARCH_ERR("[Test input] Failed to create JSON parser.\n");
goto end;
@@ -258,8 +260,8 @@ end:
if (context.param_str)
free(context.param_str);
/* Close log file */
filestream_close(file);
/* Release file contents */
free(file_buf);
if (last_test_step >= MAX_TEST_STEPS)
{
+18 -16
View File
@@ -185,33 +185,35 @@ static bool input_test_file_read(const char* file_path)
{
bool success = false;
JTifJSONContext context = {0};
RFILE *file = NULL;
uint8_t *file_buf = NULL;
int64_t file_len = 0;
rjson_t* parser;
/* Sanity check */
if ( (!file_path || !*file_path)
|| !path_is_valid(file_path)
)
if (!file_path || !*file_path)
{
RARCH_DBG("[Test joypad] No test input file supplied.\n");
return false;
}
/* Attempt to open test input file */
file = filestream_open(
file_path,
RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!file)
/* Read the whole file in one operation: it is tiny and always
* parsed in full, so a single open/size/read/close beats a
* pre-open stat plus the chunked callback path (which itself
* sizes the stream with an extra fstat). The stat below runs
* only to classify a failure. */
if (filestream_read_file(file_path,
(void**)&file_buf, &file_len) < 0)
{
RARCH_ERR("[Test joypad] Failed to open test input file: \"%s\".\n",
file_path);
if (!path_is_valid(file_path))
RARCH_DBG("[Test joypad] No test input file supplied.\n");
else
RARCH_ERR("[Test joypad] Failed to open test input file: \"%s\".\n",
file_path);
return false;
}
/* Initialise JSON parser */
if (!(parser = rjson_open_filestream(file)))
if (!(parser = rjson_open_buffer(file_buf, (size_t)file_len)))
{
RARCH_ERR("[Test joypad] Failed to create JSON parser.\n");
goto end;
@@ -256,8 +258,8 @@ end:
if (context.param_str)
free(context.param_str);
/* Close log file */
filestream_close(file);
/* Release file contents */
free(file_buf);
if (last_test_step >= MAX_TEST_STEPS)
{
+19 -11
View File
@@ -127,18 +127,26 @@ static void runtime_log_read_file(runtime_log_t *runtime_log)
unsigned state_slot = 0;
RtlJSONContext context = {0};
/* Attempt to open log file */
RFILE *file = filestream_open(runtime_log->path,
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
uint8_t *file_buf = NULL;
int64_t file_len = 0;
if (!file)
/* Read the whole log in one operation: these files are a few
* hundred bytes and always parsed in full, so a single
* open/size/read/close beats a pre-open stat plus the chunked
* callback path (which itself sizes the stream with an extra
* fstat). A missing log - the first-run case - is silent, as
* it always was; the stat runs only when there is a failure to
* classify. */
if (filestream_read_file(runtime_log->path,
(void**)&file_buf, &file_len) < 0)
{
RARCH_ERR("[Runtime] Failed to open runtime log file: \"%s\".\n", runtime_log->path);
if (path_is_valid(runtime_log->path))
RARCH_ERR("[Runtime] Failed to open runtime log file: \"%s\".\n", runtime_log->path);
return;
}
/* Initialise JSON parser */
if (!(parser = rjson_open_filestream(file)))
if (!(parser = rjson_open_buffer(file_buf, (size_t)file_len)))
{
RARCH_ERR("[Runtime] Failed to create JSON parser.\n");
goto end;
@@ -315,8 +323,8 @@ end:
if (context.state_slot)
free(context.state_slot);
/* Close log file */
filestream_close(file);
/* Release file contents */
free(file_buf);
}
/* Initialise runtime log, loading current parameters
@@ -485,9 +493,9 @@ runtime_log_t *runtime_log_init(
strlcpy(runtime_log->path, log_file_path, sizeof(runtime_log->path));
/* Load existing log file, if it exists */
if (path_is_valid(runtime_log->path))
runtime_log_read_file(runtime_log);
/* Load existing log file, if it exists (a missing file is
* handled - silently - inside) */
runtime_log_read_file(runtime_log);
return runtime_log;
}