content: load content as a staged job the frame loop advances

The menu load ran the whole close-core/init-core/init-drivers cycle
inside one runloop_iterate, so the window went dark - or dropped to
the compositor on webOS - for the length of the read.  content_load
is now a job of three stages the frame loop steps through one per
frame: the old core closes with the drivers kept up, the new core
comes up behind them, then one driver reinit follows.  Between stages
the loop presents the cached frame under the menu, so the window
keeps drawing.

retroarch_main_init splits into a core phase and a driver phase for
this: the drivers are found in the driver phase, after the previous
session's are freed, so both sessions never hold a driver at once.
Core deinit keeps the drivers while switching, a reinit the core asks
for mid-load defers to the driver stage, and the pending menu actions
(config replace, close, core reload) wait for the frame after.
Startup and the CLI still load in one go, before any frame runs.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-09-30 00:42:48 +02:00
1 parent 4c61f9fede
commit 2db2e86ba7
8 files changed
+599 -256

No files matched your search

+7
View File
@@ -2890,6 +2890,13 @@ void command_event_reinit(const int flags)
return;
}
/* A staged content load ends in a reinit of every driver against
* the new core: one asked for before then - the old override
* unloading, the new core's SET_SYSTEM_AV_INFO - would rebuild
* the drivers the stage is about to rebuild again. */
if (runloop_is_content_switching())
return;
{
+6 -1
View File
@@ -4288,7 +4288,12 @@ void video_driver_cached_frame(void)
/* Cannot allow recording when pushing duped frames. */
recording_st->data = NULL;
if (runloop_st->current_core.flags & RETRO_CORE_FLAG_INITED)
/* A staged content load has no core between its close and the
* next core's init, and the drivers it left up present the last
* frame they were handed - a dupe - under the menu, as they do
* on the first frame after a core comes up. */
if ( (runloop_st->current_core.flags & RETRO_CORE_FLAG_INITED)
|| runloop_st->content_switching)
{
const void *data;
unsigned dims;
+149 -86
View File
@@ -8543,7 +8543,99 @@ static bool retroarch_core_info_savestate_probe(void)
&& core_serialize_size() > 0;
}
bool retroarch_main_init(int argc, char *argv[])
/* What a failed phase leaves behind: no core, no session, and the
* jmp_buf retroarch_fail() would land in dead. */
static bool retroarch_main_init_fail(global_t *global)
{
command_event(CMD_EVENT_CORE_DEINIT, NULL);
runloop_is_inited_clear();
global->flags &= ~GLOB_FLG_INIT_IN_PROGRESS;
return false;
}
/* Picks the driver of every kind from the configuration; the video,
* input and menu ones are required. Reports through retroarch_fail(),
* so it runs under a live error_sjlj_context. */
static void retroarch_find_drivers(settings_t *settings,
bool verbosity_enabled)
{
#ifdef HAVE_MENU
struct menu_state *menu_st = menu_state_get_ptr();
#endif
if (!(audio_driver_find_driver(settings->arrays.audio_driver,
"audio driver", verbosity_enabled)))
retroarch_fail(1, "audio_driver_find()");
if (!video_driver_find_driver(settings,
"video driver", verbosity_enabled))
retroarch_fail(1, "video_driver_find_driver()");
if (!input_driver_find_driver(settings,
"input driver", verbosity_enabled))
retroarch_fail(1, "input_driver_find_driver()");
if (!camera_driver_find_driver("camera driver", verbosity_enabled))
retroarch_fail(1, "find_camera_driver()");
#ifdef HAVE_BLUETOOTH
bluetooth_driver_ctl(RARCH_BLUETOOTH_CTL_FIND_DRIVER, NULL);
#endif
#ifdef HAVE_WIFI
wifi_driver_ctl(RARCH_WIFI_CTL_FIND_DRIVER, NULL);
#endif
#ifdef HAVE_CLOUDSYNC
cloud_sync_find_driver(settings->arrays.cloud_sync_driver,
"cloud sync driver", verbosity_enabled);
#endif
location_driver_find_driver(settings->arrays.location_driver,
&location_driver_st,
"location driver", verbosity_enabled);
#ifdef HAVE_MENU
{
const menu_ctx_driver_t *menu_ctx_new = menu_driver_find_driver(
settings, "menu driver", verbosity_enabled);
if (!menu_ctx_new)
retroarch_fail(1, "menu_driver_find_driver()");
/* If a menu driver instance is already allocated and the
* selected menu driver has changed since that instance was
* created - e.g. a configuration file specifying a different
* 'menu_driver' has just been loaded at runtime - the stale
* instance must be torn down here, while menu_st->driver_ctx
* still references the *old* driver (so that the correct
* free()/context_destroy() handlers are invoked on the old
* handle).
*
* Otherwise menu_driver_init() would skip (re)initialisation
* - because driver_data is non-NULL - and invoke the new
* driver's context_reset() on the old driver's handle,
* dereferencing it as the wrong type (crash). */
if ( menu_st->driver_data
&& menu_st->driver_ctx
&& (menu_st->driver_ctx != menu_ctx_new))
{
uint16_t menu_data_own = (menu_st->flags & MENU_ST_FLAG_DATA_OWN);
#ifdef HAVE_THREADS
/* Same barrier as driver_uninit(): RARCH_MENU_CTL_DEINIT runs
* the old driver's context_destroy(), which frees the textures
* and fonts an in-flight threaded frame is still drawing with.
*
* No-op when threaded video is not active. */
video_driver_state_t *video_st = video_state_get_ptr();
if ( VIDEO_DRIVER_IS_THREADED_INTERNAL(video_st)
&& video_st->thread_wrapper_active)
video_thread_wait_idle();
#endif
menu_st->flags &= ~MENU_ST_FLAG_DATA_OWN;
menu_driver_ctl(RARCH_MENU_CTL_DEINIT, NULL);
menu_st->flags |= menu_data_own;
}
menu_st->driver_ctx = menu_ctx_new;
}
#endif
}
bool retroarch_main_init_core(int argc, char *argv[],
bool find_drivers, bool *verbosity)
{
#if defined(DEBUG) && defined(HAVE_DRMINGW)
char log_file_name[128];
@@ -8555,7 +8647,6 @@ bool retroarch_main_init(int argc, char *argv[])
input_driver_state_t
*input_st = input_state_get_ptr();
settings_t *settings = config_get_ptr();
recording_state_t *rec_st = recording_state_get_ptr();
global_t *global = global_get_ptr();
#ifdef HAVE_ACCESSIBILITY
access_state_t *access_st = access_state_get_ptr();
@@ -8569,15 +8660,23 @@ bool retroarch_main_init(int argc, char *argv[])
core_info_set_savestate_probe(retroarch_core_info_savestate_probe);
input_st->osk_idx = OSK_LOWERCASE_LATIN;
video_state_get_ptr()->main_flags |= VIDEO_FLAG_ACTIVE;
AUDIO_FLAGS_SET(audio_state_get_ptr(), AUDIO_FLAG_ACTIVE);
/* The active bits answer the core's GET_AV_ENABLE during its init.
* Staged, the previous session's drivers are still up and present
* between stages, so the bits keep saying what those drivers can
* do (an audio driver that failed to start has none); the driver
* phase raises them for the new session. */
if (find_drivers)
{
video_state_get_ptr()->main_flags |= VIDEO_FLAG_ACTIVE;
AUDIO_FLAGS_SET(audio_state_get_ptr(), AUDIO_FLAG_ACTIVE);
}
if (setjmp(global->error_sjlj_context) > 0)
{
RARCH_ERR("%s: \"%s\"\n",
msg_hash_to_str(MSG_FATAL_ERROR_RECEIVED_IN),
global_get_ptr()->error_string);
goto error;
return retroarch_main_init_fail(global);
}
/* Mark error_sjlj_context as live. retroarch_fail checks this
@@ -8811,80 +8910,12 @@ bool retroarch_main_init(int argc, char *argv[])
}
}
/* Pre-initialize all drivers
* Attempts to find a default driver for
* all driver types.
*/
if (!(audio_driver_find_driver(settings->arrays.audio_driver,
"audio driver", verbosity_enabled)))
retroarch_fail(1, "audio_driver_find()");
if (!video_driver_find_driver(settings,
"video driver", verbosity_enabled))
retroarch_fail(1, "video_driver_find_driver()");
if (!input_driver_find_driver(settings,
"input driver", verbosity_enabled))
retroarch_fail(1, "input_driver_find_driver()");
/* Pre-initialize all drivers: find a driver of every kind. The
* staged load has the previous session's drivers up at this point
* and finds after freeing them, in retroarch_main_init_drivers(). */
if (find_drivers)
retroarch_find_drivers(settings, verbosity_enabled);
if (!camera_driver_find_driver("camera driver", verbosity_enabled))
retroarch_fail(1, "find_camera_driver()");
#ifdef HAVE_BLUETOOTH
bluetooth_driver_ctl(RARCH_BLUETOOTH_CTL_FIND_DRIVER, NULL);
#endif
#ifdef HAVE_WIFI
wifi_driver_ctl(RARCH_WIFI_CTL_FIND_DRIVER, NULL);
#endif
#ifdef HAVE_CLOUDSYNC
cloud_sync_find_driver(settings->arrays.cloud_sync_driver,
"cloud sync driver", verbosity_enabled);
#endif
location_driver_find_driver(settings->arrays.location_driver,
&location_driver_st,
"location driver", verbosity_enabled);
#ifdef HAVE_MENU
{
const menu_ctx_driver_t *menu_ctx_new = menu_driver_find_driver(
settings, "menu driver", verbosity_enabled);
if (!menu_ctx_new)
retroarch_fail(1, "menu_driver_find_driver()");
/* If a menu driver instance is already allocated and the
* selected menu driver has changed since that instance was
* created - e.g. a configuration file specifying a different
* 'menu_driver' has just been loaded at runtime - the stale
* instance must be torn down here, while menu_st->driver_ctx
* still references the *old* driver (so that the correct
* free()/context_destroy() handlers are invoked on the old
* handle).
*
* Otherwise menu_driver_init() would skip (re)initialisation
* - because driver_data is non-NULL - and invoke the new
* driver's context_reset() on the old driver's handle,
* dereferencing it as the wrong type (crash). */
if ( menu_st->driver_data
&& menu_st->driver_ctx
&& (menu_st->driver_ctx != menu_ctx_new))
{
uint16_t menu_data_own = (menu_st->flags & MENU_ST_FLAG_DATA_OWN);
#ifdef HAVE_THREADS
/* Same barrier as driver_uninit(): RARCH_MENU_CTL_DEINIT runs
* the old driver's context_destroy(), which frees the textures
* and fonts an in-flight threaded frame is still drawing with.
*
* No-op when threaded video is not active. */
video_driver_state_t *video_st = video_state_get_ptr();
if ( VIDEO_DRIVER_IS_THREADED_INTERNAL(video_st)
&& video_st->thread_wrapper_active)
video_thread_wait_idle();
#endif
menu_st->flags &= ~MENU_ST_FLAG_DATA_OWN;
menu_driver_ctl(RARCH_MENU_CTL_DEINIT, NULL);
menu_st->flags |= menu_data_own;
}
menu_st->driver_ctx = menu_ctx_new;
}
#endif
/* Enforce stored brightness if needed */
if (frontend_driver_can_set_screen_brightness())
frontend_driver_set_screen_brightness(settings->uints.screen_brightness);
@@ -8946,11 +8977,11 @@ bool retroarch_main_init(int argc, char *argv[])
/* Attempt initializing dummy core */
runloop_st->current_core_type = CORE_TYPE_DUMMY;
if (!command_event(CMD_EVENT_CORE_INIT, &runloop_st->current_core_type))
goto error;
return retroarch_main_init_fail(global);
}
#ifdef HAVE_DYNAMIC
else /* Fall back to regular error handling */
goto error;
return retroarch_main_init_fail(global);
#endif
}
@@ -8966,6 +8997,38 @@ bool retroarch_main_init(int argc, char *argv[])
#endif
);
#endif
/* The jmp_buf dies with this frame; the next phase arms its own. */
global->flags &= ~GLOB_FLG_INIT_IN_PROGRESS;
*verbosity = verbosity_enabled;
return true;
}
bool retroarch_main_init_drivers(bool find_drivers,
bool verbosity_enabled)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
input_driver_state_t
*input_st = input_state_get_ptr();
settings_t *settings = config_get_ptr();
recording_state_t *rec_st = recording_state_get_ptr();
global_t *global = global_get_ptr();
if (setjmp(global->error_sjlj_context) > 0)
{
RARCH_ERR("%s: \"%s\"\n",
msg_hash_to_str(MSG_FATAL_ERROR_RECEIVED_IN),
global_get_ptr()->error_string);
return retroarch_main_init_fail(global);
}
global->flags |= GLOB_FLG_INIT_IN_PROGRESS;
if (find_drivers)
{
video_state_get_ptr()->main_flags |= VIDEO_FLAG_ACTIVE;
AUDIO_FLAGS_SET(audio_state_get_ptr(), AUDIO_FLAG_ACTIVE);
retroarch_find_drivers(settings, verbosity_enabled);
}
drivers_init(settings, DRIVERS_CMD_ALL, (enum driver_lifetime_flags)0, verbosity_enabled);
#ifdef HAVE_COMMAND
input_driver_deinit_command(input_st);
@@ -9036,13 +9099,13 @@ bool retroarch_main_init(int argc, char *argv[])
global->flags &= ~GLOB_FLG_INIT_IN_PROGRESS;
return true;
}
error:
command_event(CMD_EVENT_CORE_DEINIT, NULL);
runloop_is_inited_clear();
global->flags &= ~GLOB_FLG_INIT_IN_PROGRESS;
return false;
bool retroarch_main_init(int argc, char *argv[])
{
bool verbosity_enabled = false;
return retroarch_main_init_core(argc, argv, true, &verbosity_enabled)
&& retroarch_main_init_drivers(false, verbosity_enabled);
}
#ifdef DEBUG
+22
View File
@@ -143,6 +143,28 @@ void retroarch_drain_tasks_for_exit(void);
**/
bool retroarch_main_init(int argc, char *argv[]);
/**
* retroarch_main_init_core:
* retroarch_main_init_drivers:
*
* retroarch_main_init() in two phases, for the staged content load:
* the first parses the configuration and brings the core up (dlopen,
* retro_init, the content read, retro_load_game), the second finds
* and initializes the drivers and finishes the session. The staged
* load runs the first behind the drivers of the previous session and
* frees those between the two; the drivers are then found in the
* second phase (find_drivers), where the previous session's are no
* longer in the way. Startup runs both back to back with the find
* in the first phase, ahead of the core, as it always has.
*
* Both phases report a failure that retroarch_fail() raised; the
* verbosity the first phase parsed is handed to the second.
**/
bool retroarch_main_init_core(int argc, char *argv[],
bool find_drivers, bool *verbosity_enabled);
bool retroarch_main_init_drivers(bool find_drivers,
bool verbosity_enabled);
bool retroarch_main_quit(void);
global_t *global_get_ptr(void);
+39 -13
View File
@@ -468,6 +468,11 @@ bool runloop_is_content_closing(void)
return runloop_state.content_closing;
}
bool runloop_is_content_switching(void)
{
return runloop_state.content_switching;
}
bool state_manager_frame_is_reversed(void)
{
#ifdef HAVE_REWIND
@@ -4713,7 +4718,11 @@ void runloop_event_deinit_core(void)
if (settings->bools.video_frame_delay_auto)
video_st->frame_delay_target = 0;
driver_uninit(DRIVERS_CMD_ALL, (enum driver_lifetime_flags)0);
/* A staged content load keeps the drivers up through the close
* and the next core's init, so the window keeps presenting; it
* frees them itself, right before the new session's drivers_init. */
if (!runloop_st->content_switching)
driver_uninit(DRIVERS_CMD_ALL, (enum driver_lifetime_flags)0);
#ifdef HAVE_CONFIGFILE
/* Reload the original config */
@@ -7028,7 +7037,13 @@ static enum runloop_state_enum runloop_check_state(
* free and recreate the menu driver), so it must never run from
* within menu iteration. Exit afterwards to start the next frame
* with a freshly (re)built menu. */
if (menu_st->flags & MENU_ST_FLAG_PENDING_CONFIG_REPLACE)
/* A content load in flight owns the session until it is through:
* a pending config replace or close starts another load (of the
* dummy core) and a pending core reload loads a library into a
* session the job is still building, so each waits, flag set,
* for the frame after the load. */
if ( (menu_st->flags & MENU_ST_FLAG_PENDING_CONFIG_REPLACE)
&& !runloop_st->content_switching)
{
bool config_save_on_exit = settings->bools.config_save_on_exit;
menu_st->flags &= ~MENU_ST_FLAG_PENDING_CONFIG_REPLACE;
@@ -7150,8 +7165,9 @@ static enum runloop_state_enum runloop_check_state(
menu_st->flags &= ~MENU_ST_FLAG_PENDING_STARTUP_PAGE;
return RUNLOOP_STATE_POLLED_AND_CONTINUE;
}
else if ((menu_st->flags & MENU_ST_FLAG_PENDING_CLOSE_CONTENT)
|| (menu_st->flags & MENU_ST_FLAG_PENDING_ENV_SHUTDOWN_FLUSH))
else if ( ((menu_st->flags & MENU_ST_FLAG_PENDING_CLOSE_CONTENT)
|| (menu_st->flags & MENU_ST_FLAG_PENDING_ENV_SHUTDOWN_FLUSH))
&& !runloop_st->content_switching)
{
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *menu_stack = menu_list ? MENU_LIST_GET(menu_list, (unsigned)0) : NULL;
@@ -7253,7 +7269,8 @@ static enum runloop_state_enum runloop_check_state(
}
/* Handle pending core reload separately after menu driver iterate */
if (menu_st->flags & MENU_ST_FLAG_PENDING_RELOAD_CORE)
if ( (menu_st->flags & MENU_ST_FLAG_PENDING_RELOAD_CORE)
&& !runloop_st->content_switching)
{
#ifdef HAVE_DYNAMIC
const char *a = path_get(RARCH_PATH_CORE_LAST);
@@ -8382,13 +8399,13 @@ int runloop_iterate(void)
android_input_flush_pending_haptics();
#endif
#if defined(HAVE_DYNAMIC) && defined(HAVE_MENU)
/* Perform the parked remainder of a deferred (prefetched) menu
* load. It runs here, not from the prefetch task's callback,
* because content_load() reinitializes the task queue - fatal
* from inside the queue's own dispatch. */
task_content_deferred_load_check();
#endif
/* Advance a staged content load by one stage, and take up a
* prefetched menu load whose read has completed. Here, at the
* top of the frame, rather than from a task callback: the load
* reinitializes the task queue, fatal from inside the queue's
* own dispatch, and the rest of this frame presents whatever
* the stage left on screen. */
task_content_load_check();
/* Tick deferred shader compilation (one pass per frame) */
video_driver_shader_deferred_tick();
@@ -8611,6 +8628,15 @@ int runloop_iterate(void)
#endif
goto end;
case RUNLOOP_STATE_ITERATE:
/* A staged content load has no core to run between its
* stages: poll, present what is on screen, and pace as a
* paused frame does. */
if (runloop_st->content_switching)
{
input_driver_poll();
video_driver_cached_frame();
goto end;
}
runloop_st->flags |= RUNLOOP_FLAG_CORE_RUNNING;
break;
}
@@ -9516,7 +9542,7 @@ void core_run(void)
* keeps being drawn. Same shape as the netplay-paused case
* below, for the same reason: a frame that stops being produced
* reads as a hang. */
if (runloop_st->content_closing)
if (runloop_st->content_closing || runloop_st->content_switching)
{
input_driver_poll();
video_driver_cached_frame();
+21 -14
View File
@@ -371,22 +371,22 @@ struct runloop
bool perfcnt_enable;
bool paused_hotkey;
/* True from the moment closing content starts tearing the core
* down until the teardown is finished.
/* content_closing: true while the core is being torn down, from
* the first step of closing until the last.
*
* Set and cleared around the existing synchronous teardown, so at
* present nothing can observe it as true: the main thread is
* inside that teardown for its whole duration and no frame runs.
* It is introduced separately, and deliberately inert, because
* the work that makes it observable - returning to the frame loop
* instead of blocking - is a lifecycle change, and this is the
* piece everything else will key off.
* content_switching: true while content is being replaced in
* stages from the frame loop (tasks/task_content.c): the old core
* is closed with the drivers left up, the new one is brought up
* behind them, and one driver reinit follows. Core deinit keeps
* the drivers and a reinit the core asks for defers to that stage
* while this is set, and the frame loop presents instead of
* running a core.
*
* A plain bool rather than a RUNLOOP_FLAG bit: bits 0-30 of that
* Plain bools rather than RUNLOOP_FLAG bits: bits 0-30 of that
* word are taken and bit 31 was deliberately vacated to avoid a
* cross-thread race, so reusing it would undo that reasoning for
* no gain. This is main-thread only. */
* cross-thread race. Both are main-thread only. */
bool content_closing;
bool content_switching;
};
/* Frame pacing sources.
@@ -940,11 +940,18 @@ runloop_state_t *runloop_state_get_ptr(void);
* runloop_is_content_closing:
*
* True while content is being closed, i.e. while the core is being
* torn down. Currently only ever true inside the synchronous
* teardown, where nothing else runs to ask.
* torn down.
*/
bool runloop_is_content_closing(void);
/**
* runloop_is_content_switching:
*
* True while a staged content load is in flight, from the close of
* the old core to the driver reinit that follows the new one.
*/
bool runloop_is_content_switching(void);
RETRO_END_DECLS
#endif
+345 -135
View File
@@ -2003,78 +2003,127 @@ static void content_load_init_wrap(
argv[(*argc)++] = strldup("-v", sizeof("-v"));
}
/**
* content_load:
*
* Loads content file and starts up RetroArch.
* If no content file can be loaded, will start up RetroArch
* as-is.
*
* Returns: false (0) if retroarch_main_init failed,
* otherwise true (1).
**/
static bool content_load(content_ctx_info_t *info,
content_state_t *p_content)
/* ---- the content load: a job the frame loop advances in stages ---- */
enum content_load_stage
{
CONTENT_LOAD_STAGE_NONE = 0,
/* The old session closes: SRAM written, core unloaded; the drivers
* stay up when staged. */
CONTENT_LOAD_STAGE_DEINIT,
/* The new core comes up behind those drivers: configuration,
* task queue, dlopen, retro_init, the content read, retro_load_game. */
CONTENT_LOAD_STAGE_CORE,
/* One driver reinit against the new core, then the session tail
* and whatever the entry point asked for after the load. */
CONTENT_LOAD_STAGE_DRIVERS
};
/* What an entry point wants done once the load is through. */
enum content_load_after
{
CONTENT_LOAD_AFTER_HISTORY = 1 << 0,
CONTENT_LOAD_AFTER_HISTORY_MENU = 1 << 1, /* launched from menu */
CONTENT_LOAD_AFTER_HISTORY_CLI = 1 << 2,
CONTENT_LOAD_AFTER_HISTORY_UI = 1 << 3, /* companion UI */
CONTENT_LOAD_AFTER_QUICK_MENU = 1 << 4,
CONTENT_LOAD_AFTER_QUICK_MENU_KEEP = 1 << 5, /* without a menu flush */
CONTENT_LOAD_AFTER_MENU_ON_FAIL = 1 << 6, /* retroarch_menu_running */
CONTENT_LOAD_AFTER_DUMMY_IS_FAIL = 1 << 7 /* the dummy core counts as failure */
};
struct content_load_job
{
struct rarch_main_wrap *wrap_args;
char **argv_ptr;
int *argc_ptr;
char *rarch_argv[MAX_ARGS];
char *argv_copy [MAX_ARGS];
content_ctx_info_t info;
int rarch_argc;
unsigned after;
enum content_load_stage stage;
bool staged; /* advanced from the frame loop; else run in one go */
bool verbosity;
bool ok;
};
/* One load at a time: an entry point called while a job is in flight
* is refused, as the prefetch deferral refuses a second deferral. */
static struct content_load_job content_load_job;
/* How the last job ended, for the unstaged caller's return value. */
static bool content_load_job_result;
static void content_load_job_free(struct content_load_job *job)
{
size_t i;
bool ret = false;
int rarch_argc = 0;
char *rarch_argv[MAX_ARGS] = {NULL};
char *argv_copy [MAX_ARGS] = {NULL};
char **rarch_argv_ptr = (char**)info->argv;
int *rarch_argc_ptr = (int*)&info->argc;
struct rarch_main_wrap *wrap_args = NULL;
for (i = 0; i < ARRAY_SIZE(job->argv_copy); i++)
{
free(job->argv_copy[i]);
job->argv_copy[i] = NULL;
}
free(job->wrap_args);
job->wrap_args = NULL;
job->stage = CONTENT_LOAD_STAGE_NONE;
}
if (!(wrap_args = (struct rarch_main_wrap*)
malloc(sizeof(*wrap_args))))
return false;
wrap_args->argv = NULL;
wrap_args->content_path = NULL;
wrap_args->sram_path = NULL;
wrap_args->state_path = NULL;
wrap_args->config_path = NULL;
wrap_args->libretro_path = NULL;
wrap_args->flags = 0;
wrap_args->argc = 0;
/* The close of the old session. Staged, the drivers stay up
* (runloop_event_deinit_core keys off content_switching) and keep
* presenting until the driver stage. */
static bool content_load_stage_deinit(struct content_load_job *job)
{
struct rarch_main_wrap *wrap_args = job->wrap_args;
/* The following snippet breaks command-line arguments on Haiku which in turn
prevents from using RA without a menu or to start it from a front-end like ES-DE.
All things considered, the risk/reward is favorable to just skipping this. */
#ifndef __HAIKU__
if (info->environ_get)
info->environ_get(rarch_argc_ptr,
rarch_argv_ptr, info->args, wrap_args);
if (job->info.environ_get)
job->info.environ_get(job->argc_ptr,
job->argv_ptr, job->info.args, wrap_args);
#endif
if (wrap_args->flags & RARCH_MAIN_WRAP_FLAG_TOUCHED)
{
content_load_init_wrap(wrap_args, &rarch_argc, rarch_argv);
memcpy(argv_copy, rarch_argv, sizeof(rarch_argv));
rarch_argv_ptr = (char**)rarch_argv;
rarch_argc_ptr = (int*)&rarch_argc;
content_load_init_wrap(wrap_args, &job->rarch_argc, job->rarch_argv);
memcpy(job->argv_copy, job->rarch_argv, sizeof(job->rarch_argv));
job->argv_ptr = job->rarch_argv;
job->argc_ptr = &job->rarch_argc;
}
retroarch_ctl(RARCH_CTL_MAIN_DEINIT, NULL);
wrap_args->argc = *rarch_argc_ptr;
wrap_args->argv = rarch_argv_ptr;
wrap_args->argc = *job->argc_ptr;
wrap_args->argv = job->argv_ptr;
return true;
}
ret = retroarch_main_init(wrap_args->argc, wrap_args->argv);
static bool content_load_stage_core(struct content_load_job *job)
{
/* Staged, the previous session's drivers are still up: they are
* found once freed, in the driver stage. */
return retroarch_main_init_core(job->wrap_args->argc,
job->wrap_args->argv, !job->staged, &job->verbosity);
}
for (i = 0; i < ARRAY_SIZE(argv_copy); i++)
free(argv_copy[i]);
free(wrap_args);
if (!ret)
return false;
static bool content_load_stage_drivers(struct content_load_job *job)
{
if (job->staged)
driver_uninit(DRIVERS_CMD_ALL, (enum driver_lifetime_flags)0);
return retroarch_main_init_drivers(job->staged, job->verbosity);
}
/* Everything content_load() did once retroarch_main_init() had
* returned true. */
static void content_load_tail(struct content_load_job *job,
content_state_t *p_content)
{
if (p_content->flags & CONTENT_ST_FLAG_PENDING_SUBSYSTEM_INIT)
content_clear_subsystem();
#ifdef HAVE_GFX_WIDGETS
#ifdef HAVE_CONFIGFILE
/* If retroarch_main_init() returned true, we
* can safely trigger a load content animation.
/* The load went through, so a load content animation is safe to
* trigger.
*
* Unless the deferred path already started one before the read,
* so it could carry the read percentage: restarting it here would
@@ -2115,10 +2164,159 @@ static bool content_load(content_ctx_info_t *info,
command_event(CMD_EVENT_RESUME, NULL);
command_event(CMD_EVENT_VIDEO_SET_ASPECT_RATIO, NULL);
frontend_driver_process_args(rarch_argc_ptr, rarch_argv_ptr);
frontend_driver_process_args(job->argc_ptr, job->argv_ptr);
frontend_driver_content_loaded();
}
return true;
#if defined(HAVE_DYNAMIC) && defined(HAVE_MENU)
static void content_file_prefetch_free(content_state_t *p_content);
#endif
static void task_push_to_history_list(content_state_t *p_content,
bool launched_from_menu, bool launched_from_cli,
bool launched_from_companion_ui);
/* The entry point's after-load work, and the job's end. */
static void content_load_finish(struct content_load_job *job,
content_state_t *p_content)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
unsigned after = job->after;
bool ok = job->ok;
runloop_st->content_switching = false;
if (ok)
content_load_tail(job, p_content);
/* A load that fell back to the dummy core did not start the core
* the caller asked for. */
if ( ok
&& (after & CONTENT_LOAD_AFTER_DUMMY_IS_FAIL)
&& runloop_st->current_core_type == CORE_TYPE_DUMMY)
ok = false;
if (ok)
{
if (after & CONTENT_LOAD_AFTER_HISTORY)
task_push_to_history_list(p_content,
(after & CONTENT_LOAD_AFTER_HISTORY_MENU) != 0,
(after & CONTENT_LOAD_AFTER_HISTORY_CLI) != 0,
(after & CONTENT_LOAD_AFTER_HISTORY_UI) != 0);
#ifdef HAVE_MENU
if (after & CONTENT_LOAD_AFTER_QUICK_MENU)
{
bool flush_menu = !(after & CONTENT_LOAD_AFTER_QUICK_MENU_KEEP);
menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU,
flush_menu ? NULL : &flush_menu);
}
#endif
}
#ifdef HAVE_MENU
else if (after & CONTENT_LOAD_AFTER_MENU_ON_FAIL)
retroarch_menu_running();
#endif
#if defined(HAVE_DYNAMIC) && defined(HAVE_MENU)
content_file_prefetch_free(p_content); /* leftovers, if any */
#endif
content_load_job_result = ok;
content_load_job_free(job);
}
/* Runs the job's current stage and moves it on. */
static void content_load_step(struct content_load_job *job,
content_state_t *p_content)
{
switch (job->stage)
{
case CONTENT_LOAD_STAGE_DEINIT:
content_load_stage_deinit(job);
job->stage = CONTENT_LOAD_STAGE_CORE;
break;
case CONTENT_LOAD_STAGE_CORE:
job->ok = content_load_stage_core(job);
if (!job->ok && job->staged)
{
/* Not even the dummy core: no session to build drivers
* for, and the previous session's are freed as an
* unstaged failure leaves them. */
driver_uninit(DRIVERS_CMD_ALL, (enum driver_lifetime_flags)0);
content_load_finish(job, p_content);
break;
}
if (!job->ok)
{
content_load_finish(job, p_content);
break;
}
job->stage = CONTENT_LOAD_STAGE_DRIVERS;
break;
case CONTENT_LOAD_STAGE_DRIVERS:
job->ok = content_load_stage_drivers(job);
content_load_finish(job, p_content);
break;
case CONTENT_LOAD_STAGE_NONE:
break;
}
}
/**
* content_load:
*
* Starts loading the content file and bringing the session up on
* it - or on the dummy core, if no content can be loaded.
*
* Staged, the job is advanced one stage per frame by
* task_content_load_check() and after-work runs when it is through;
* the frame loop presents between stages. Unstaged (startup), the
* whole load runs here.
*
* Returns: false (0) if the load could not be started - another is
* in flight, or no memory - otherwise true (1). Unstaged, false
* also reports a failed retroarch_main_init.
**/
static bool content_load(content_ctx_info_t *info,
content_state_t *p_content, unsigned after, bool staged)
{
struct content_load_job *job = &content_load_job;
struct rarch_main_wrap *wrap_args = NULL;
if (job->stage != CONTENT_LOAD_STAGE_NONE)
{
RARCH_LOG("[Content] A content load is already in flight.\n");
return false;
}
if (!(wrap_args = (struct rarch_main_wrap*)
malloc(sizeof(*wrap_args))))
return false;
wrap_args->argv = NULL;
wrap_args->content_path = NULL;
wrap_args->sram_path = NULL;
wrap_args->state_path = NULL;
wrap_args->config_path = NULL;
wrap_args->libretro_path = NULL;
wrap_args->flags = 0;
wrap_args->argc = 0;
memset(job, 0, sizeof(*job));
job->wrap_args = wrap_args;
job->info = *info;
job->argv_ptr = (char**)info->argv;
job->argc_ptr = (int*)&job->info.argc;
job->after = after;
job->staged = staged;
job->stage = CONTENT_LOAD_STAGE_DEINIT;
if (staged)
{
runloop_state_get_ptr()->content_switching = true;
return true;
}
while (job->stage != CONTENT_LOAD_STAGE_NONE)
content_load_step(job, p_content);
return content_load_job_result;
}
void menu_content_environment_get(int *argc, char *argv[],
@@ -2453,7 +2651,8 @@ static bool command_event_cmd_exec(
content_state_t *p_content,
const char *data,
content_information_ctx_t *content_ctx,
bool launched_from_cli
bool launched_from_cli,
unsigned after
)
{
if (path_get(RARCH_PATH_CONTENT) != data)
@@ -2473,9 +2672,13 @@ static bool command_event_cmd_exec(
content_info.environ_get = menu_content_environment_get;
/* Loads content into currently selected core. */
if (!content_load(&content_info, p_content))
if (!content_load(&content_info, p_content,
CONTENT_LOAD_AFTER_HISTORY
| CONTENT_LOAD_AFTER_HISTORY_MENU
| (launched_from_cli ? CONTENT_LOAD_AFTER_HISTORY_CLI : 0)
| after,
true))
return false;
task_push_to_history_list(p_content, true, launched_from_cli, false);
}
#else
/* Static build: the only way to run a different core is to
@@ -2527,8 +2730,8 @@ bool task_push_start_dummy_core(content_ctx_info_t *content_info)
retroarch_init_task_queue();
/* Loads content into currently selected core. */
if ((ret = content_load(content_info, p_content)))
task_push_to_history_list(p_content, false, false, false);
ret = content_load(content_info, p_content,
CONTENT_LOAD_AFTER_HISTORY, true);
content_information_ctx_free(&content_ctx);
@@ -2580,8 +2783,10 @@ bool task_push_load_content_from_playlist_from_menu(
path_set(RARCH_PATH_CONTENT, fullpath);
/* Load content and update content history */
if ((ret = content_load(content_info, p_content)))
task_push_to_history_list(p_content, true, false, false);
ret = content_load(content_info, p_content,
CONTENT_LOAD_AFTER_HISTORY
| CONTENT_LOAD_AFTER_HISTORY_MENU
| CONTENT_LOAD_AFTER_MENU_ON_FAIL, true);
goto end;
}
@@ -2608,7 +2813,8 @@ bool task_push_load_content_from_playlist_from_menu(
* > On targets that do not support dynamic core loading,
* command_event_cmd_exec() will fork a new instance */
if ((ret = command_event_cmd_exec(p_content,
fullpath, &content_ctx, false)))
fullpath, &content_ctx, false,
CONTENT_LOAD_AFTER_MENU_ON_FAIL)))
{
#ifndef HAVE_DYNAMIC
/* No dynamic core loading support: if we reach
@@ -2655,29 +2861,21 @@ bool task_push_start_current_core(content_ctx_info_t *content_info)
* load the actual content. Can differ per mode. */
runloop_set_current_core_type(CORE_TYPE_PLAIN, true);
/* Loads content into currently selected core.
* Note that 'content_load()' can fail and yet still
* return 'true'... In this case, the dummy core
* will be loaded; the 'start core' operation can
* therefore only be considered successful if the
* dummy core is not running following 'content_load()' */
if ( !(ret = content_load(content_info, p_content))
|| !(ret = (runloop_st->current_core_type != CORE_TYPE_DUMMY)))
/* Loads content into currently selected core. A load that falls
* back to the dummy core did not start this one: the menu comes
* back instead of the history entry and the Quick Menu. */
if (!(ret = content_load(content_info, p_content,
CONTENT_LOAD_AFTER_HISTORY
| CONTENT_LOAD_AFTER_HISTORY_MENU
| CONTENT_LOAD_AFTER_QUICK_MENU
| CONTENT_LOAD_AFTER_MENU_ON_FAIL
| CONTENT_LOAD_AFTER_DUMMY_IS_FAIL, true)))
{
#ifdef HAVE_MENU
retroarch_menu_running();
#endif
goto end;
}
task_push_to_history_list(p_content, true, false, false);
#ifdef HAVE_MENU
/* Push Quick Menu onto menu stack */
menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU, NULL);
#endif
end:
content_information_ctx_free(&content_ctx);
return ret;
@@ -2747,18 +2945,21 @@ bool task_push_load_contentless_core_from_menu(
command_event(CMD_EVENT_LOAD_CORE, NULL);
runloop_set_current_core_type(CORE_TYPE_PLAIN, true);
/* Loads content into currently selected core.
* Note that 'content_load()' can fail and yet still
* return 'true'... In this case, the dummy core
* will be loaded; the 'start core' operation can
* therefore only be considered successful if the
* dummy core is not running following 'content_load()' */
if ( !(ret = content_load(&content_info, p_content))
|| !(ret = (runloop_st->current_core_type != CORE_TYPE_DUMMY)))
{
menu_entries_get_last_stack(NULL, &menu_label, NULL, NULL, NULL);
if ( string_is_equal(menu_label, MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB_STR)
|| string_is_equal(menu_label, MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST_STR))
flush_menu = false;
/* Loads content into currently selected core. A load that falls
* back to the dummy core did not start this one: the menu comes
* back instead of the Quick Menu. */
if (!(ret = content_load(&content_info, p_content,
CONTENT_LOAD_AFTER_QUICK_MENU
| (flush_menu ? 0 : CONTENT_LOAD_AFTER_QUICK_MENU_KEEP)
| CONTENT_LOAD_AFTER_MENU_ON_FAIL
| CONTENT_LOAD_AFTER_DUMMY_IS_FAIL, true)))
retroarch_menu_running();
goto end;
}
#else
/* TODO/FIXME: Static builds do not support running
* a core directly from the 'command line' without
@@ -2771,13 +2972,12 @@ bool task_push_load_contentless_core_from_menu(
* future devs who may wish to implement this... */
if (!(ret = command_event_cmd_exec(p_content,
path_get(RARCH_PATH_CONTENT), &content_ctx,
false)))
false, 0)))
{
retroarch_menu_running();
goto end;
}
command_event(CMD_EVENT_QUIT, NULL);
#endif
menu_entries_get_last_stack(NULL, &menu_label, NULL, NULL, NULL);
@@ -2789,6 +2989,7 @@ bool task_push_load_contentless_core_from_menu(
menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU, &flush_menu);
end:
#endif
content_information_ctx_free(&content_ctx);
return ret;
@@ -2813,13 +3014,12 @@ struct content_deferred_menu_load
static struct content_deferred_menu_load *deferred_menu_load_ready = NULL;
/* Fires when the prefetch task completes. The remainder of the
* load cannot run here: content_load() reaches
* load cannot start here: its core stage reaches
* retroarch_init_task_queue(), which tears down and recreates the
* task queue - fatal from inside the queue's own dispatch (and,
* under the threaded queue, this may not be the main thread). So
* the continuation is parked, and runloop_iterate() performs it at
* the top of the next frame - the same call depth the synchronous
* path ran it from. */
* the continuation is parked, and task_content_load_check() starts
* the job from the top of the next frame. */
static void task_content_deferred_menu_load_done(void *ud, bool all_ok)
{
deferred_menu_load_ready = (struct content_deferred_menu_load*)ud;
@@ -2841,13 +3041,12 @@ static void content_file_prefetch_free(content_state_t *p_content)
}
/* Called once per frame from runloop_iterate(): performs the
* remainder of task_push_load_content_with_new_core_from_menu for a
* completed prefetch, byte-for-byte the sequence the synchronous
* path runs - content_load, history, quick menu. A prefetch that
* skipped files changed nothing; the load's ordinary reads cover
* whatever is not in the cache. */
void task_content_deferred_load_check(void)
/* Starts the remainder of task_push_load_content_with_new_core_from_menu
* for a completed prefetch: the load, then history and the Quick
* Menu once it is through. A prefetch that skipped files changed
* nothing; the load's ordinary reads cover whatever is not in the
* cache. */
static void task_content_deferred_load_start(void)
{
struct content_deferred_menu_load *d = deferred_menu_load_ready;
content_state_t *p_content;
@@ -2874,23 +3073,34 @@ void task_content_deferred_load_check(void)
return;
}
if (!content_load(&d->info, p_content))
#if defined(HAVE_GFX_WIDGETS)
content_load_animation_showing = d->showed_animation;
#endif
if (!content_load(&d->info, p_content,
CONTENT_LOAD_AFTER_HISTORY
| CONTENT_LOAD_AFTER_HISTORY_MENU
| (d->type != CORE_TYPE_DUMMY ? CONTENT_LOAD_AFTER_QUICK_MENU : 0)
| CONTENT_LOAD_AFTER_MENU_ON_FAIL, true))
{
content_file_prefetch_free(p_content);
retroarch_menu_running();
}
else
{
task_push_to_history_list(p_content, true, false, false);
if (d->type != CORE_TYPE_DUMMY)
menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU, NULL);
}
content_file_prefetch_free(p_content); /* leftovers, if any */
free(d->core_path);
free(d->fullpath);
free(d);
}
#endif
void task_content_load_check(void)
{
#if defined(HAVE_DYNAMIC) && defined(HAVE_MENU)
task_content_deferred_load_start();
#endif
if (content_load_job.stage != CONTENT_LOAD_STAGE_NONE)
content_load_step(&content_load_job, content_state_get_ptr());
}
#if defined(HAVE_DYNAMIC) && defined(HAVE_MENU)
#if defined(HAVE_GFX_WIDGETS)
/* Feeds the read percentage to the "Load Content" startup
* notification. Delivered on the thread that pumps the queue -
@@ -3084,10 +3294,9 @@ bool task_push_load_content_with_new_core_from_menu(
* slice per task tick, so the menu keeps running instead of
* freezing for one long read. The deferral is taken only in the
* exact shape this menu path produces (no argv, plain core type,
* no deferral already in flight); anything else keeps the
* synchronous path below, and if the prefetch cannot even be
* pushed, so does everything. The continuation performs the
* identical remainder of this function. */
* no deferral already in flight); anything else starts the load
* right away, and if the prefetch cannot even be pushed, so does
* everything. The continuation starts the identical job. */
if (task_content_defer_menu_load(p_content, runloop_st,
fullpath, type, content_info))
{
@@ -3096,29 +3305,28 @@ bool task_push_load_content_with_new_core_from_menu(
}
/* Loads content into currently selected core. */
if (!(ret = content_load(content_info, p_content)))
{
if (!(ret = content_load(content_info, p_content,
CONTENT_LOAD_AFTER_HISTORY
| CONTENT_LOAD_AFTER_HISTORY_MENU
| (type != CORE_TYPE_DUMMY ? CONTENT_LOAD_AFTER_QUICK_MENU : 0)
| CONTENT_LOAD_AFTER_MENU_ON_FAIL, true)))
retroarch_menu_running();
goto end;
}
task_push_to_history_list(p_content, true, false, false);
#else
if (!(ret = command_event_cmd_exec(p_content,
path_get(RARCH_PATH_CONTENT), &content_ctx,
false)))
false, 0)))
{
retroarch_menu_running();
goto end;
}
command_event(CMD_EVENT_QUIT, NULL);
#endif
/* Push Quick Menu onto menu stack */
if (type != CORE_TYPE_DUMMY)
menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU, NULL);
end:
#endif
content_information_ctx_free(&content_ctx);
return ret;
@@ -3129,7 +3337,8 @@ static bool task_load_content_internal(
content_ctx_info_t *content_info,
bool loading_from_menu,
bool loading_from_cli,
bool loading_from_companion_ui)
bool loading_from_companion_ui,
unsigned after)
{
content_information_ctx_t content_ctx;
content_state_t *p_content = content_state_get_ptr();
@@ -3152,10 +3361,16 @@ static bool task_load_content_internal(
}
#endif
/* Loads content into currently selected core. */
if ((ret = content_load(content_info, p_content)))
task_push_to_history_list(p_content,
true, loading_from_cli, loading_from_companion_ui);
/* Loads content into currently selected core. From the command
* line this is startup itself: no frame loop runs yet to advance
* a staged load, so it runs in one go. */
ret = content_load(content_info, p_content,
CONTENT_LOAD_AFTER_HISTORY
| CONTENT_LOAD_AFTER_HISTORY_MENU
| (loading_from_cli ? CONTENT_LOAD_AFTER_HISTORY_CLI : 0)
| (loading_from_companion_ui ? CONTENT_LOAD_AFTER_HISTORY_UI : 0)
| after,
!loading_from_cli);
content_information_ctx_free(&content_ctx);
@@ -3167,17 +3382,12 @@ static bool task_load_content_internal_wrap(
enum rarch_core_type type,
bool load_from_companion_ui)
{
/* Load content */
/* Load content; the Quick Menu once it is through. */
if (task_load_content_internal(content_info, true, false,
load_from_companion_ui))
{
#ifdef HAVE_MENU
/* Push Quick Menu onto menu stack */
if (type != CORE_TYPE_DUMMY)
menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU, NULL);
#endif
load_from_companion_ui,
(type != CORE_TYPE_DUMMY ? CONTENT_LOAD_AFTER_QUICK_MENU : 0)
| CONTENT_LOAD_AFTER_MENU_ON_FAIL))
return true;
}
#ifdef HAVE_MENU
retroarch_menu_running();
@@ -3235,7 +3445,7 @@ bool task_push_load_content_from_cli(
retro_task_callback_t cb,
void *user_data)
{
return task_load_content_internal(content_info, true, true, false);
return task_load_content_internal(content_info, true, true, false, 0);
}
bool task_push_start_builtin_core(
+10 -7
View File
@@ -73,6 +73,16 @@ bool task_push_start_current_core(content_ctx_info_t *content_info);
bool task_push_start_dummy_core(content_ctx_info_t *content_info);
/* Advances a staged content load by one stage, and starts the parked
* remainder of a prefetched menu load whose read has completed.
* Called once per frame from runloop_iterate(), outside the task
* system's dispatch: a stage reinitializes the task queue. Every
* task_push_load_* entry point below (the CLI one excepted, which is
* startup itself and loads in one go) only starts the job and
* returns; the load is done when runloop_is_content_switching()
* reads false again. */
void task_content_load_check(void);
bool task_push_load_content_with_new_core_from_companion_ui(
const char *core_path,
const char *fullpath,
@@ -92,13 +102,6 @@ bool task_push_load_content_with_new_core_from_menu(
retro_task_callback_t cb,
void *user_data);
#ifdef HAVE_DYNAMIC
/* Performs the parked remainder of a deferred (prefetched) menu
* load, if one is ready. Called once per frame from
* runloop_iterate(); content_load() reinitializes the task queue,
* so this must run outside the task system's dispatch. */
void task_content_deferred_load_check(void);
#endif
bool task_push_load_content_from_playlist_from_menu(
const char *core_path,