Files
RetroArch/runahead.c
T
LibretroAdmin c83441dfdd vulkan_common: two real C89 violations, and the pass that could not see them
gfx/common/vulkan_common.c has been failing the linux-c89 flags on
master: a declaration after a statement at the top of
vulkan_destroy_swapchain(), where the Win32 exclusive-mode release
sits above `unsigned i`, and two VkSurfaceFullScreenExclusive*
structs initialised from settings read earlier in the same function,
which C89 will not compute at load time. The declaration moves above
the #ifdef and the two structs are assigned rather than initialised;
no behaviour changes on any path.

Neither would have been caught by tools/mingw_syntax_check.sh even
when run over the whole tree, because the C89 pass did not define
HAVE_VULKAN: the file's settings members did not exist, so it failed
on those instead and the real violations never got that far. The
define joins that pass, which is what made both visible.

The decl gate added last commit then found six more files at the top
level using strlcpy through a transitive declaration - the core
option manager, the core updater list, both disk-control files, the
manual scanner and runahead - and they now include <compat/strl.h>
themselves.
2026-09-11 08:49:48 +02:00

2184 lines
69 KiB
C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2023 - Daniel De Matteis
* Copyright (C) 2018-2023 - Dan Weiss
* Copyright (C) 2022-2023 - Neil Fore
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX)
#ifndef LEGACY_WIN32
#define LEGACY_WIN32
#endif
#endif
#include <encodings/utf.h>
#include <string/stdstring.h>
#include <streams/file_stream.h>
#include <queues/task_queue.h>
#include <time/rtime.h>
#include <compat/strl.h>
#include "configuration.h"
#include "content.h"
#include "core.h"
#include "dynamic.h"
#include "driver.h"
#include "audio/audio_driver.h"
#include "gfx/video_driver.h"
#include "paths.h"
#include "runloop.h"
#include "verbosity.h"
/* ===== BEGIN runahead dense input cache =====
* The last value each input tuple returned, for the dirty comparison
* on every core poll. The common tuples - a plain joypad's sixteen
* buttons and its mask, an analog device's two sticks and sixteen
* buttons - live in a fixed table indexed by (port, index, id): a
* load, no search. Anything else - a subclassed device, a keyboard,
* a pointer - goes to the list the frontend kept, found by a walk.
* A tuple never set reads 0 from either, as it always did. */
typedef struct runahead_dense_cache
{
int16_t joypad[MAX_USERS][17]; /* ids 0..15; 16 is the mask */
int16_t analog[MAX_USERS][3][16]; /* index 0..2, id 0..15 */
} runahead_dense_cache_t;
/* Where a tuple lives in the dense cache, or NULL for the list. */
static int16_t *runahead_dense_slot(runahead_dense_cache_t *c,
unsigned port, unsigned device, unsigned index, unsigned id)
{
if (!c || port >= MAX_USERS)
return NULL;
if (device == RETRO_DEVICE_JOYPAD && index == 0)
{
if (id < 16)
return &c->joypad[port][id];
if (id == RETRO_DEVICE_ID_JOYPAD_MASK)
return &c->joypad[port][16];
return NULL;
}
if (device == RETRO_DEVICE_ANALOG && index < 3 && id < 16)
return &c->analog[port][index][id];
return NULL;
}
/* ===== END runahead dense input cache ===== */
static runahead_dense_cache_t *runahead_dense;
static int16_t input_state_get_last(unsigned port,
unsigned device, unsigned index, unsigned id)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
const int16_t *slot = runahead_dense_slot(runahead_dense, port, device, index, id);
if (slot)
return *slot;
if (runloop_st->input_state_list)
{
int i;
/* find list item */
for (i = 0; i < runloop_st->input_state_list->size; i++)
{
input_list_element *element =
(input_list_element*)runloop_st->input_state_list->data[i];
if ( (element->port == port)
&& (element->device == device)
&& (element->index == index))
{
if (id < element->state_size)
return element->state[id];
break;
}
}
}
return 0;
}
static void free_retro_ctx_load_content_info(struct
retro_ctx_load_content_info *dest)
{
if (!dest)
return;
string_list_free((struct string_list*)dest->content);
if (dest->info)
free(dest->info);
dest->info = NULL;
dest->content = NULL;
}
static struct retro_game_info* clone_retro_game_info(const
struct retro_game_info *src)
{
struct retro_game_info *dest = (struct retro_game_info*)malloc(
sizeof(struct retro_game_info));
if (!dest)
return NULL;
/* The copy task in flight copies the core binary, never this; the
* data is read only at retro_load_game on the secondary, on the
* main thread, after the task has reported ready. Unloading
* destroys the secondary - which invalidates any in-flight result
* through the copy generation - before retro_unload_game and
* before the content is freed, and a new secondary needs a new
* core_load_game, which replaces this clone. So the alias holds
* for as long as it is read.
*
* content_file_init() guarantees that all
* elements of the source retro_game_info
* struct will persist for the lifetime of
* the core. This means we do not have to
* copy any data; pointer assignment is
* sufficient */
dest->path = src->path;
dest->data = src->data;
dest->size = src->size;
dest->meta = src->meta;
return dest;
}
static struct retro_ctx_load_content_info
*clone_retro_ctx_load_content_info(
const struct retro_ctx_load_content_info *src)
{
struct retro_ctx_load_content_info *dest = NULL;
if (!src || src->special)
return NULL; /* refuse to deal with the Special field */
dest = (struct retro_ctx_load_content_info*)
malloc(sizeof(*dest));
if (!dest)
return NULL;
dest->info = NULL;
dest->content = NULL;
dest->special = NULL;
if (src->info)
dest->info = clone_retro_game_info(src->info);
if (src->content)
dest->content = string_list_clone(src->content);
return dest;
}
void runahead_set_load_content_info(void *data,
const retro_ctx_load_content_info_t *ctx)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
free_retro_ctx_load_content_info(runloop_st->load_content_info);
free(runloop_st->load_content_info);
runloop_st->load_content_info = clone_retro_ctx_load_content_info(ctx);
}
/* RUNAHEAD - SECONDARY CORE
*
* A secondary instance is a second copy of the core's binary loaded
* beside the first, so it exists only where the core is a dynamic
* library: HAVE_DYNAMIC. A build that can load libraries but links
* its core statically (HAVE_DYLIB without HAVE_DYNAMIC) has no binary
* to copy, and every part of the secondary path is compiled out of it
* together - creation, teardown, deserialize, and the run loop's use
* of it - so run-ahead there is the single-instance method
* throughout. Every gate below is the same test. */
#if defined(HAVE_DYNAMIC)
/* enum runahead_copy_status lives in runloop.h (shared with the
* secondary_core_ensure_exists() callers) */
static void runahead_copy_reset(bool delete_file);
static void strcat_alloc(char **dst, const char *s)
{
char *tmp;
size_t dst_len, s_len, new_len;
if (!dst)
return;
if (!*dst)
{
if (s)
{
dst_len = strlen(s);
tmp = (char*)malloc(dst_len + 1);
if (tmp)
memcpy(tmp, s, dst_len + 1);
*dst = tmp;
}
else
*dst = (char*)calloc(1, 1);
return;
}
if (!s || !*s)
return;
dst_len = strlen(*dst);
s_len = strlen(s);
new_len = dst_len + s_len + 1;
tmp = (char*)realloc(*dst, new_len);
if (!tmp)
return;
*dst = tmp;
strlcpy(tmp + dst_len, s, s_len + 1);
}
void runahead_secondary_core_destroy(void *data)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
/* Drop any unconsumed async copy result (deleting its temp
* file) and tell an in-flight copy task to discard its result;
* this runs regardless of whether a secondary instance was
* ever actually created. */
runahead_copy_reset(true);
if (!runloop_st->secondary_lib_handle)
return;
/* unload game from core */
if (runloop_st->secondary_core.retro_unload_game)
runloop_st->secondary_core.retro_unload_game();
runloop_st->core_poll_type_override = POLL_TYPE_OVERRIDE_DONTCARE;
/* deinit */
if (runloop_st->secondary_core.retro_deinit)
runloop_st->secondary_core.retro_deinit();
memset(&runloop_st->secondary_core, 0, sizeof(struct retro_core_t));
dylib_close(runloop_st->secondary_lib_handle);
runloop_st->secondary_lib_handle = NULL;
/* Delete the on-disk copy of the secondary core and free the
* path string. The NULL check guards both: filestream_delete
* is currently NULL-safe but the explicit guard here also
* documents the intent and protects against future changes
* to the VFS layer's NULL handling. */
if (runloop_st->secondary_library_path)
{
filestream_delete(runloop_st->secondary_library_path);
free(runloop_st->secondary_library_path);
runloop_st->secondary_library_path = NULL;
}
}
static char *get_tmpdir_alloc(const char *override_dir)
{
char *path = NULL;
#ifdef _WIN32
#ifdef LEGACY_WIN32
DWORD plen = GetTempPath(0, NULL) + 1;
if (!(path = (char*)malloc(plen * sizeof(char))))
return NULL;
path[plen - 1] = 0;
GetTempPath(plen, path);
#else
DWORD plen = GetTempPathW(0, NULL) + 1;
wchar_t *wide_str = (wchar_t*)malloc(plen * sizeof(wchar_t));
if (!wide_str)
return NULL;
wide_str[plen - 1] = 0;
GetTempPathW(plen, wide_str);
path = utf16_to_utf8_string_alloc(wide_str);
free(wide_str);
#endif
#else
const char *src = NULL;
#if defined ANDROID
src = override_dir;
#else
{
char *tmpdir = getenv("TMPDIR");
if (tmpdir)
src = tmpdir;
else
src = "/tmp";
}
#endif
if (src)
{
size_t _len = strlen(src);
if (_len != 0)
path = strldup(src, _len + 1);
}
else
path = (char*)calloc(1,1);
#endif
return path;
}
/* ===== BEGIN runahead core-copy fragment =====
* Everything between the BEGIN/END markers depends only on
* libretro-common (filestream, task_queue, path) plus libc and the
* static helpers above, so a test harness can extract and execute
* it verbatim.
*
* Duplicating the core binary for the secondary instance is pure
* file IO sized by the core (MAME and friends run hundreds of
* megabytes), so it must not run synchronously on the thread that
* drives frames. The copy runs as a task; while it is in flight,
* secondary_core_create() reports 'pending' and run-ahead falls
* back to the single-instance savestate method for those frames -
* identical output, no stall - then upgrades to the secondary
* instance when the copy completes. All slot state below is
* touched only from the main thread (the task callback runs on the
* main thread); the worker touches only its own task state. */
#define RUNAHEAD_COPY_CHUNK_SIZE (1 * 1024 * 1024)
/* Result slot, published by the task callback (main thread) */
static char *runahead_copy_slot_path = NULL;
static char *runahead_copy_slot_src = NULL; /* identity of the copy */
static bool runahead_copy_slot_done = false;
static bool runahead_copy_slot_failed = false;
/* Bumped by runahead_copy_reset(); a task publishes its result only
* if its recorded generation still matches, otherwise it discards
* it (deleting the temp file). This covers every teardown window,
* including the race where the worker has already finished but the
* main-thread callback has not yet run - a plain 'in flight?' check
* misses that window. All reads/writes happen on the main thread
* (the handler never touches it). */
static unsigned runahead_copy_generation = 0;
/* True from a successful task push until that task's main-thread
* callback has run. This must NOT be derived from
* task_queue_find(): the threaded queue has a window where the
* worker has finished (task no longer in the running list) but the
* callback has not yet executed - a find-based check reports 'not
* in flight' there, and a concurrent poll would push a second copy
* task racing the first onto the same destination path. The flag
* transitions strictly on the main thread (push / callback), so no
* such window exists. */
static bool runahead_copy_task_pending = false;
typedef struct runahead_copy_handle
{
char *src_path;
char *dir_libretro;
char *out_path; /* produced temp file on success */
bool failed;
unsigned generation;
} runahead_copy_handle_t;
static bool runahead_copy_file_chunked(const char *src_path,
const char *dst_path)
{
RFILE *src = NULL;
RFILE *dst = NULL;
char *buf = NULL;
bool okay = false;
if (!(buf = (char*)malloc(RUNAHEAD_COPY_CHUNK_SIZE)))
return false;
if (!(src = filestream_open(src_path,
RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
goto end;
if (!(dst = filestream_open(dst_path,
RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
goto end;
for (;;)
{
int64_t n = filestream_read(src, buf, RUNAHEAD_COPY_CHUNK_SIZE);
if (n < 0)
goto end;
if (n == 0)
break;
if (filestream_write(dst, buf, n) != n)
goto end;
}
okay = true;
end:
if (src)
filestream_close(src);
if (dst)
{
filestream_close(dst);
/* Don't leave a truncated copy behind on failure */
if (!okay)
filestream_delete(dst_path);
}
free(buf);
return okay;
}
static bool copy_file_with_random_name(char **temp_dll_path,
const char *tmp_path, const char *src_dll_path)
{
int i;
char number_buf[32];
bool okay = false;
const char *prefix = "tmp";
char *ext = NULL;
time_t time_value = time(NULL);
/* The generator's state, advanced for every candidate: unsigned,
* so the wrap is defined. */
uint32_t lcg = (uint32_t)time_value;
const char *src = path_get_extension(*temp_dll_path);
if (src)
{
size_t _len = strlen(src);
if (_len != 0)
ext = strldup(src, _len + 1);
}
else
ext = (char*)calloc(1,1);
if (ext)
{
size_t ext_len = strlen(ext);
if (ext_len > 0)
{
strcat_alloc(&ext, ".");
memmove(ext + 1, ext, ext_len);
ext[0] = '.';
ext_len++;
}
}
/* Try up to 30 'random' filenames before giving up */
for (i = 0; i < 30; i++)
{
unsigned number;
lcg = lcg * 214013u + 2531011u;
number = (lcg >> 14) % 100000u;
snprintf(number_buf, sizeof(number_buf), "%05u", number);
if (*temp_dll_path)
free(*temp_dll_path);
*temp_dll_path = NULL;
strcat_alloc(temp_dll_path, tmp_path);
strcat_alloc(temp_dll_path, PATH_DEFAULT_SLASH());
strcat_alloc(temp_dll_path, prefix);
strcat_alloc(temp_dll_path, number_buf);
strcat_alloc(temp_dll_path, ext);
if (runahead_copy_file_chunked(src_dll_path, *temp_dll_path))
{
okay = true;
break;
}
}
if (ext)
free(ext);
ext = NULL;
return okay;
}
/* Worker: builds the temp path and performs the chunked copy into
* the task's own state. No shared state is touched here. */
static void runahead_copy_task_handler(retro_task_t *task)
{
if (task)
{
runahead_copy_handle_t *h = (runahead_copy_handle_t*)task->state;
if (h)
{
char tmp_path[PATH_MAX_LENGTH];
char *tmpdir = NULL;
char *dst = NULL;
const char *core_base = path_basename_nocompression(h->src_path);
h->failed = true;
if ( core_base && *core_base
&& (tmpdir = get_tmpdir_alloc(h->dir_libretro)))
{
fill_pathname_join_special(tmp_path,
tmpdir, "retroarch_temp", sizeof(tmp_path));
if (path_mkdir(tmp_path))
{
strcat_alloc(&dst, tmp_path);
strcat_alloc(&dst, PATH_DEFAULT_SLASH());
strcat_alloc(&dst, core_base);
if (runahead_copy_file_chunked(h->src_path, dst))
{
h->out_path = dst;
h->failed = false;
dst = NULL;
}
else if (copy_file_with_random_name(&dst,
tmp_path, h->src_path))
{
h->out_path = dst;
h->failed = false;
dst = NULL;
}
}
}
if (dst)
free(dst);
if (tmpdir)
free(tmpdir);
}
task_set_flags(task, RETRO_TASK_FLG_FINISHED, true);
}
}
/* Main thread: publish the result to the slot - unless teardown
* abandoned the copy in the meantime, in which case discard it. */
static void runahead_copy_task_cb(retro_task_t *task,
void *task_data, void *user_data, const char *err)
{
runahead_copy_handle_t *h = NULL;
if (!task)
return;
if (!(h = (runahead_copy_handle_t*)task->state))
return;
/* The pipeline is free again in every case below - including
* the discard path: that task is done. */
runahead_copy_task_pending = false;
if (h->generation != runahead_copy_generation)
{
/* Teardown happened while this copy was running (or after it
* finished but before this callback ran): discard. */
if (h->out_path)
filestream_delete(h->out_path);
return;
}
runahead_copy_slot_path = h->out_path;
h->out_path = NULL;
runahead_copy_slot_src = h->src_path;
h->src_path = NULL;
runahead_copy_slot_failed = h->failed;
runahead_copy_slot_done = true;
}
static void runahead_copy_task_free(retro_task_t *task)
{
runahead_copy_handle_t *h = NULL;
if (!task)
return;
if ((h = (runahead_copy_handle_t*)task->state))
{
if (h->src_path)
free(h->src_path);
if (h->dir_libretro)
free(h->dir_libretro);
if (h->out_path)
free(h->out_path);
free(h);
}
task->state = NULL;
}
static bool runahead_copy_in_flight(void)
{
return runahead_copy_task_pending;
}
/* Main thread. Resets the result slot; if a copy is in flight, the
* callback is told to discard its result. delete_file also removes
* an already-published (but unconsumed) temp file. */
static void runahead_copy_reset(bool delete_file)
{
if (runahead_copy_slot_path)
{
if (delete_file)
filestream_delete(runahead_copy_slot_path);
free(runahead_copy_slot_path);
runahead_copy_slot_path = NULL;
}
if (runahead_copy_slot_src)
{
free(runahead_copy_slot_src);
runahead_copy_slot_src = NULL;
}
runahead_copy_slot_done = false;
runahead_copy_slot_failed = false;
/* Invalidate any copy that is in flight or awaiting its
* callback; see runahead_copy_generation. Note that
* runahead_copy_task_pending is deliberately NOT cleared here:
* a superseded task still occupies the pipeline until its
* callback runs, which keeps poll from starting an overlapping
* copy to the same destination path. */
runahead_copy_generation++;
}
/* Main thread. Polls / advances the async copy:
* - no result and no task -> push the task, report PENDING
* - task in flight -> PENDING
* - finished with failure -> UNAVAILABLE (slot reset, so a later
* attempt starts a fresh copy)
* - finished ok -> READY; ownership of the temp path is
* transferred to *out_path */
static enum runahead_copy_status runahead_copy_poll(
const char *core_path, const char *dir_libretro,
char **out_path)
{
/* A published result for a different core binary is stale
* (e.g. core switched without an intervening teardown, or any
* path we have not anticipated): drop it and start over. */
if ( runahead_copy_slot_done
&& !string_is_equal(runahead_copy_slot_src ? runahead_copy_slot_src : "",
core_path ? core_path : ""))
runahead_copy_reset(true);
if (!runahead_copy_slot_done)
{
if (!runahead_copy_in_flight())
{
retro_task_t *task = NULL;
runahead_copy_handle_t *h = NULL;
task = task_init();
h = (runahead_copy_handle_t*)calloc(1, sizeof(*h));
if (!task || !h)
{
if (task)
free(task);
if (h)
free(h);
return RUNAHEAD_COPY_UNAVAILABLE;
}
h->src_path = strdup(core_path);
h->dir_libretro = dir_libretro ? strdup(dir_libretro) : NULL;
h->generation = runahead_copy_generation;
task->handler = runahead_copy_task_handler;
task->state = h;
task->title = NULL;
task->callback = runahead_copy_task_cb;
task->cleanup = runahead_copy_task_free;
task->flags |= RETRO_TASK_FLG_MUTE;
runahead_copy_task_pending = true;
task_queue_push(task);
}
return RUNAHEAD_COPY_PENDING;
}
if (runahead_copy_slot_failed)
{
runahead_copy_reset(false);
return RUNAHEAD_COPY_UNAVAILABLE;
}
*out_path = runahead_copy_slot_path;
runahead_copy_slot_path = NULL;
runahead_copy_reset(false);
return RUNAHEAD_COPY_READY;
/* note: the generation bump in the reset above also invalidates
* any concurrent task, which cannot exist here (poll is the only
* pusher and the slot was occupied) - harmless */
}
/* ===== END runahead core-copy fragment ===== */
static bool runloop_environment_secondary_core_hook(
unsigned cmd, void *data)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
bool result = runloop_environment_cb(cmd, data);
if (runloop_st->flags & RUNLOOP_FLAG_HAS_VARIABLE_UPDATE)
{
if (cmd == RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE)
{
bool *bool_p = (bool*)data;
*bool_p = true;
runloop_st->flags &= ~RUNLOOP_FLAG_HAS_VARIABLE_UPDATE;
return true;
}
else if (cmd == RETRO_ENVIRONMENT_GET_VARIABLE)
runloop_st->flags &= ~RUNLOOP_FLAG_HAS_VARIABLE_UPDATE;
}
return result;
}
void runahead_clear_controller_port_map(void *data)
{
int i;
runloop_state_t *runloop_st = (runloop_state_t*)data;
for (i = 0; i < MAX_USERS; i++)
runloop_st->port_map[i] = -1;
}
static enum runahead_copy_status secondary_core_create(
runloop_state_t *runloop_st,
const char *path_directory_libretro, unsigned num_active_users)
{
enum runahead_copy_status copy_status;
char *copied_path = NULL;
const enum rarch_core_type
last_core_type = runloop_st->last_core_type;
rarch_system_info_t *sys_info = &runloop_st->system;
uint8_t flags = content_get_flags();
if ( (last_core_type != CORE_TYPE_PLAIN)
|| (!runloop_st->load_content_info)
|| ( runloop_st->load_content_info->special))
return RUNAHEAD_COPY_UNAVAILABLE;
/* The core binary is duplicated by a task; until the copy
* completes this reports PENDING and the caller runs the
* single-instance fallback for the frame - no stall. */
copy_status = runahead_copy_poll(
path_get(RARCH_PATH_CORE), path_directory_libretro,
&copied_path);
if (copy_status != RUNAHEAD_COPY_READY)
return copy_status;
if (runloop_st->secondary_library_path)
free(runloop_st->secondary_library_path);
runloop_st->secondary_library_path = copied_path;
/* Load Core */
if (!runloop_init_libretro_symbols(runloop_st,
CORE_TYPE_PLAIN, &runloop_st->secondary_core,
runloop_st->secondary_library_path,
&runloop_st->secondary_lib_handle))
return false;
runloop_st->secondary_core.flags |= RETRO_CORE_FLAG_SYMBOLS_INITED;
runloop_st->secondary_core.retro_set_environment(
runloop_environment_secondary_core_hook);
runloop_st->flags |= RUNLOOP_FLAG_HAS_VARIABLE_UPDATE;
runloop_st->secondary_core.retro_init();
if (flags & CONTENT_ST_FLAG_IS_INITED)
runloop_st->secondary_core.flags |= RETRO_CORE_FLAG_INITED;
else
runloop_st->secondary_core.flags &= ~RETRO_CORE_FLAG_INITED;
/* Load Content */
/* disabled due to crashes */
if ( (!runloop_st->load_content_info)
|| (runloop_st->load_content_info->special))
return false;
if ( ( runloop_st->load_content_info->content->size > 0)
&& runloop_st->load_content_info->content->elems[0].data)
{
if (!runloop_st->secondary_core.retro_load_game(
runloop_st->load_content_info->info))
{
runloop_st->secondary_core.flags &= ~RETRO_CORE_FLAG_GAME_LOADED;
goto error;
}
runloop_st->secondary_core.flags |= RETRO_CORE_FLAG_GAME_LOADED;
}
else if (flags & CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT)
{
if (!runloop_st->secondary_core.retro_load_game(NULL))
{
runloop_st->secondary_core.flags &= ~RETRO_CORE_FLAG_GAME_LOADED;
goto error;
}
runloop_st->secondary_core.flags |= RETRO_CORE_FLAG_GAME_LOADED;
}
else
runloop_st->secondary_core.flags &= ~RETRO_CORE_FLAG_GAME_LOADED;
if (!(runloop_st->secondary_core.flags & RETRO_CORE_FLAG_INITED))
goto error;
core_set_default_callbacks(&runloop_st->secondary_callbacks);
runloop_st->secondary_core.retro_set_video_refresh(
runloop_st->secondary_callbacks.frame_cb);
runloop_st->secondary_core.retro_set_audio_sample(
runloop_st->secondary_callbacks.sample_cb);
runloop_st->secondary_core.retro_set_audio_sample_batch(
runloop_st->secondary_callbacks.sample_batch_cb);
runloop_st->secondary_core.retro_set_input_state(
runloop_st->secondary_callbacks.state_cb);
runloop_st->secondary_core.retro_set_input_poll(
runloop_st->secondary_callbacks.poll_cb);
if (sys_info)
{
ssize_t port;
for (port = 0; port < MAX_USERS; port++)
{
if (port < (ssize_t)sys_info->ports.size)
{
unsigned device = (port < (ssize_t)num_active_users)
? runloop_st->port_map[port]
: RETRO_DEVICE_NONE;
runloop_st->secondary_core.retro_set_controller_port_device(
(unsigned)port, device);
}
}
}
#if defined(HAVE_DYNAMIC)
runahead_clear_controller_port_map(runloop_st);
#endif
return RUNAHEAD_COPY_READY;
error:
runahead_secondary_core_destroy(runloop_st);
return RUNAHEAD_COPY_UNAVAILABLE;
}
#if defined(HAVE_DYNAMIC)
enum runahead_copy_status secondary_core_ensure_exists(void *data,
settings_t *settings)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
const char *path_directory_libretro = settings->paths.directory_libretro;
unsigned input_max_users = settings->uints.input_max_users;
if (!runloop_st->secondary_lib_handle)
return secondary_core_create(runloop_st, path_directory_libretro,
input_max_users);
return RUNAHEAD_COPY_READY;
}
#endif
#if defined(HAVE_DYNAMIC)
static bool secondary_core_deserialize(runloop_state_t *runloop_st,
settings_t *settings, const void *data, size_t len)
{
bool ret = false;
enum runahead_copy_status status =
secondary_core_ensure_exists(runloop_st, settings);
if (status == RUNAHEAD_COPY_READY)
{
runloop_st->flags |= RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE;
ret = runloop_st->secondary_core.retro_unserialize(data, len);
runloop_st->flags &= ~RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE;
}
else if (status == RUNAHEAD_COPY_UNAVAILABLE)
runahead_secondary_core_destroy(runloop_st);
/* PENDING: copy still in flight - report failure quietly and
* leave the task running; do NOT destroy (that would abandon
* the copy and restart it on the next call). */
return ret;
}
#endif
static void secondary_core_input_poll_null(void) { }
static bool secondary_core_run_use_last_input(runloop_state_t *runloop_st)
{
retro_input_poll_t old_poll_function;
retro_input_state_t old_input_function;
{
enum runahead_copy_status status =
secondary_core_ensure_exists(runloop_st, config_get_ptr());
if (status != RUNAHEAD_COPY_READY)
{
if (status == RUNAHEAD_COPY_UNAVAILABLE)
runahead_secondary_core_destroy(runloop_st);
/* PENDING: leave the copy task running */
return false;
}
}
old_poll_function = runloop_st->secondary_callbacks.poll_cb;
old_input_function = runloop_st->secondary_callbacks.state_cb;
runloop_st->secondary_callbacks.poll_cb = secondary_core_input_poll_null;
runloop_st->secondary_callbacks.state_cb = input_state_get_last;
runloop_st->secondary_core.retro_set_input_poll(
runloop_st->secondary_callbacks.poll_cb);
runloop_st->secondary_core.retro_set_input_state(
runloop_st->secondary_callbacks.state_cb);
runloop_st->secondary_core.retro_run();
runloop_st->secondary_callbacks.poll_cb = old_poll_function;
runloop_st->secondary_callbacks.state_cb = old_input_function;
runloop_st->secondary_core.retro_set_input_poll(
runloop_st->secondary_callbacks.poll_cb);
runloop_st->secondary_core.retro_set_input_state(
runloop_st->secondary_callbacks.state_cb);
return true;
}
void runahead_remember_controller_port_device(void *data,
long port, long device)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
if (port >= 0 && port < MAX_USERS)
runloop_st->port_map[port] = (int)device;
if ( runloop_st->secondary_lib_handle
&& runloop_st->secondary_core.retro_set_controller_port_device)
runloop_st->secondary_core.retro_set_controller_port_device((unsigned)port, (unsigned)device);
}
#else
/* No secondary instance in this build: the callers that would use
* one get 'unavailable' and stay on the single-instance method, and
* the port map they keep for it has nothing to remember. */
void runahead_secondary_core_destroy(void *data) { (void)data; }
enum runahead_copy_status secondary_core_ensure_exists(void *data,
settings_t *settings)
{
(void)data; (void)settings;
return RUNAHEAD_COPY_UNAVAILABLE;
}
void runahead_clear_controller_port_map(void *data) { (void)data; }
void runahead_remember_controller_port_device(void *data,
long port, long device)
{
(void)data; (void)port; (void)device;
}
#endif
static void mylist_resize(my_list *list,
int new_size, bool run_constructor)
{
int i;
int new_capacity;
int old_size;
void *element = NULL;
if (new_size < 0)
new_size = 0;
new_capacity = new_size;
old_size = list->size;
if (new_size == old_size)
return;
if (new_size > list->capacity)
{
void **new_data;
if (new_capacity < list->capacity * 2)
new_capacity = list->capacity * 2;
/* Try to realloc. On OOM, leave the list at its current
* capacity and silently no-op the resize - the caller
* (mylist_add_element) tolerates list->data[old_size]
* being NULL: runahead_input_state_set_last guards on
* 'if (element)' before writing to it, and downstream
* code already accepts that runahead state may be
* incomplete. */
new_data = (void**)realloc(
(void*)list->data, new_capacity * sizeof(void*));
if (!new_data)
return;
list->data = new_data;
for (i = list->capacity; i < new_capacity; i++)
list->data[i] = NULL;
list->capacity = new_capacity;
}
if (new_size <= list->size)
{
for (i = new_size; i < list->size; i++)
{
element = list->data[i];
if (element)
{
list->destructor(element);
list->data[i] = NULL;
}
}
}
else
{
for (i = list->size; i < new_size; i++)
{
list->data[i] = NULL;
if (run_constructor)
list->data[i] = list->constructor();
}
}
list->size = new_size;
}
static void *mylist_add_element(my_list *list)
{
int old_size;
if (!list)
return NULL;
old_size = list->size;
mylist_resize(list, old_size + 1, true);
/* mylist_resize may have failed to grow on OOM, in which case
* list->size is still old_size and list->data[old_size] is
* out of bounds. Re-check before returning. */
if (list->size <= old_size)
return NULL;
return list->data[old_size];
}
static void mylist_destroy(my_list **list_p)
{
my_list *list = NULL;
if (!list_p)
return;
list = *list_p;
if (list)
{
mylist_resize(list, 0, false);
free(list->data);
free(list);
*list_p = NULL;
}
}
static void mylist_create(my_list **list_p, int initial_capacity,
constructor_t constructor, destructor_t destructor)
{
my_list *list = NULL;
if (!list_p)
return;
list = *list_p;
if (list)
mylist_destroy(list_p);
list = (my_list*)malloc(sizeof(my_list));
if (!list)
{
*list_p = NULL;
return;
}
*list_p = list;
list->size = 0;
list->constructor = constructor;
list->destructor = destructor;
list->data = (void**)calloc(initial_capacity, sizeof(void*));
/* On calloc OOM, leave list->data NULL and capacity 0;
* mylist_resize's realloc grows from there on first add and
* a subsequent realloc(NULL, n) is well-defined. */
list->capacity = list->data ? initial_capacity : 0;
}
static void *input_list_element_constructor(void)
{
void *ptr = malloc(sizeof(input_list_element));
input_list_element *element;
/* NULL-check the outer malloc: the field writes below
* (port/device/index/state_size) would NULL-deref on OOM.
* The caller (runahead_input_state_set_last) already tolerates
* a NULL return: 'if (element) { ... }'. mylist_resize also
* tolerates a NULL constructor result - it just leaves
* list->data[i] = NULL. */
if (!ptr)
return NULL;
element = (input_list_element*)ptr;
element->port = 0;
element->device = 0;
element->index = 0;
element->state = (int16_t*)calloc(NAME_MAX_LENGTH,
sizeof(int16_t));
/* NULL-check the inner calloc too. If it fails the caller's
* 'element->state[id] = value' NULL-derefs. No way to signal
* partial-failure through the constructor callback's return
* type, so free the outer allocation and return NULL to match
* the outer-OOM behaviour. */
if (!element->state)
{
free(ptr);
return NULL;
}
element->state_size = NAME_MAX_LENGTH;
return ptr;
}
static bool input_list_element_realloc(input_list_element *element,
unsigned int new_size)
{
if (new_size > element->state_size)
{
/* realloc-to-tmp: the pre-patch 'element->state = realloc(
* element->state, ...)' self-assigns NULL on OOM, which
* then made the very next line '&element->state[element->
* state_size]' perform pointer arithmetic on NULL (UB) and
* the memset trap on a garbage address. */
int16_t *tmp = (int16_t*)realloc(element->state,
new_size * sizeof(int16_t));
if (!tmp)
return false;
element->state = tmp;
memset(&element->state[element->state_size], 0,
(new_size - element->state_size) * sizeof(int16_t));
element->state_size = new_size;
}
return true;
}
static bool input_list_element_expand(input_list_element *element,
unsigned int new_index)
{
unsigned int new_size = element->state_size;
if (new_size == 0)
new_size = 32;
while (new_index >= new_size)
new_size *= 2;
return input_list_element_realloc(element, new_size);
}
static void input_list_element_destructor(void* element_ptr)
{
input_list_element *element = (input_list_element*)element_ptr;
if (!element)
return;
free(element->state);
free(element_ptr);
}
static void runahead_input_state_set_last(
runloop_state_t *runloop_st,
unsigned port, unsigned device,
unsigned index, unsigned id, int16_t value)
{
size_t i;
input_list_element *element = NULL;
int16_t *slot = runahead_dense_slot(runahead_dense, port, device, index, id);
if (slot)
{
*slot = value;
return;
}
if (!runloop_st->input_state_list)
mylist_create(&runloop_st->input_state_list, 16,
input_list_element_constructor,
input_list_element_destructor);
/* Find list item */
for (i = 0; i < (unsigned)runloop_st->input_state_list->size; i++)
{
element = (input_list_element*)runloop_st->input_state_list->data[i];
if ( (element->port == port)
&& (element->device == device)
&& (element->index == index)
)
{
/* Gate the state[id] write on expand success: if expand
* OOM'd, state_size is still too small and writing to
* state[id] would corrupt memory past the buffer. */
if (id >= element->state_size
&& !input_list_element_expand(element, id))
return;
element->state[id] = value;
return;
}
}
element = NULL;
if (runloop_st->input_state_list)
element = (input_list_element*)
mylist_add_element(runloop_st->input_state_list);
if (element)
{
element->port = port;
element->device = device;
element->index = index;
/* Same expand-OOM guard as the lookup branch above. */
if (id >= element->state_size
&& !input_list_element_expand(element, id))
return;
element->state[id] = value;
}
}
static int16_t runahead_input_state_with_logging(unsigned port,
unsigned device, unsigned index, unsigned id)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
if (runloop_st->input_state_callback_original)
{
int16_t result =
runloop_st->input_state_callback_original(
port, device, index, id);
int16_t last_input =
input_state_get_last(port, device, index, id);
if (result != last_input)
runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY;
/*arbitrary limit of up to 65536 elements in state array*/
if (id < 65536)
runahead_input_state_set_last(runloop_st, port, device, index, id, result);
return result;
}
return 0;
}
static void runahead_reset_hook(void)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY;
if (runloop_st->retro_reset_callback_original)
runloop_st->retro_reset_callback_original();
}
static bool runahead_unserialize_hook(const void *buf, size_t len)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY;
if (runloop_st->retro_unserialize_callback_original)
return runloop_st->retro_unserialize_callback_original(buf, len);
return false;
}
static void runahead_add_input_state_hook(runloop_state_t *runloop_st)
{
struct retro_callbacks *cbs = &runloop_st->retro_ctx;
if (!runloop_st->input_state_callback_original)
{
/* Allocated once, here; the polls that follow allocate nothing
* for the common tuples. Without it, everything takes the list. */
if (!runahead_dense)
runahead_dense = (runahead_dense_cache_t*)calloc(1, sizeof(*runahead_dense));
runloop_st->input_state_callback_original = cbs->state_cb;
cbs->state_cb = runahead_input_state_with_logging;
runloop_st->current_core.retro_set_input_state(cbs->state_cb);
}
if (!runloop_st->retro_reset_callback_original)
{
runloop_st->retro_reset_callback_original
= runloop_st->current_core.retro_reset;
runloop_st->current_core.retro_reset = runahead_reset_hook;
}
if (!runloop_st->retro_unserialize_callback_original)
{
runloop_st->retro_unserialize_callback_original = runloop_st->current_core.retro_unserialize;
runloop_st->current_core.retro_unserialize = runahead_unserialize_hook;
}
}
static void runahead_remove_input_state_hook(runloop_state_t *runloop_st)
{
struct retro_callbacks *cbs = &runloop_st->retro_ctx;
if (runloop_st->input_state_callback_original)
{
cbs->state_cb =
runloop_st->input_state_callback_original;
runloop_st->current_core.retro_set_input_state(cbs->state_cb);
runloop_st->input_state_callback_original = NULL;
mylist_destroy(&runloop_st->input_state_list);
if (runahead_dense)
{
free(runahead_dense);
runahead_dense = NULL;
}
}
if (runloop_st->retro_reset_callback_original)
{
runloop_st->current_core.retro_reset =
runloop_st->retro_reset_callback_original;
runloop_st->retro_reset_callback_original = NULL;
}
if (runloop_st->retro_unserialize_callback_original)
{
runloop_st->current_core.retro_unserialize =
runloop_st->retro_unserialize_callback_original;
runloop_st->retro_unserialize_callback_original = NULL;
}
}
static bool runahead_savestate_info_init(
runloop_state_t *runloop_st,
size_t save_state_size)
{
retro_ctx_serialize_info_t *info = &runloop_st->runahead_savestate_info;
runloop_st->runahead_save_state_size = save_state_size;
runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_SAVE_STATE_SIZE_KNOWN;
/* Free any previous buffer so callers can safely re-init. The
* matching invariant (data NULL when nothing allocated) lets the
* per-frame save_state/load_state path use the buffer pointer as
* its readiness check. */
free(info->data);
info->data = NULL;
info->data_const = NULL;
info->size = 0;
if (save_state_size == 0)
return false;
info->data = malloc(save_state_size);
if (!info->data)
return false;
info->data_const = info->data;
info->size = save_state_size;
return true;
}
static void runahead_savestate_info_free(runloop_state_t *runloop_st)
{
retro_ctx_serialize_info_t *info = &runloop_st->runahead_savestate_info;
free(info->data);
info->data = NULL;
info->data_const = NULL;
info->size = 0;
}
/* Hooks - Hooks to cleanup, and add dirty input hooks */
static void runahead_remove_hooks(runloop_state_t *runloop_st)
{
if (runloop_st->original_retro_deinit)
{
runloop_st->current_core.retro_deinit =
runloop_st->original_retro_deinit;
runloop_st->original_retro_deinit = NULL;
}
if (runloop_st->original_retro_unload)
{
runloop_st->current_core.retro_unload_game =
runloop_st->original_retro_unload;
runloop_st->original_retro_unload = NULL;
}
runahead_remove_input_state_hook(runloop_st);
}
static void runahead_destroy(runloop_state_t *runloop_st)
{
runahead_savestate_info_free(runloop_st);
runahead_remove_hooks(runloop_st);
runahead_clear_variables(runloop_st);
}
static void runahead_unload_hook(void)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
runahead_remove_hooks(runloop_st);
runahead_destroy(runloop_st);
runahead_secondary_core_destroy(runloop_st);
if (runloop_st->current_core.retro_unload_game)
runloop_st->current_core.retro_unload_game();
runloop_st->core_poll_type_override = POLL_TYPE_OVERRIDE_DONTCARE;
}
static void runahead_deinit_hook(void)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
runahead_remove_hooks(runloop_st);
runahead_destroy(runloop_st);
runahead_secondary_core_destroy(runloop_st);
if (runloop_st->current_core.retro_deinit)
runloop_st->current_core.retro_deinit();
}
static void runahead_add_hooks(runloop_state_t *runloop_st)
{
if (!runloop_st->original_retro_deinit)
{
runloop_st->original_retro_deinit =
runloop_st->current_core.retro_deinit;
runloop_st->current_core.retro_deinit = runahead_deinit_hook;
}
if (!runloop_st->original_retro_unload)
{
runloop_st->original_retro_unload = runloop_st->current_core.retro_unload_game;
runloop_st->current_core.retro_unload_game = runahead_unload_hook;
}
runahead_add_input_state_hook(runloop_st);
}
/* Runahead Code */
static void runahead_err(runloop_state_t *runloop_st)
{
runloop_st->flags &= ~RUNLOOP_FLAG_RUNAHEAD_AVAILABLE;
runahead_savestate_info_free(runloop_st);
runahead_remove_hooks(runloop_st);
runloop_st->runahead_save_state_size = 0;
runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_SAVE_STATE_SIZE_KNOWN;
}
static bool runahead_create(runloop_state_t *runloop_st)
{
/* get savestate size and allocate buffer */
video_driver_state_t *video_st = video_state_get_ptr();
size_t info_size = core_serialize_size_special();
if (!runahead_savestate_info_init(runloop_st, info_size))
{
runahead_err(runloop_st);
return false;
}
if (video_st->main_flags & VIDEO_FLAG_ACTIVE)
video_driver_modify_disp_flags(VIDEO_FLAG_RUNAHEAD_IS_ACTIVE, 0);
else
video_driver_modify_disp_flags(0, VIDEO_FLAG_RUNAHEAD_IS_ACTIVE);
runahead_add_hooks(runloop_st);
runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_FORCE_INPUT_DIRTY;
return true;
}
static bool runahead_save_state(runloop_state_t *runloop_st)
{
retro_ctx_serialize_info_t *info = &runloop_st->runahead_savestate_info;
if (info->data)
{
if (core_serialize_special(info))
return true;
runahead_err(runloop_st);
}
return false;
}
static bool runahead_load_state(runloop_state_t *runloop_st)
{
retro_ctx_serialize_info_t *info = &runloop_st->runahead_savestate_info;
bool last_dirty = (runloop_st->flags & RUNLOOP_FLAG_INPUT_IS_DIRTY) ? true : false;
bool ret = core_unserialize_special(info);
if (last_dirty)
runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY;
else
runloop_st->flags &= ~RUNLOOP_FLAG_INPUT_IS_DIRTY;
if (!ret)
runahead_err(runloop_st);
return ret;
}
#if defined(HAVE_DYNAMIC)
static bool runahead_load_state_secondary(runloop_state_t *runloop_st, settings_t *settings)
{
retro_ctx_serialize_info_t *info = &runloop_st->runahead_savestate_info;
if (!secondary_core_deserialize(runloop_st, settings,
info->data_const, info->size))
{
runloop_st->flags &= ~RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE;
runahead_err(runloop_st);
return false;
}
return true;
}
#endif
static void runahead_core_run_use_last_input(runloop_state_t *runloop_st)
{
struct retro_callbacks *cbs = &runloop_st->retro_ctx;
retro_input_poll_t old_poll_function = cbs->poll_cb;
retro_input_state_t old_input_function = cbs->state_cb;
cbs->poll_cb = retro_input_poll_null;
cbs->state_cb = input_state_get_last;
runloop_st->current_core.retro_set_input_poll(cbs->poll_cb);
runloop_st->current_core.retro_set_input_state(cbs->state_cb);
runloop_st->current_core.retro_run();
audio_driver_frame_end();
cbs->poll_cb = old_poll_function;
cbs->state_cb = old_input_function;
runloop_st->current_core.retro_set_input_poll(cbs->poll_cb);
runloop_st->current_core.retro_set_input_state(cbs->state_cb);
}
void runahead_run(void *data,
int runahead_count,
bool runahead_hide_warnings,
bool use_secondary)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
int frame_number = 0;
bool last_frame = false;
bool suspended_frame = false;
#if defined(HAVE_DYNAMIC)
const bool have_dynamic = true;
settings_t *settings = config_get_ptr();
#else
const bool have_dynamic = false;
#endif
#if defined(HAVE_DYNAMIC)
enum runahead_copy_status sec_status = RUNAHEAD_COPY_UNAVAILABLE;
#else
const enum runahead_copy_status sec_status = RUNAHEAD_COPY_UNAVAILABLE;
#endif
video_driver_state_t
*video_st = video_state_get_ptr();
uint64_t frame_count = video_st->frame_count;
audio_driver_state_t
*audio_st = audio_state_get_ptr();
if ( runahead_count <= 0
|| !(runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_AVAILABLE))
goto force_input_dirty;
if (!(runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_SAVE_STATE_SIZE_KNOWN))
{
/* Disable runahead if current core reports
* that it has an insufficient savestate
* support level */
if (!core_info_current_supports_runahead())
{
runahead_err(runloop_st);
/* If core is incompatible with runahead,
* log a warning but do not spam OSD messages.
* Runahead menu entries are hidden when using
* incompatible cores, so there is no mechanism
* for users to respond to notifications. In
* addition, auto-disabling runahead is a feature,
* not a cause for 'concern'; OSD warnings should
* be reserved for when a core reports that it is
* runahead-compatible but subsequently fails in
* execution */
RARCH_WARN("[Run-Ahead] %s\n", msg_hash_to_str(MSG_RUNAHEAD_CORE_DOES_NOT_SUPPORT_RUNAHEAD));
goto force_input_dirty;
}
if (!runahead_create(runloop_st))
{
const char *_msg =
msg_hash_to_str(MSG_RUNAHEAD_CORE_DOES_NOT_SUPPORT_SAVESTATES);
if (!runahead_hide_warnings)
runloop_msg_queue_push(_msg, strlen(_msg), 0, 2 * 60, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_WARN("[Run-Ahead] %s\n", _msg);
goto force_input_dirty;
}
}
/* Check for GUI */
/* Hack: If we were in the GUI, force a resync. */
if (frame_count != runloop_st->runahead_last_frame_count + 1)
runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_FORCE_INPUT_DIRTY;
runloop_st->runahead_last_frame_count = frame_count;
#if defined(HAVE_DYNAMIC)
if ( use_secondary
&& have_dynamic
&& (runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE))
{
sec_status = secondary_core_ensure_exists(runloop_st, config_get_ptr());
if (sec_status == RUNAHEAD_COPY_UNAVAILABLE)
{
const char *_msg =
msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_CREATE_SECONDARY_INSTANCE);
runahead_secondary_core_destroy(runloop_st);
runloop_st->flags &= ~RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE;
runloop_msg_queue_push(_msg, strlen(_msg), 0, 3 * 60, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_WARN("[Run-Ahead] %s\n", _msg);
goto force_input_dirty;
}
/* PENDING: the copy task is still duplicating the core
* binary. Run the single-instance savestate method for this
* frame (identical output) and retry next frame; the
* secondary instance attaches seamlessly once ready, since
* it is (re)synchronised from a savestate every time it
* runs anyway. */
}
#endif
if ( !use_secondary
|| !have_dynamic
|| !(runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE)
|| (sec_status != RUNAHEAD_COPY_READY))
{
/* TODO: multiple savestates for higher performance
* when not using secondary core */
for (frame_number = 0; frame_number <= runahead_count; frame_number++)
{
last_frame = frame_number == runahead_count;
suspended_frame = !last_frame;
if (suspended_frame)
{
AUDIO_FLAGS_SET(audio_st, AUDIO_FLAG_SUSPENDED);
video_st->main_flags &= ~VIDEO_FLAG_ACTIVE;
}
if (frame_number == 0)
core_run();
else
runahead_core_run_use_last_input(runloop_st);
if (suspended_frame)
{
if (video_st->flags & VIDEO_FLAG_RUNAHEAD_IS_ACTIVE)
video_st->main_flags |= VIDEO_FLAG_ACTIVE;
else
video_st->main_flags &= ~VIDEO_FLAG_ACTIVE;
AUDIO_FLAGS_CLEAR(audio_st, AUDIO_FLAG_SUSPENDED);
}
if (frame_number == 0)
{
if (!runahead_save_state(runloop_st))
{
const char *_msg =
msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_SAVE_STATE);
runloop_msg_queue_push(_msg, strlen(_msg), 0, 3 * 60, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_WARN("[Run-Ahead] %s\n", _msg);
return;
}
}
if (last_frame)
{
if (!runahead_load_state(runloop_st))
{
const char *_msg = msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_LOAD_STATE);
runloop_msg_queue_push(_msg, strlen(_msg), 0, 3 * 60, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_WARN("[Run-Ahead] %s\n", _msg);
return;
}
}
}
}
else
{
#if defined(HAVE_DYNAMIC)
/* sec_status == RUNAHEAD_COPY_READY here (checked above) */
/* run main core with video suspended */
video_st->main_flags &= ~VIDEO_FLAG_ACTIVE;
core_run();
if (video_st->flags & VIDEO_FLAG_RUNAHEAD_IS_ACTIVE)
video_st->main_flags |= VIDEO_FLAG_ACTIVE;
else
video_st->main_flags &= ~VIDEO_FLAG_ACTIVE;
if ( (runloop_st->flags & RUNLOOP_FLAG_INPUT_IS_DIRTY)
|| (runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_FORCE_INPUT_DIRTY))
{
runloop_st->flags &= ~RUNLOOP_FLAG_INPUT_IS_DIRTY;
if (!runahead_save_state(runloop_st))
{
const char *_msg = msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_SAVE_STATE);
runloop_msg_queue_push(_msg, strlen(_msg), 0, 3 * 60, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_WARN("[Run-Ahead] %s\n", _msg);
return;
}
if (!runahead_load_state_secondary(runloop_st, settings))
{
const char *_msg = msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_LOAD_STATE);
runloop_msg_queue_push(_msg, strlen(_msg), 0, 3 * 60, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_WARN("[Run-Ahead] %s\n", _msg);
return;
}
for (frame_number = 0; frame_number < runahead_count - 1; frame_number++)
{
video_st->main_flags &= ~VIDEO_FLAG_ACTIVE;
AUDIO_FLAGS_SET(audio_st, AUDIO_FLAG_SUSPENDED | AUDIO_FLAG_HARD_DISABLE);
if (secondary_core_run_use_last_input(runloop_st))
runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE;
else
runloop_st->flags &= ~RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE;
AUDIO_FLAGS_CLEAR(audio_st, AUDIO_FLAG_SUSPENDED | AUDIO_FLAG_HARD_DISABLE);
if (video_st->flags & VIDEO_FLAG_RUNAHEAD_IS_ACTIVE)
video_st->main_flags |= VIDEO_FLAG_ACTIVE;
else
video_st->main_flags &= ~VIDEO_FLAG_ACTIVE;
}
}
AUDIO_FLAGS_SET(audio_st, AUDIO_FLAG_SUSPENDED | AUDIO_FLAG_HARD_DISABLE);
if (secondary_core_run_use_last_input(runloop_st))
runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE;
else
runloop_st->flags &= ~RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE;
AUDIO_FLAGS_CLEAR(audio_st, AUDIO_FLAG_SUSPENDED | AUDIO_FLAG_HARD_DISABLE);
#endif
}
runloop_st->flags &= ~RUNLOOP_FLAG_RUNAHEAD_FORCE_INPUT_DIRTY;
return;
force_input_dirty:
core_run();
runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_FORCE_INPUT_DIRTY;
}
/* Preemptive Frames */
/* ===== BEGIN preempt analog-mask bit =====
* The bit of analog_mask an (index, id) pair asks for: the left and
* right sticks' two axes in bits 0..3, the analog buttons in bits
* 4..19. A pair outside that - an index past the buttons, an id past
* the sixteen buttons or the two axes - has no bit, and is rejected
* rather than folded onto one that exists. */
static bool preempt_analog_mask_bit(unsigned index, unsigned id, unsigned *bit)
{
if (index == RETRO_DEVICE_INDEX_ANALOG_BUTTON)
{
if (id >= 16)
return false;
*bit = 4 + id;
return true;
}
if (index > RETRO_DEVICE_INDEX_ANALOG_RIGHT || id > RETRO_DEVICE_ID_ANALOG_Y)
return false;
*bit = index * 2 + id;
return true;
}
/* ===== END preempt analog-mask bit ===== */
static int16_t preempt_input_state(unsigned port,
unsigned device, unsigned index, unsigned id)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
preempt_t *preempt = runloop_st->preempt_data;
unsigned device_class = device & RETRO_DEVICE_MASK;
/* A port the state has no slot for is not one this core has: no
* input, and nothing written past the arrays. */
if (port >= MAX_USERS)
return 0;
switch (device_class)
{
case RETRO_DEVICE_ANALOG:
{
/* Add requested inputs to mask */
unsigned bit;
if (preempt_analog_mask_bit(index, id, &bit))
preempt->analog_mask[port] |= (1u << bit);
break;
}
case RETRO_DEVICE_LIGHTGUN:
case RETRO_DEVICE_POINTER:
/* Set pointing device for this port */
preempt->ptr_dev_needed[port] = device_class;
break;
case RETRO_DEVICE_MOUSE:
/* Set pointing device and return stored x,y */
if (id <= RETRO_DEVICE_ID_MOUSE_Y)
{
preempt->ptr_dev_needed[port] = device_class;
if (preempt->ptr_dev_polled[port] == device_class)
return preempt->ptrdev_state[port][id];
}
break;
default:
break;
}
return input_driver_state_wrapper(port, device, index, id);
}
/* ===== BEGIN preempt slab =====
* The frame buffers are one allocation, frames * state_size bytes,
* with buffer[i] at i * state_size from the base; buffer[0] is the
* base and the one to free. The product is checked before it is
* asked for. */
static bool preempt_slab_alloc(void **buffer, unsigned frames, size_t state_size)
{
unsigned i;
uint8_t *base;
if (!frames || !state_size || state_size > ((size_t)-1) / frames)
return false;
if (!(base = (uint8_t*)malloc((size_t)frames * state_size)))
return false;
for (i = 0; i < frames; i++)
buffer[i] = base + (size_t)i * state_size;
return true;
}
/* ===== END preempt slab ===== */
static const char* preempt_allocate(runloop_state_t *runloop_st,
const uint8_t frames)
{
size_t info_size;
preempt_t *preempt = (preempt_t*)calloc(1, sizeof(preempt_t));
if (!(runloop_st->preempt_data = preempt))
return msg_hash_to_str(MSG_PREEMPT_FAILED_TO_ALLOCATE);
info_size = core_serialize_size_special();
if (!info_size)
return msg_hash_to_str(MSG_PREEMPT_CORE_DOES_NOT_SUPPORT_SAVESTATES);
preempt->state_size = info_size;
preempt->frames = frames;
if (!preempt_slab_alloc(preempt->buffer, frames, info_size))
return msg_hash_to_str(MSG_PREEMPT_FAILED_TO_ALLOCATE);
return NULL;
}
/**
* preempt_deinit:
*
* Frees preempt object and unsets overrides.
**/
void preempt_deinit(void *data)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
preempt_t *preempt = runloop_st->preempt_data;
struct retro_core_t *current_core = &runloop_st->current_core;
if (!preempt)
return;
/* One slab; buffer[0] is its base. */
free(preempt->buffer[0]);
free(preempt);
runloop_st->preempt_data = NULL;
/* Undo overrides */
runloop_st->flags |= (RUNLOOP_FLAG_RUNAHEAD_AVAILABLE
| RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE);
if (current_core->retro_set_input_poll)
current_core->retro_set_input_poll(runloop_st->input_poll_callback_original);
if (current_core->retro_set_input_state)
current_core->retro_set_input_state(runloop_st->retro_ctx.state_cb);
}
/**
* preempt_init:
*
* @return true on success, false on failure
*
* Allocates savestate buffer and sets overrides for preemptive frames.
**/
bool preempt_init(void *data)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
settings_t *settings = config_get_ptr();
const char *_msg = NULL;
bool preemptive_frames_enable = settings->bools.preemptive_frames_enable;
unsigned run_ahead_frames = settings->uints.run_ahead_frames;
bool run_ahead_hide_warnings = settings->bools.run_ahead_hide_warnings;
if ( runloop_st->preempt_data
|| !preemptive_frames_enable
|| !run_ahead_frames
|| !(runloop_st->current_core.flags & RETRO_CORE_FLAG_GAME_LOADED))
return false;
/* Check if supported - same requirements as runahead */
if (!core_info_current_supports_runahead())
{
_msg = msg_hash_to_str(MSG_PREEMPT_CORE_DOES_NOT_SUPPORT_PREEMPT);
goto error;
}
/* Set flags to block runahead and request 'same instance' states */
runloop_st->flags &= ~(RUNLOOP_FLAG_RUNAHEAD_AVAILABLE
| RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE);
/* Run at least one frame before attempting
* retro_serialize_size or retro_serialize */
if (video_state_get_ptr()->frame_count == 0)
{
runloop_st->current_core.retro_run();
audio_driver_frame_end();
}
/* Allocate - same 'frames' setting as runahead */
if ((_msg = preempt_allocate(runloop_st, run_ahead_frames)))
goto error;
/* Only poll in preempt_run() */
runloop_st->current_core.retro_set_input_poll(retro_input_poll_null);
/* Track requested analog states and pointing device types */
runloop_st->current_core.retro_set_input_state(preempt_input_state);
return true;
error:
preempt_deinit(runloop_st);
if (!run_ahead_hide_warnings)
runloop_msg_queue_push(_msg, strlen(_msg), 0, 2 * 60, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_WARN("[Run-Ahead Preemptive] %s\n", _msg);
return false;
}
static INLINE bool preempt_analog_input_dirty(preempt_t *preempt,
retro_input_state_t state_cb, unsigned port)
{
int16_t state[20] = {0};
uint8_t base, i;
/* axes */
for (i = 0; i < 2; i++)
{
base = i * 2;
if (preempt->analog_mask[port] & (1 << (base )))
state[base ] = state_cb(port, RETRO_DEVICE_ANALOG, i, 0);
if (preempt->analog_mask[port] & (1 << (base + 1)))
state[base + 1] = state_cb(port, RETRO_DEVICE_ANALOG, i, 1);
}
/* buttons */
if (preempt->analog_mask[port] & 0xfff0)
{
for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++)
{
if (preempt->analog_mask[port] & (1 << (i + 4)))
state[i + 4] = state_cb(port, RETRO_DEVICE_ANALOG,
RETRO_DEVICE_INDEX_ANALOG_BUTTON, i);
}
}
if (memcmp(preempt->analog_state[port], state, sizeof(state)) == 0)
return false;
memcpy(preempt->analog_state[port], state, sizeof(state));
return true;
}
static INLINE bool preempt_ptr_input_dirty(preempt_t *preempt,
retro_input_state_t state_cb, unsigned device, unsigned port)
{
int16_t state[4] = {0};
unsigned count_id = 0;
unsigned x_id = 0;
unsigned id, max_id;
switch (device)
{
case RETRO_DEVICE_MOUSE:
max_id = RETRO_DEVICE_ID_MOUSE_BUTTON_5;
break;
case RETRO_DEVICE_LIGHTGUN:
x_id = RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X;
max_id = RETRO_DEVICE_ID_LIGHTGUN_DPAD_RIGHT;
break;
case RETRO_DEVICE_POINTER:
max_id = RETRO_DEVICE_ID_POINTER_PRESSED;
count_id = RETRO_DEVICE_ID_POINTER_COUNT;
break;
default:
return false;
}
/* x, y */
state[0] = state_cb(port, device, 0, x_id );
state[1] = state_cb(port, device, 0, x_id + 1);
/* buttons */
for (id = 2; id <= max_id; id++)
state[2] |= state_cb(port, device, 0, id) ? 1 << id : 0;
/* ptr count */
if (count_id)
state[3] = state_cb(port, device, 0, count_id);
if (memcmp(preempt->ptrdev_state[port], state, sizeof(state)) == 0)
return false;
memcpy(preempt->ptrdev_state[port], state, sizeof(state));
return true;
}
static INLINE void preempt_input_poll(preempt_t *preempt,
runloop_state_t *runloop_st, unsigned max_users)
{
size_t p;
retro_input_state_t state_cb = input_driver_state_wrapper;
input_driver_poll();
/* Check for input state changes */
for (p = 0; p < max_users; p++)
{
/* Check full digital joypad */
int16_t joypad_state = (int16_t)(state_cb((unsigned)p, RETRO_DEVICE_JOYPAD,
0, RETRO_DEVICE_ID_JOYPAD_MASK));
if (joypad_state != preempt->joypad_state[p])
{
preempt->joypad_state[p] = joypad_state;
runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY;
}
/* Check requested analogs */
if ( preempt->analog_mask[p]
&& preempt_analog_input_dirty(preempt, state_cb, (unsigned)p))
{
runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY;
preempt->analog_mask[p] = 0;
}
/* Check requested pointing device */
if (preempt->ptr_dev_needed[p])
{
if (preempt_ptr_input_dirty(
preempt, state_cb, preempt->ptr_dev_needed[p], (unsigned)p))
runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY;
preempt->ptr_dev_polled[p] = preempt->ptr_dev_needed[p];
preempt->ptr_dev_needed[p] = RETRO_DEVICE_NONE;
}
}
}
/* macro for preempt_run */
#define PREEMPT_NEXT_PTR(x) ((x + 1) % preempt->frames)
/**
* preempt_run:
* @preempt : pointer to preemptive frames object
*
* Call in place of core_run() for preemptive frames.
**/
void preempt_run(preempt_t *preempt, void *data)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
struct retro_core_t *current_core = &runloop_st->current_core;
const char *_msg = NULL;
audio_driver_state_t *audio_st = audio_state_get_ptr();
settings_t *settings = config_get_ptr();
unsigned input_max_users = settings->uints.input_max_users;
bool run_ahead_hide_warnings = settings->bools.run_ahead_hide_warnings;
/* Poll and check for dirty input */
preempt_input_poll(preempt, runloop_st, input_max_users);
runloop_st->flags |= RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE;
if ((runloop_st->flags & RUNLOOP_FLAG_INPUT_IS_DIRTY)
&& preempt->frame_count >= preempt->frames)
{
/* Suspend A/V and run preemptive frames */
AUDIO_FLAGS_SET(audio_st, AUDIO_FLAG_SUSPENDED);
video_state_get_ptr()->main_flags &= ~VIDEO_FLAG_ACTIVE;
if (!current_core->retro_unserialize(
preempt->buffer[preempt->start_ptr], preempt->state_size))
{
_msg = msg_hash_to_str(MSG_PREEMPT_FAILED_TO_LOAD_STATE);
goto error;
}
current_core->retro_run();
preempt->replay_ptr = PREEMPT_NEXT_PTR(preempt->start_ptr);
while (preempt->replay_ptr != preempt->start_ptr)
{
if (!current_core->retro_serialize(
preempt->buffer[preempt->replay_ptr], preempt->state_size))
{
_msg = msg_hash_to_str(MSG_PREEMPT_FAILED_TO_SAVE_STATE);
goto error;
}
current_core->retro_run();
preempt->replay_ptr = PREEMPT_NEXT_PTR(preempt->replay_ptr);
}
AUDIO_FLAGS_CLEAR(audio_st, AUDIO_FLAG_SUSPENDED);
video_state_get_ptr()->main_flags |= VIDEO_FLAG_ACTIVE;
}
/* Save current state and set start_ptr to oldest state */
if (!current_core->retro_serialize(
preempt->buffer[preempt->start_ptr], preempt->state_size))
{
_msg = msg_hash_to_str(MSG_PREEMPT_FAILED_TO_SAVE_STATE);
goto error;
}
preempt->start_ptr = PREEMPT_NEXT_PTR(preempt->start_ptr);
runloop_st->flags &= ~(RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE
| RUNLOOP_FLAG_INPUT_IS_DIRTY);
/* Run normal frame */
current_core->retro_run();
audio_driver_frame_end();
preempt->frame_count++;
return;
error:
runloop_st->flags &= ~(RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE
| RUNLOOP_FLAG_INPUT_IS_DIRTY);
AUDIO_FLAGS_CLEAR(audio_st, AUDIO_FLAG_SUSPENDED);
video_state_get_ptr()->main_flags |= VIDEO_FLAG_ACTIVE;
preempt_deinit(runloop_st);
if (!run_ahead_hide_warnings)
runloop_msg_queue_push(_msg, strlen(_msg), 0, 2 * 60, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
RARCH_ERR("[Run-Ahead Preemptive] %s\n", _msg);
}
void runahead_clear_variables(void *data)
{
runloop_state_t *runloop_st = (runloop_state_t*)data;
runloop_st->runahead_save_state_size = 0;
runloop_st->flags &= ~RUNLOOP_FLAG_RUNAHEAD_SAVE_STATE_SIZE_KNOWN;
video_driver_modify_disp_flags(VIDEO_FLAG_RUNAHEAD_IS_ACTIVE, 0);
runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_AVAILABLE
| RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE
| RUNLOOP_FLAG_RUNAHEAD_FORCE_INPUT_DIRTY;
runloop_st->runahead_last_frame_count = 0;
}