Files
RetroArch/state_manager.c
T
LibretroAdmin 6da6466e88 input: no file under input/ takes a state pointer but the frontend
The last 23, in 11 files, are gone. Nothing under input/ calls
config_get_ptr(), input_state_get_ptr(), runloop_state_get_ptr(),
menu_state_get_ptr() or video_driver_get_ptr() any more, apart from
the frontend itself (input_driver.c, input_driver_choice.c).

The replay code (input/bsv/bsvmovie.c), seven:
- its four entry points that took no state are handed the input state
  by their callers, the save-state task and the rewind;
- its two inner functions used the state only to say the replay is
  over, fifteen times between them. That is bsv_movie_set_end() now,
  and they are handed the replay alone.

The platform drivers, fourteen settings reads: named calls, as the
others became - Apple (keyboard as gamepad, mouse index, rumble gain,
device vibration), Wii and GameCube (mouse index and scale, joypad
index), Vita (back touch), Switch (split Joy-Con), webOS
(screensaver), udev (the five touch options).

Two drivers wrote to the settings, and each has a call that says what
it is doing:
- the Wii and GameCube joypad driver puts port 1 back on the first pad
  when only one is left: input_config_set_joypad_index();
- the udev input driver, finding no device on the very first start,
  makes linuxraw the configured driver:
  input_driver_first_start_fallback().

The SDL2 input driver took the video driver's data to grab the pointer
in its window. The video driver does the grab, sdl2_video_grab_window(),
and the input driver asks.

The Android input driver held the code that writes SRAM, core options
and the configuration when Android takes the application away. That is
frontend work; it is retroarch_save_for_suspend() in retroarch.c, as
it was, and the driver calls it.

tools/input_state_grab_check.py has an empty list, and no exemption
for the replay code: a file under input/ that takes one of these
fails.

Built for Linux and Android; the frontend harness, the four replay
tests and the udev devices test pass. The Apple, Wii, GameCube, Vita,
Switch and webOS files are not built here.
2026-10-05 04:31:56 +02:00

1048 lines
31 KiB
C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2017 - Daniel De Matteis
* Copyright (C) 2014-2017 - Alfred Agrell
*
* 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/>.
*/
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <retro_inline.h>
#include <compat/strl.h>
#include <compat/intrinsics.h>
#include "state_manager.h"
#include "msg_hash.h"
#include "core.h"
#include "core_info.h"
#include "retroarch.h"
#include "verbosity.h"
#include "content.h"
#include "audio/audio_driver.h"
#ifdef HAVE_NETWORKING
#include "network/netplay/netplay.h"
#endif
/* This makes Valgrind throw errors if a core overflows its savestate size. */
/* Keep it off unless you're chasing a core bug, it slows things down. */
#define STRICT_BUF_SIZE 0
/* A dirty run of this many words or more is copied with memcpy; a
* shorter one by the loop, which for the few-word runs that dominate
* a delta costs less than a call. */
#ifndef STATE_MANAGER_MEMCPY_WORDS
#define STATE_MANAGER_MEMCPY_WORDS 32
#endif
#ifndef UINT16_MAX
#define UINT16_MAX 0xffff
#endif
#ifndef UINT32_MAX
#define UINT32_MAX 0xffffffffu
#endif
#if defined(__x86_64__) || defined(__i386__) || defined(__i486__) || defined(__i686__) || defined(_M_IX86) || defined(_M_AMD64) || defined(_M_X64)
#define CPU_X86
#endif
/* Other arches SIGBUS (usually) on unaligned accesses. */
#ifndef CPU_X86
#define NO_UNALIGNED_MEM
#endif
#if __SSE2__
#include <emmintrin.h>
/* An AVX2 scanner doubles the bytes compared per iteration. It is selected at
* runtime, and is only built where the compiler can emit an AVX2 function
* without raising the baseline ISA for the whole file. */
#if (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))
#include <immintrin.h>
#include <features/features_cpu.h>
#define STATE_MANAGER_HAVE_AVX2_SCAN 1
#endif
#endif
/* Tail padding on each block, in bytes. Must be >= the widest vector load
* find_change() can issue (32 for AVX2); 64 leaves room for a wider scanner
* without having to revisit the allocation. */
#define STATE_MANAGER_SCAN_PAD 64
/* There's no equivalent in libc, you'd think so ...
* std::mismatch exists, but it's not optimized at all. */
#ifdef STATE_MANAGER_HAVE_AVX2_SCAN
__attribute__((target("avx2")))
static size_t find_change_avx2(const uint16_t *a, const uint16_t *b)
{
const __m256i *a256 = (const __m256i*)a;
const __m256i *b256 = (const __m256i*)b;
for (;;)
{
__m256i v0 = _mm256_loadu_si256(a256);
__m256i v1 = _mm256_loadu_si256(b256);
__m256i c = _mm256_cmpeq_epi8(v0, v1);
uint32_t mask = (uint32_t)_mm256_movemask_epi8(c);
if (mask != 0xffffffffu)
{
/* Offset is a multiple of 32 and the tzcnt is 0..31, so the OR is
* an add, exactly as in the SSE2 form. */
size_t ret = (((uint8_t*)a256 - (uint8_t*)a) |
(compat_ctz(~mask)));
return (ret >> 1);
}
a256++;
b256++;
}
}
static int state_manager_use_avx2(void)
{
static int cached = -1;
if (cached < 0)
cached = (cpu_features_get() & RETRO_SIMD_AVX2) ? 1 : 0;
return cached;
}
#endif
static size_t find_change(const uint16_t *a, const uint16_t *b)
{
#if __SSE2__
const __m128i *a128 = (const __m128i*)a;
const __m128i *b128 = (const __m128i*)b;
#ifdef STATE_MANAGER_HAVE_AVX2_SCAN
if (state_manager_use_avx2())
return find_change_avx2(a, b);
#endif
for (;;)
{
__m128i v0 = _mm_loadu_si128(a128);
__m128i v1 = _mm_loadu_si128(b128);
__m128i c = _mm_cmpeq_epi8(v0, v1);
uint32_t mask = _mm_movemask_epi8(c);
if (mask != 0xffff) /* Something has changed, figure out where. */
{
/* calculate the real offset to the differing byte */
size_t ret = (((uint8_t*)a128 - (uint8_t*)a) |
(compat_ctz(~mask)));
/* and convert that to the uint16_t offset */
return (ret >> 1);
}
a128++;
b128++;
}
#else
const uint16_t *a_org = a;
#ifdef NO_UNALIGNED_MEM
while (((uintptr_t)a & (sizeof(size_t) - 1)) && *a == *b)
{
a++;
b++;
}
if (*a == *b)
#endif
{
const size_t *a_big = (const size_t*)a;
const size_t *b_big = (const size_t*)b;
while (*a_big == *b_big)
{
a_big++;
b_big++;
}
a = (const uint16_t*)a_big;
b = (const uint16_t*)b_big;
while (*a == *b)
{
a++;
b++;
}
}
return a - a_org;
#endif
}
static size_t find_same(const uint16_t *a, const uint16_t *b)
{
const uint16_t *a_org = a;
#ifdef NO_UNALIGNED_MEM
if (((uintptr_t)a & (sizeof(uint32_t) - 1)) && *a != *b)
{
a++;
b++;
}
if (*a != *b)
#endif
{
/* With this, it's random whether two consecutive identical
* words are caught.
*
* Luckily, compression rate is the same for both cases, and
* three is always caught.
*
* (We prefer to miss two-word blocks, anyways; fewer iterations
* of the outer loop, as well as in the decompressor.) */
const uint32_t *a_big = (const uint32_t*)a;
const uint32_t *b_big = (const uint32_t*)b;
#if __SSE2__
/* Four words per iteration. The granularity stays 32-bit -- the lane
* mask is per word, so this stops at exactly the word the scalar loop
* would, which the compressed output depends on. */
for (;;)
{
__m128i v0 = _mm_loadu_si128((const __m128i*)a_big);
__m128i v1 = _mm_loadu_si128((const __m128i*)b_big);
int mask = _mm_movemask_ps(_mm_castsi128_ps(
_mm_cmpeq_epi32(v0, v1)));
if (mask)
{
unsigned lane = (unsigned)compat_ctz((unsigned)mask);
a_big += lane;
b_big += lane;
break;
}
a_big += 4;
b_big += 4;
}
#else
while (*a_big != *b_big)
{
a_big++;
b_big++;
}
#endif
a = (const uint16_t*)a_big;
b = (const uint16_t*)b_big;
if (a != a_org && a[-1] == b[-1])
{
a--;
b--;
}
}
return a - a_org;
}
/* Adds two sizes, or reports that the sum does not fit. */
static bool state_manager_size_add(size_t a, size_t b, size_t *out)
{
if (a > (size_t)-1 - b)
return false;
*out = a + b;
return true;
}
/* Multiplies two sizes, or reports that the product does not fit. */
static bool state_manager_size_mul(size_t a, size_t b, size_t *out)
{
if (b && a > (size_t)-1 / b)
return false;
*out = a * b;
return true;
}
/* Returns the maximum compressed size of a savestate.
* It is very likely to compress to far less.
*
* Every term derives from @uncomp, which reaches here from a core's
* retro_serialize_size(), so the roundings and the per-block overhead
* are added and multiplied under check. Returns 0 when the answer does
* not fit a size_t; a real answer always carries the three closing
* uint16_t and so is never 0. */
static size_t state_manager_raw_maxsize(size_t uncomp)
{
/* bytes covered by a compressed block */
const size_t maxcblkcover = UINT16_MAX * sizeof(uint16_t);
size_t uncomp16;
size_t maxcblks;
size_t overhead;
size_t total;
/* uncompressed size, rounded to 16 bits */
if (!state_manager_size_add(uncomp, sizeof(uint16_t) - 1, &uncomp16))
return 0;
uncomp16 &= -sizeof(uint16_t);
/* number of blocks */
if (!state_manager_size_add(uncomp, maxcblkcover - 1, &maxcblks))
return 0;
maxcblks /= maxcblkcover;
/* two u16 overhead per block */
if (!state_manager_size_mul(maxcblks, sizeof(uint16_t) * 2, &overhead))
return 0;
if (!state_manager_size_add(uncomp16, overhead, &total))
return 0;
/* three u16 to end it */
if (!state_manager_size_add(total, sizeof(uint16_t) * 3, &total))
return 0;
return total;
}
/*
* A reverse delta. Takes two savestates, 'src' the older and 'dst' the
* newer, and creates a patch that, applied to a copy of 'dst', restores
* 'src': the patch stores the old words of every run that differs, so
* a rewind step applies it to the current state to get the previous
* one. The next push swaps the blocks, so the block that was 'dst'
* becomes 'src' for the following patch.
*
* 'patch' must be size 'state_manager_raw_maxsize(len)' or more.
* Returns the number of bytes actually written to 'patch'.
*/
static size_t state_manager_raw_compress(const void *src,
const void *dst, size_t len, void *patch)
{
const uint16_t *old16 = (const uint16_t*)src;
const uint16_t *new16 = (const uint16_t*)dst;
uint16_t *compressed16 = (uint16_t*)patch;
size_t num16s = (len + sizeof(uint16_t) - 1)
/ sizeof(uint16_t);
while (num16s)
{
size_t i, changed;
size_t skip = find_change(old16, new16);
if (skip >= num16s)
break;
old16 += skip;
new16 += skip;
num16s -= skip;
if (skip > UINT16_MAX)
{
/* This will make it scan the entire thing again,
* but it only hits on 8GB unchanged data anyways,
* and if you're doing that, you've got bigger problems. */
if (skip > UINT32_MAX)
skip = UINT32_MAX;
*compressed16++ = 0;
*compressed16++ = skip;
*compressed16++ = skip >> 16;
continue;
}
changed = find_same(old16, new16);
if (changed > UINT16_MAX)
changed = UINT16_MAX;
*compressed16++ = changed;
*compressed16++ = skip;
/* The typical dirty run is a few words, where a call costs more
* than the loop; a long one - a framebuffer, a redrawn tilemap -
* is a copy. */
if (changed >= STATE_MANAGER_MEMCPY_WORDS)
memcpy(compressed16, old16, changed * sizeof(uint16_t));
else
for (i = 0; i < changed; i++)
compressed16[i] = old16[i];
old16 += changed;
new16 += changed;
num16s -= changed;
compressed16 += changed;
}
compressed16[0] = 0;
compressed16[1] = 0;
compressed16[2] = 0;
return (uint8_t*)(compressed16 + 3) - (uint8_t*)patch;
}
/*
* Takes 'patch' from a previous call to 'state_manager_raw_compress'
* and applies it to 'data' ('src' from that call),
* yielding 'dst' in that call.
*
* If the given arguments do not match a previous call to
* state_manager_raw_compress(), anything at all can happen.
*/
/* Applies a patch to 'data', a block of 'len' bytes. Two passes: the
* first walks every token and checks that each run of changed words
* and each skip stays inside the block and that the record ends inside
* 'patch_len' bytes; the second writes. A record that fails the first
* pass changes nothing and returns false, so a corrupted ring cannot
* write past the block, nor leave it half-applied. */
static bool state_manager_raw_decompress(const void *patch, size_t patch_len,
void *data, size_t len)
{
const uint16_t *patch16 = (const uint16_t*)patch;
const uint16_t *p = patch16;
size_t patch_words = patch_len / sizeof(uint16_t);
size_t words = len / sizeof(uint16_t);
size_t at = 0;
uint16_t *out16 = (uint16_t*)data;
for (;;)
{
uint16_t numchanged;
if ((size_t)(p - patch16) >= patch_words)
return false;
numchanged = *p++;
if (numchanged)
{
uint16_t skip;
if ((size_t)(p - patch16) + 1 + numchanged > patch_words)
return false;
skip = *p++;
if (skip > words - at)
return false;
at += skip;
if (numchanged > words - at)
return false;
at += numchanged;
p += numchanged;
}
else
{
uint32_t numunchanged;
if ((size_t)(p - patch16) + 2 > patch_words)
return false;
numunchanged = p[0] | ((uint32_t)p[1] << 16);
if (!numunchanged)
break;
p += 2;
if (numunchanged > words - at)
return false;
at += numunchanged;
}
}
for (p = patch16;;)
{
uint16_t numchanged = *(p++);
if (numchanged)
{
uint16_t i;
out16 += *p++;
/* The typical run is a few words, where a call costs more
* than the loop; a long one is a copy. */
if (numchanged >= STATE_MANAGER_MEMCPY_WORDS)
memcpy(out16, p, numchanged * sizeof(uint16_t));
else
for (i = 0; i < numchanged; i++)
out16[i] = p[i];
p += numchanged;
out16 += numchanged;
}
else
{
uint32_t numunchanged = p[0] | ((uint32_t)p[1] << 16);
if (!numunchanged)
break;
p += 2;
out16 += numunchanged;
}
}
return true;
}
/* The start offsets point to 'nextstart' of any given compressed frame.
* Each uint16 is stored native endian; anything that claims any other
* endianness refers to the endianness of this specific item.
* The uint32 is stored little endian.
*
* Each size value is stored native endian if alignment is not enforced;
* if it is, they're little endian.
*
* The start of the buffer contains a size pointing to the end of the
* buffer; the end points to its start.
*
* Wrapping is handled by returning to the start of the buffer if the
* compressed data could potentially hit the edge;
*
* if the compressed data could potentially overwrite the tail pointer,
* the tail retreats until it can no longer collide.
*
* This means that on average, ~2 * maxcompsize is
* unused at any given moment. */
/* These are called very few constant times per frame,
* keep it as simple as possible. */
static INLINE void write_size_t(void *ptr, size_t val)
{
memcpy(ptr, &val, sizeof(val));
}
static INLINE size_t read_size_t(const void *ptr)
{
size_t ret;
memcpy(&ret, ptr, sizeof(ret));
return ret;
}
static void state_manager_free(state_manager_t *state)
{
if (!state)
return;
if (state->data)
free(state->data);
/* thisblock and nextblock share a single allocation;
* after pointer swaps, either could point to the base.
* Free whichever has the lower address. */
if (state->thisblock || state->nextblock)
{
uint8_t *base = state->thisblock;
if (state->nextblock && (!base || state->nextblock < base))
base = state->nextblock;
free(base);
}
#if STRICT_BUF_SIZE
if (state->debugblock)
free(state->debugblock);
state->debugblock = NULL;
#endif
state->data = NULL;
state->thisblock = NULL;
state->nextblock = NULL;
}
static state_manager_t *state_manager_new(
size_t state_size, size_t buffer_size)
{
size_t max_comp_size, block_size, alloc_size, single_block_alloc;
uint8_t *block_buf = NULL;
uint8_t *state_data = NULL;
state_manager_t *state = (state_manager_t*)calloc(1, sizeof(*state));
if (!state)
return NULL;
/* state_size is whatever the core answered retro_serialize_size()
* with. The block layout, the sentinel positions and the two-block
* doubling all derive from it, and an unchecked derivation wraps to
* an allocation smaller than the offsets computed from the same
* numbers, so each step is taken under check and a size that cannot
* be laid out is refused. */
if (!state_manager_size_add(state_size, sizeof(uint16_t) - 1, &block_size))
goto error;
block_size &= -sizeof(uint16_t);
/* the compressed data is surrounded by pointers to the other side */
if (!(max_comp_size = state_manager_raw_maxsize(state_size)))
goto error;
if (!state_manager_size_add(max_comp_size, sizeof(size_t) * 2,
&max_comp_size))
goto error;
state_data = (uint8_t*)malloc(buffer_size);
if (!state_data)
goto error;
/* Combine thisblock and nextblock into a single allocation.
* Each block needs: block_size rounded to uint16_t alignment, plus the
* four sentinel uint16_t, plus STATE_MANAGER_SCAN_PAD.
*
* find_change() deliberately scans past the end of the logical data --
* the caller bounds the result afterwards -- and terminates only on the
* sentinel at byte block_size + 6. The final vector load may therefore
* begin at that sentinel and read a full vector beyond it, so the tail
* padding must be at least the widest load find_change can issue.
* Keep the two in step: widening the scanner means widening this. */
if (!state_manager_size_add(block_size,
sizeof(uint16_t) * 4 + STATE_MANAGER_SCAN_PAD,
&single_block_alloc))
goto error;
if (!state_manager_size_mul(single_block_alloc, 2, &alloc_size))
goto error;
block_buf = (uint8_t*)calloc(alloc_size, 1);
if (!block_buf)
goto error;
/* Set up sentinel bytes.
* thisblock gets uniq=0 (already zero from calloc).
* nextblock gets uniq=1. */
((uint16_t*)block_buf)[block_size / sizeof(uint16_t) + 3] = 0;
((uint16_t*)(block_buf + single_block_alloc))[block_size / sizeof(uint16_t) + 3] = 1;
state->blocksize = block_size;
state->maxcompsize = max_comp_size;
state->data = state_data;
state->thisblock = block_buf;
state->nextblock = block_buf + single_block_alloc;
state->capacity = buffer_size;
state->head = state->data + sizeof(size_t);
state->tail = state->data + sizeof(size_t);
#if STRICT_BUF_SIZE
state->debugsize = state_size;
state->debugblock = (uint8_t*)malloc(state_size);
#endif
return state;
error:
if (state_data)
free(state_data);
if (block_buf)
free(block_buf);
free(state);
return NULL;
}
static bool state_manager_pop(state_manager_t *state, const void **data)
{
size_t start;
uint8_t *out = NULL;
const uint8_t *compressed = NULL;
*data = NULL;
if (state->thisblock_valid)
{
state->thisblock_valid = false;
state->entries--;
*data = state->thisblock;
return true;
}
*data = state->thisblock;
if (state->head == state->tail)
return false;
start = read_size_t(state->head - sizeof(size_t));
/* start comes out of the buffer itself, so it is checked by
* subtracting from the capacity rather than adding to it: the
* addition wraps on a corrupt value and lets the bound pass. */
if ( state->capacity < sizeof(size_t)
|| start > state->capacity - sizeof(size_t))
return false;
compressed = state->data + start + sizeof(size_t);
out = state->thisblock;
/* The record runs from its start to the size_t that pointed here;
* a record that does not decode inside that, or inside the block,
* is the end of the ring's usable history. */
if (!state_manager_raw_decompress(compressed,
(size_t)(state->head - sizeof(size_t) - compressed), out,
state->blocksize))
return false;
state->head = state->data + start;
state->entries--;
return true;
}
static void state_manager_push_where(state_manager_t *state, void **data)
{
/* We need to ensure we have an uncompressed copy of the last
* pushed state, or we could end up applying a 'patch' to wrong
* savestate, and that'd blow up rather quickly. */
if (!state->thisblock_valid)
{
const void *ignored;
if (state_manager_pop(state, &ignored))
{
state->thisblock_valid = true;
state->entries++;
}
}
*data = state->nextblock;
#if STRICT_BUF_SIZE
*data = state->debugblock;
#endif
}
static void state_manager_push_do(state_manager_t *state)
{
uint8_t *swap = NULL;
#if STRICT_BUF_SIZE
memcpy(state->nextblock, state->debugblock, state->debugsize);
#endif
if (state->thisblock_valid)
{
uint8_t *compressed;
const uint8_t *oldb, *newb;
size_t headpos, tailpos, remaining;
if (state->capacity < sizeof(size_t) + state->maxcompsize)
{
RARCH_ERR("[Rewind] %s.\n",
msg_hash_to_str(MSG_REWIND_BUFFER_CAPACITY_INSUFFICIENT));
return;
}
recheckcapacity:;
headpos = state->head - state->data;
tailpos = state->tail - state->data;
remaining = (tailpos + state->capacity -
sizeof(size_t) - headpos - 1) % state->capacity + 1;
if (remaining <= state->maxcompsize)
{
state->tail = state->data + read_size_t(state->tail);
state->entries--;
goto recheckcapacity;
}
oldb = state->thisblock;
newb = state->nextblock;
compressed = state->head + sizeof(size_t);
compressed += state_manager_raw_compress(oldb, newb,
state->blocksize, compressed);
/* The next record must fit before the end of the ring without
* folding mid-record; if it will not, the head folds to the
* start now, and the record that starts there is dropped, as a
* record dropped for room anywhere else is. */
if (compressed - state->data + state->maxcompsize > state->capacity)
{
compressed = state->data;
if (state->tail == state->data + sizeof(size_t))
{
state->tail = state->data + read_size_t(state->tail);
state->entries--;
}
}
write_size_t(compressed, state->head-state->data);
compressed += sizeof(size_t);
write_size_t(state->head, compressed-state->data);
state->head = compressed;
}
else
state->thisblock_valid = true;
swap = state->thisblock;
state->thisblock = state->nextblock;
state->nextblock = swap;
state->entries++;
}
void state_manager_event_init(
struct state_manager_rewind_state *rewind_st,
unsigned rewind_buffer_size)
{
core_info_t *core_info = NULL;
void *state = NULL;
if (!rewind_st)
return;
rewind_st->flags &= ~STATE_MGR_REWIND_ST_FLAG_INIT_PENDING;
if ( (rewind_st->flags & STATE_MGR_REWIND_ST_FLAG_INIT_ATTEMPTED)
|| rewind_st->state)
return;
rewind_st->size = 0;
retro_atomic_store_release_int(&rewind_st->frame_reversed_atomic, 0);
rewind_st->flags &= ~(
STATE_MGR_REWIND_ST_FLAG_FRAME_IS_REVERSED
| STATE_MGR_REWIND_ST_FLAG_HOTKEY_WAS_CHECKED
| STATE_MGR_REWIND_ST_FLAG_HOTKEY_WAS_PRESSED
);
/* We cannot initialise the rewind buffer
* unless the core info struct for the current
* core has been initialised (i.e. without this,
* the savestate support level for the current
* core is unknown) */
if (!core_info_get_current_core(&core_info) || !core_info)
return;
rewind_st->flags |= STATE_MGR_REWIND_ST_FLAG_INIT_ATTEMPTED;
if (!core_info_current_supports_rewind())
{
RARCH_ERR("[Rewind] %s.\n",
msg_hash_to_str(MSG_REWIND_UNSUPPORTED));
return;
}
if (audio_driver_has_callback())
{
RARCH_ERR("[Rewind] %s.\n",
msg_hash_to_str(MSG_REWIND_INIT_FAILED_THREADED_AUDIO));
return;
}
rewind_st->size = content_get_serialized_size_rewind();
if (!rewind_st->size)
{
RARCH_ERR("[Rewind] %s.\n",
msg_hash_to_str(MSG_REWIND_INIT_FAILED));
return;
}
RARCH_LOG("[Rewind] %s: %u MB\n",
msg_hash_to_str(MSG_REWIND_INIT),
(unsigned)(rewind_buffer_size / 1000000));
rewind_st->state = state_manager_new(rewind_st->size,
rewind_buffer_size);
if (!rewind_st->state)
{
RARCH_WARN("[Rewind] %s.\n",
msg_hash_to_str(MSG_REWIND_INIT_FAILED));
return;
}
state_manager_push_where(rewind_st->state, &state);
content_serialize_state_rewind(state, rewind_st->size);
state_manager_push_do(rewind_st->state);
}
void state_manager_event_deinit(
struct state_manager_rewind_state *rewind_st,
struct retro_core_t *current_core)
{
bool restore_callbacks = false;
if (!rewind_st)
return;
restore_callbacks =
(rewind_st->flags & STATE_MGR_REWIND_ST_FLAG_INIT_ATTEMPTED)
&& (rewind_st->state)
&& (current_core);
if (rewind_st->state)
{
state_manager_free(rewind_st->state);
free(rewind_st->state);
}
rewind_st->state = NULL;
rewind_st->size = 0;
retro_atomic_store_release_int(&rewind_st->frame_reversed_atomic, 0);
rewind_st->flags &= ~(
STATE_MGR_REWIND_ST_FLAG_FRAME_IS_REVERSED
| STATE_MGR_REWIND_ST_FLAG_HOTKEY_WAS_CHECKED
| STATE_MGR_REWIND_ST_FLAG_HOTKEY_WAS_PRESSED
| STATE_MGR_REWIND_ST_FLAG_INIT_ATTEMPTED
| STATE_MGR_REWIND_ST_FLAG_INIT_PENDING
);
/* Restore regular (non-rewind) core audio
* callbacks if required */
if (restore_callbacks)
{
if (current_core->retro_set_audio_sample)
current_core->retro_set_audio_sample(audio_driver_sample);
if (current_core->retro_set_audio_sample_batch)
current_core->retro_set_audio_sample_batch(audio_driver_sample_batch);
}
}
/**
* check_rewind:
* @pressed : was rewind key pressed or held?
*
* Checks if rewind toggle/hold was being pressed and/or held.
**/
bool state_manager_check_rewind(
struct state_manager_rewind_state *rewind_st,
struct retro_core_t *current_core,
bool pressed,
unsigned rewind_granularity, bool is_paused,
char *s, size_t len, unsigned *time)
{
bool ret = false;
#ifdef HAVE_NETWORKING
bool was_reversed = false;
#endif
if ( !rewind_st
|| (!(rewind_st->flags & STATE_MGR_REWIND_ST_FLAG_INIT_ATTEMPTED)))
return false;
if (!(rewind_st->flags & STATE_MGR_REWIND_ST_FLAG_HOTKEY_WAS_CHECKED))
{
rewind_st->flags |= STATE_MGR_REWIND_ST_FLAG_HOTKEY_WAS_CHECKED;
return false;
}
if (!rewind_st->state)
{
if ((pressed
&& (!(rewind_st->flags
& STATE_MGR_REWIND_ST_FLAG_HOTKEY_WAS_PRESSED)))
&& !core_info_current_supports_rewind())
{
const char *_msg = msg_hash_to_str(MSG_REWIND_UNSUPPORTED);
runloop_msg_queue_push(_msg, strlen(_msg), 1, 100, false, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
if (pressed)
rewind_st->flags |= STATE_MGR_REWIND_ST_FLAG_HOTKEY_WAS_PRESSED;
else
rewind_st->flags &= ~STATE_MGR_REWIND_ST_FLAG_HOTKEY_WAS_PRESSED;
return false;
}
if (rewind_st->flags & STATE_MGR_REWIND_ST_FLAG_FRAME_IS_REVERSED)
{
#ifdef HAVE_NETWORKING
was_reversed = true;
#endif
audio_driver_frame_is_reverse();
retro_atomic_store_release_int(&rewind_st->frame_reversed_atomic, 0);
rewind_st->flags &= ~STATE_MGR_REWIND_ST_FLAG_FRAME_IS_REVERSED;
}
if (pressed)
{
const void *buf = NULL;
if (state_manager_pop(rewind_st->state, &buf))
{
#ifdef HAVE_NETWORKING
/* Make sure netplay isn't confused */
if (!was_reversed
&& !netplay_driver_ctl(RARCH_NETPLAY_CTL_DESYNC_PUSH, NULL))
return false;
#endif
retro_atomic_store_release_int(
&rewind_st->frame_reversed_atomic, 1);
rewind_st->flags |= STATE_MGR_REWIND_ST_FLAG_FRAME_IS_REVERSED;
audio_driver_setup_rewind();
strlcpy(s, msg_hash_to_str(MSG_REWINDING), len);
*time = is_paused ? 1 : 30;
ret = true;
content_deserialize_state(buf, rewind_st->size);
#ifdef HAVE_BSV_MOVIE
bsv_movie_frame_rewind(input_state_get_ptr());
#endif
}
else
{
#ifdef HAVE_BSV_MOVIE
input_driver_state_t *input_st = input_state_get_ptr();
/* Don't end reversing during playback or recording */
if(BSV_MOVIE_IS_PLAYBACK_ON() || BSV_MOVIE_IS_RECORDING())
{
retro_atomic_store_release_int(
&rewind_st->frame_reversed_atomic, 1);
rewind_st->flags |= STATE_MGR_REWIND_ST_FLAG_FRAME_IS_REVERSED;
bsv_movie_frame_rewind(input_state_get_ptr());
}
else
#endif
content_deserialize_state(buf, rewind_st->size);
#ifdef HAVE_NETWORKING
/* Tell netplay we're done */
if (was_reversed)
netplay_driver_ctl(RARCH_NETPLAY_CTL_DESYNC_POP, NULL);
#endif
strlcpy(s,
msg_hash_to_str(MSG_REWIND_REACHED_END),
len);
*time = 30;
ret = true;
}
}
else
{
static unsigned cnt = 0;
#ifdef HAVE_NETWORKING
/* Tell netplay we're done */
if (was_reversed)
netplay_driver_ctl(RARCH_NETPLAY_CTL_DESYNC_POP, NULL);
#endif
cnt = (cnt + 1) % (rewind_granularity ?
rewind_granularity : 1); /* Avoid possible SIGFPE. */
if ( !is_paused
&& ((cnt == 0) || retroarch_ctl(RARCH_CTL_BSV_MOVIE_IS_INITED, NULL)))
{
void *state = NULL;
state_manager_push_where(rewind_st->state, &state);
content_serialize_state_rewind(state, rewind_st->size);
state_manager_push_do(rewind_st->state);
}
}
/* Update core audio callbacks */
if (current_core)
{
if (current_core->retro_set_audio_sample)
current_core->retro_set_audio_sample(
(rewind_st->flags
& STATE_MGR_REWIND_ST_FLAG_FRAME_IS_REVERSED)
? audio_driver_sample_rewind
: audio_driver_sample);
if (current_core->retro_set_audio_sample_batch)
current_core->retro_set_audio_sample_batch(
( rewind_st->flags
& STATE_MGR_REWIND_ST_FLAG_FRAME_IS_REVERSED)
? audio_driver_sample_batch_rewind
: audio_driver_sample_batch);
}
if (pressed)
rewind_st->flags |= STATE_MGR_REWIND_ST_FLAG_HOTKEY_WAS_PRESSED;
else
rewind_st->flags &= ~STATE_MGR_REWIND_ST_FLAG_HOTKEY_WAS_PRESSED;
return ret;
}