mirror of
https://github.com/libretro/RetroArch
synced 2024-12-26 21:29:08 +00:00
Allow for both -e and -R to start a BSV file recording at a state (#14898)
* Allow for both -e and -R to start a BSV file recording at a state The key issue is that loading a state takes some time, and the BSV recording shouldn't start until that's done. The minimal patch for this would just be a change to runloop.c which moves movie initialization after entry state loading, throwing in a task_queue_wait(). This makes for some awkward repeated autoload OSD messages and doesn't solve the underlying issue. Most of this change puts BSV recording start/stop into tasks, like saving and loading are tasks; this was important to centralize BSV operations a bit more and is the first part of a refactoring towards more robust input recording. The necessary wait is introduced in the begin-recording callback. Co-authored-by: Joseph C. Osborn <jcoa2018@pomona.edu>
This commit is contained in:
parent
afe3bf72bb
commit
f149519351
@ -257,6 +257,7 @@ endif
|
||||
|
||||
OBJ += \
|
||||
tasks/task_save.o \
|
||||
tasks/task_movie.o \
|
||||
tasks/task_file_transfer.o \
|
||||
tasks/task_image.o \
|
||||
tasks/task_playlist_manager.o \
|
||||
|
@ -1193,9 +1193,7 @@ void command_event_init_cheats(
|
||||
bool allow_cheats = true;
|
||||
#endif
|
||||
#ifdef HAVE_BSV_MOVIE
|
||||
bsv_movie_t *
|
||||
bsv_movie_state_handle = (bsv_movie_t*)bsv_movie_data;
|
||||
allow_cheats &= !(bsv_movie_state_handle != NULL);
|
||||
allow_cheats &= !(bsv_movie_data != NULL);
|
||||
#endif
|
||||
|
||||
if (!allow_cheats)
|
||||
|
39
command.h
39
command.h
@ -319,45 +319,6 @@ command_t* command_uds_new(void);
|
||||
|
||||
bool command_network_send(const char *cmd_);
|
||||
|
||||
#ifdef HAVE_BSV_MOVIE
|
||||
enum bsv_flags
|
||||
{
|
||||
BSV_FLAG_MOVIE_START_RECORDING = (1 << 0),
|
||||
BSV_FLAG_MOVIE_START_PLAYBACK = (1 << 1),
|
||||
BSV_FLAG_MOVIE_PLAYBACK = (1 << 2),
|
||||
BSV_FLAG_MOVIE_EOF_EXIT = (1 << 3),
|
||||
BSV_FLAG_MOVIE_END = (1 << 4)
|
||||
};
|
||||
|
||||
struct bsv_state
|
||||
{
|
||||
uint8_t flags;
|
||||
/* Movie playback/recording support. */
|
||||
char movie_path[PATH_MAX_LENGTH];
|
||||
/* Immediate playback/recording. */
|
||||
char movie_start_path[PATH_MAX_LENGTH];
|
||||
};
|
||||
|
||||
struct bsv_movie
|
||||
{
|
||||
intfstream_t *file;
|
||||
uint8_t *state;
|
||||
/* A ring buffer keeping track of positions
|
||||
* in the file for each frame. */
|
||||
size_t *frame_pos;
|
||||
size_t frame_mask;
|
||||
size_t frame_ptr;
|
||||
size_t min_file_pos;
|
||||
size_t state_size;
|
||||
|
||||
bool playback;
|
||||
bool first_rewind;
|
||||
bool did_rewind;
|
||||
};
|
||||
|
||||
typedef struct bsv_movie bsv_movie_t;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
bool command_event_save_config(
|
||||
const char *config_path,
|
||||
|
@ -74,6 +74,8 @@ bool content_deserialize_state(const void* serialized_data, size_t serialized_si
|
||||
|
||||
/* Waits for any in-progress save state tasks to finish */
|
||||
void content_wait_for_save_state_task(void);
|
||||
/* Waits for any in-progress load state tasks to finish */
|
||||
void content_wait_for_load_state_task(void);
|
||||
|
||||
/* Copy a save state. */
|
||||
bool content_rename_state(const char *origin, const char *dest);
|
||||
|
@ -1367,6 +1367,7 @@ DATA RUNLOOP
|
||||
#include "../tasks/task_patch.c"
|
||||
#endif
|
||||
#include "../tasks/task_save.c"
|
||||
#include "../tasks/task_movie.c"
|
||||
#include "../tasks/task_image.c"
|
||||
#include "../tasks/task_file_transfer.c"
|
||||
#include "../tasks/task_playlist_manager.c"
|
||||
|
@ -5146,230 +5146,12 @@ void input_driver_poll(void)
|
||||
}
|
||||
|
||||
#ifdef HAVE_BSV_MOVIE
|
||||
#define MAGIC_INDEX 0
|
||||
#define SERIALIZER_INDEX 1
|
||||
#define CRC_INDEX 2
|
||||
#define STATE_SIZE_INDEX 3
|
||||
|
||||
#define BSV_MAGIC 0x42535631
|
||||
|
||||
static bool bsv_movie_init_playback(
|
||||
bsv_movie_t *handle, const char *path)
|
||||
void bsv_movie_free(bsv_movie_t*);
|
||||
void bsv_movie_deinit(input_driver_state_t *input_st)
|
||||
{
|
||||
uint32_t state_size = 0;
|
||||
uint32_t header[4] = {0};
|
||||
intfstream_t *file = intfstream_open_file(path,
|
||||
RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
|
||||
if (!file)
|
||||
{
|
||||
RARCH_ERR("Could not open BSV file for playback, path : \"%s\".\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
handle->file = file;
|
||||
handle->playback = true;
|
||||
|
||||
intfstream_read(handle->file, header, sizeof(uint32_t) * 4);
|
||||
/* Compatibility with old implementation that
|
||||
* used incorrect documentation. */
|
||||
if (swap_if_little32(header[MAGIC_INDEX]) != BSV_MAGIC
|
||||
&& swap_if_big32(header[MAGIC_INDEX]) != BSV_MAGIC)
|
||||
{
|
||||
RARCH_ERR("%s\n", msg_hash_to_str(MSG_MOVIE_FILE_IS_NOT_A_VALID_BSV1_FILE));
|
||||
return false;
|
||||
}
|
||||
|
||||
state_size = swap_if_big32(header[STATE_SIZE_INDEX]);
|
||||
|
||||
#if 0
|
||||
RARCH_ERR("----- debug %u -----\n", header[0]);
|
||||
RARCH_ERR("----- debug %u -----\n", header[1]);
|
||||
RARCH_ERR("----- debug %u -----\n", header[2]);
|
||||
RARCH_ERR("----- debug %u -----\n", header[3]);
|
||||
#endif
|
||||
|
||||
if (state_size)
|
||||
{
|
||||
retro_ctx_size_info_t info;
|
||||
retro_ctx_serialize_info_t serial_info;
|
||||
uint8_t *buf = (uint8_t*)malloc(state_size);
|
||||
|
||||
if (!buf)
|
||||
return false;
|
||||
|
||||
handle->state = buf;
|
||||
handle->state_size = state_size;
|
||||
if (intfstream_read(handle->file,
|
||||
handle->state, state_size) != state_size)
|
||||
{
|
||||
RARCH_ERR("%s\n", msg_hash_to_str(MSG_COULD_NOT_READ_STATE_FROM_MOVIE));
|
||||
return false;
|
||||
}
|
||||
|
||||
core_serialize_size( &info);
|
||||
|
||||
if (info.size == state_size)
|
||||
{
|
||||
serial_info.data_const = handle->state;
|
||||
serial_info.size = state_size;
|
||||
core_unserialize(&serial_info);
|
||||
}
|
||||
else
|
||||
RARCH_WARN("%s\n",
|
||||
msg_hash_to_str(MSG_MOVIE_FORMAT_DIFFERENT_SERIALIZER_VERSION));
|
||||
}
|
||||
|
||||
handle->min_file_pos = sizeof(header) + state_size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool bsv_movie_init_record(
|
||||
bsv_movie_t *handle, const char *path)
|
||||
{
|
||||
retro_ctx_size_info_t info;
|
||||
uint32_t state_size = 0;
|
||||
uint32_t content_crc = 0;
|
||||
uint32_t header[4] = {0};
|
||||
intfstream_t *file = intfstream_open_file(path,
|
||||
RETRO_VFS_FILE_ACCESS_WRITE,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
|
||||
if (!file)
|
||||
{
|
||||
RARCH_ERR("Could not open BSV file for recording, path : \"%s\".\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
handle->file = file;
|
||||
|
||||
content_crc = content_get_crc();
|
||||
|
||||
/* This value is supposed to show up as
|
||||
* BSV1 in a HEX editor, big-endian. */
|
||||
header[MAGIC_INDEX] = swap_if_little32(BSV_MAGIC);
|
||||
header[CRC_INDEX] = swap_if_big32(content_crc);
|
||||
|
||||
core_serialize_size(&info);
|
||||
|
||||
state_size = (unsigned)info.size;
|
||||
|
||||
header[STATE_SIZE_INDEX] = swap_if_big32(state_size);
|
||||
|
||||
intfstream_write(handle->file, header, 4 * sizeof(uint32_t));
|
||||
|
||||
handle->min_file_pos = sizeof(header) + state_size;
|
||||
handle->state_size = state_size;
|
||||
|
||||
if (state_size)
|
||||
{
|
||||
retro_ctx_serialize_info_t serial_info;
|
||||
uint8_t *st = (uint8_t*)malloc(state_size);
|
||||
|
||||
if (!st)
|
||||
return false;
|
||||
|
||||
handle->state = st;
|
||||
|
||||
serial_info.data = handle->state;
|
||||
serial_info.size = state_size;
|
||||
|
||||
core_serialize(&serial_info);
|
||||
|
||||
intfstream_write(handle->file,
|
||||
handle->state, state_size);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void bsv_movie_free(bsv_movie_t *handle)
|
||||
{
|
||||
intfstream_close(handle->file);
|
||||
free(handle->file);
|
||||
|
||||
free(handle->state);
|
||||
free(handle->frame_pos);
|
||||
free(handle);
|
||||
}
|
||||
|
||||
static bsv_movie_t *bsv_movie_init_internal(const char *path,
|
||||
enum rarch_movie_type type)
|
||||
{
|
||||
size_t *frame_pos = NULL;
|
||||
bsv_movie_t *handle = (bsv_movie_t*)calloc(1, sizeof(*handle));
|
||||
|
||||
if (!handle)
|
||||
return NULL;
|
||||
|
||||
if (type == RARCH_MOVIE_PLAYBACK)
|
||||
{
|
||||
if (!bsv_movie_init_playback(handle, path))
|
||||
goto error;
|
||||
}
|
||||
else if (!bsv_movie_init_record(handle, path))
|
||||
goto error;
|
||||
|
||||
/* Just pick something really large
|
||||
* ~1 million frames rewind should do the trick. */
|
||||
if (!(frame_pos = (size_t*)calloc((1 << 20), sizeof(size_t))))
|
||||
goto error;
|
||||
|
||||
handle->frame_pos = frame_pos;
|
||||
|
||||
handle->frame_pos[0] = handle->min_file_pos;
|
||||
handle->frame_mask = (1 << 20) - 1;
|
||||
|
||||
return handle;
|
||||
|
||||
error:
|
||||
if (handle)
|
||||
bsv_movie_free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool runloop_check_movie_init(input_driver_state_t *input_st,
|
||||
settings_t *settings)
|
||||
{
|
||||
size_t _len;
|
||||
char msg[16384], path[8192];
|
||||
bsv_movie_t *state = NULL;
|
||||
int state_slot = settings->ints.state_slot;
|
||||
msg[0] = '\0';
|
||||
|
||||
configuration_set_uint(settings, settings->uints.rewind_granularity, 1);
|
||||
|
||||
_len = strlcpy(path,
|
||||
input_st->bsv_movie_state.movie_path, sizeof(path));
|
||||
if (state_slot > 0)
|
||||
snprintf(path + _len, sizeof(path) - _len, "%d", state_slot);
|
||||
strlcat(path, ".bsv", sizeof(path));
|
||||
|
||||
snprintf(msg, sizeof(msg), "%s \"%s\".",
|
||||
msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO),
|
||||
path);
|
||||
|
||||
if (!(state = bsv_movie_init_internal(path, RARCH_MOVIE_RECORD)))
|
||||
{
|
||||
runloop_msg_queue_push(
|
||||
msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD),
|
||||
2, 180, true,
|
||||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||||
RARCH_ERR("%s\n",
|
||||
msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD));
|
||||
return false;
|
||||
}
|
||||
|
||||
input_st->bsv_movie_state_handle = state;
|
||||
|
||||
runloop_msg_queue_push(msg, 2, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||||
RARCH_LOG("%s \"%s\".\n",
|
||||
msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO),
|
||||
path);
|
||||
|
||||
return true;
|
||||
if (input_st->bsv_movie_state_handle)
|
||||
bsv_movie_free(input_st->bsv_movie_state_handle);
|
||||
input_st->bsv_movie_state_handle = NULL;
|
||||
}
|
||||
|
||||
void bsv_movie_frame_rewind(void)
|
||||
@ -5428,115 +5210,6 @@ void bsv_movie_frame_rewind(void)
|
||||
}
|
||||
}
|
||||
|
||||
bool bsv_movie_init(input_driver_state_t *input_st)
|
||||
{
|
||||
bsv_movie_t *state = NULL;
|
||||
if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_PLAYBACK)
|
||||
{
|
||||
const char *starting_movie_str = NULL;
|
||||
if (!(state = bsv_movie_init_internal(
|
||||
input_st->bsv_movie_state.movie_start_path,
|
||||
RARCH_MOVIE_PLAYBACK)))
|
||||
{
|
||||
RARCH_ERR("%s: \"%s\".\n",
|
||||
msg_hash_to_str(MSG_FAILED_TO_LOAD_MOVIE_FILE),
|
||||
input_st->bsv_movie_state.movie_start_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
input_st->bsv_movie_state_handle = state;
|
||||
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_PLAYBACK;
|
||||
starting_movie_str =
|
||||
msg_hash_to_str(MSG_STARTING_MOVIE_PLAYBACK);
|
||||
|
||||
runloop_msg_queue_push(starting_movie_str,
|
||||
2, 180, false,
|
||||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||||
RARCH_LOG("%s.\n", starting_movie_str);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_RECORDING)
|
||||
{
|
||||
char msg[8192];
|
||||
const char *movie_rec_str = msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO);
|
||||
|
||||
if (!(state = bsv_movie_init_internal(
|
||||
input_st->bsv_movie_state.movie_start_path,
|
||||
RARCH_MOVIE_RECORD)))
|
||||
{
|
||||
const char *movie_rec_fail_str =
|
||||
msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD);
|
||||
runloop_msg_queue_push(movie_rec_fail_str,
|
||||
1, 180, true,
|
||||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||||
RARCH_ERR("%s.\n", movie_rec_fail_str);
|
||||
return false;
|
||||
}
|
||||
|
||||
input_st->bsv_movie_state_handle = state;
|
||||
snprintf(msg, sizeof(msg),
|
||||
"%s \"%s\".", movie_rec_str,
|
||||
input_st->bsv_movie_state.movie_start_path);
|
||||
|
||||
runloop_msg_queue_push(msg, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||||
RARCH_LOG("%s \"%s\".\n", movie_rec_str,
|
||||
input_st->bsv_movie_state.movie_start_path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void bsv_movie_deinit(input_driver_state_t *input_st)
|
||||
{
|
||||
if (input_st->bsv_movie_state_handle)
|
||||
bsv_movie_free(input_st->bsv_movie_state_handle);
|
||||
input_st->bsv_movie_state_handle = NULL;
|
||||
}
|
||||
|
||||
bool bsv_movie_check(input_driver_state_t *input_st,
|
||||
settings_t *settings)
|
||||
{
|
||||
const char *movie_rec_stopped_str = NULL;
|
||||
if (!input_st->bsv_movie_state_handle)
|
||||
return runloop_check_movie_init(input_st, settings);
|
||||
|
||||
if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_PLAYBACK)
|
||||
{
|
||||
const char *movie_playback_end_str = NULL;
|
||||
/* Checks if movie is being played back. */
|
||||
if (!(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_END))
|
||||
return false;
|
||||
movie_playback_end_str = msg_hash_to_str(MSG_MOVIE_PLAYBACK_ENDED);
|
||||
runloop_msg_queue_push(
|
||||
movie_playback_end_str, 2, 180, false,
|
||||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||||
RARCH_LOG("%s\n", movie_playback_end_str);
|
||||
|
||||
bsv_movie_deinit(input_st);
|
||||
|
||||
input_st->bsv_movie_state.flags &= ~(
|
||||
BSV_FLAG_MOVIE_END
|
||||
| BSV_FLAG_MOVIE_PLAYBACK);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Checks if movie is being recorded. */
|
||||
if (!input_st->bsv_movie_state_handle)
|
||||
return false;
|
||||
|
||||
movie_rec_stopped_str = msg_hash_to_str(MSG_MOVIE_RECORD_STOPPED);
|
||||
runloop_msg_queue_push(movie_rec_stopped_str,
|
||||
2, 180, true,
|
||||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||||
RARCH_LOG("%s\n", movie_rec_stopped_str);
|
||||
|
||||
bsv_movie_deinit(input_st);
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
int16_t input_state_internal(unsigned port, unsigned device,
|
||||
|
@ -108,6 +108,45 @@ enum rarch_movie_type
|
||||
RARCH_MOVIE_RECORD
|
||||
};
|
||||
|
||||
#ifdef HAVE_BSV_MOVIE
|
||||
enum bsv_flags
|
||||
{
|
||||
BSV_FLAG_MOVIE_START_RECORDING = (1 << 0),
|
||||
BSV_FLAG_MOVIE_START_PLAYBACK = (1 << 1),
|
||||
BSV_FLAG_MOVIE_PLAYBACK = (1 << 2),
|
||||
BSV_FLAG_MOVIE_EOF_EXIT = (1 << 3),
|
||||
BSV_FLAG_MOVIE_END = (1 << 4)
|
||||
};
|
||||
|
||||
struct bsv_state
|
||||
{
|
||||
uint8_t flags;
|
||||
/* Movie playback/recording support. */
|
||||
char movie_path[PATH_MAX_LENGTH];
|
||||
/* Immediate playback/recording. */
|
||||
char movie_start_path[PATH_MAX_LENGTH];
|
||||
};
|
||||
|
||||
struct bsv_movie
|
||||
{
|
||||
intfstream_t *file;
|
||||
uint8_t *state;
|
||||
/* A ring buffer keeping track of positions
|
||||
* in the file for each frame. */
|
||||
size_t *frame_pos;
|
||||
size_t frame_mask;
|
||||
size_t frame_ptr;
|
||||
size_t min_file_pos;
|
||||
size_t state_size;
|
||||
|
||||
bool playback;
|
||||
bool first_rewind;
|
||||
bool did_rewind;
|
||||
};
|
||||
|
||||
typedef struct bsv_movie bsv_movie_t;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* line_complete callback (when carriage return is pressed)
|
||||
*
|
||||
@ -949,13 +988,15 @@ void input_overlay_init(void);
|
||||
|
||||
#ifdef HAVE_BSV_MOVIE
|
||||
void bsv_movie_frame_rewind(void);
|
||||
|
||||
bool bsv_movie_init(input_driver_state_t *input_st);
|
||||
|
||||
void bsv_movie_deinit(input_driver_state_t *input_st);
|
||||
|
||||
bool bsv_movie_check(input_driver_state_t *input_st,
|
||||
settings_t *settings);
|
||||
bool movie_start_playback(input_driver_state_t *input_st, char *path);
|
||||
bool movie_start_record(input_driver_state_t *input_st, char *path);
|
||||
bool movie_stop_playback();
|
||||
bool movie_stop_record(input_driver_state_t *input_st);
|
||||
bool movie_toggle_record(input_driver_state_t *input_st, settings_t *settings);
|
||||
bool movie_stop(input_driver_state_t *input_st);
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
19
retroarch.c
19
retroarch.c
@ -2176,7 +2176,7 @@ bool command_event(enum event_command cmd, void *data)
|
||||
{
|
||||
#ifdef HAVE_BSV_MOVIE
|
||||
input_driver_state_t *input_st = input_state_get_ptr();
|
||||
bsv_movie_check(input_st, settings);
|
||||
movie_toggle_record(input_st, settings);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
@ -2447,7 +2447,10 @@ bool command_event(enum event_command cmd, void *data)
|
||||
* we absolutely cannot change game state. */
|
||||
input_driver_state_t *input_st = input_state_get_ptr();
|
||||
if (input_st->bsv_movie_state_handle)
|
||||
{
|
||||
RARCH_LOG("[Load] [Movie] Can't load state during movie playback or record\n");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CHEEVOS
|
||||
@ -5905,6 +5908,17 @@ static bool retroarch_parse_input_and_config(
|
||||
runloop_st->entry_state_slot = 0;
|
||||
RARCH_WARN("Trying to load entry state without content. Ignoring.\n");
|
||||
}
|
||||
#ifdef HAVE_BSV_MOVIE
|
||||
if (runloop_st->entry_state_slot)
|
||||
{
|
||||
input_driver_state_t *input_st = input_state_get_ptr();
|
||||
if(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_PLAYBACK) {
|
||||
runloop_st->entry_state_slot = 0;
|
||||
RARCH_WARN("Trying to load entry state while BSV playback is active. Ignoring entry state.\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/* Check whether a core has been set via the
|
||||
* command line interface */
|
||||
@ -6418,9 +6432,8 @@ bool retroarch_ctl(enum rarch_ctl_state state, void *data)
|
||||
cheat_manager_state_free();
|
||||
#endif
|
||||
#ifdef HAVE_BSV_MOVIE
|
||||
bsv_movie_deinit(input_st);
|
||||
movie_stop(input_st);
|
||||
#endif
|
||||
|
||||
command_event(CMD_EVENT_CORE_DEINIT, NULL);
|
||||
|
||||
content_deinit();
|
||||
|
51
runloop.c
51
runloop.c
@ -4143,16 +4143,6 @@ static bool event_init_content(
|
||||
RARCH_LOG("[SRAM]: %s\n",
|
||||
msg_hash_to_str(MSG_SKIPPING_SRAM_LOAD));
|
||||
|
||||
#ifdef HAVE_BSV_MOVIE
|
||||
bsv_movie_deinit(input_st);
|
||||
if (bsv_movie_init(input_st))
|
||||
{
|
||||
/* Set granularity upon success */
|
||||
configuration_set_uint(settings,
|
||||
settings->uints.rewind_granularity, 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
Since the operations are asynchronous we can't
|
||||
guarantee users will not use auto_load_state to cheat on
|
||||
@ -4162,17 +4152,44 @@ static bool event_init_content(
|
||||
*/
|
||||
#ifdef HAVE_CHEEVOS
|
||||
if (!cheevos_enable || !cheevos_hardcore_mode_enable)
|
||||
#endif
|
||||
#ifdef HAVE_BSV_MOVIE
|
||||
if (!input_st->bsv_movie_state_handle)
|
||||
#endif
|
||||
{
|
||||
if (runloop_st->entry_state_slot && !command_event_load_entry_state(settings))
|
||||
runloop_st->entry_state_slot = 0;
|
||||
if (!runloop_st->entry_state_slot && settings->bools.savestate_auto_load)
|
||||
command_event_load_auto_state();
|
||||
#ifdef HAVE_BSV_MOVIE
|
||||
/* ignore entry state if we're doing bsv playback (we do want it
|
||||
for bsv recording though) */
|
||||
if (!(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_PLAYBACK))
|
||||
#endif
|
||||
{
|
||||
if (runloop_st->entry_state_slot && !command_event_load_entry_state(settings))
|
||||
{
|
||||
/* loading the state failed, reset entry slot */
|
||||
runloop_st->entry_state_slot = 0;
|
||||
}
|
||||
}
|
||||
#ifdef HAVE_BSV_MOVIE
|
||||
/* ignore autoload state if we're doing bsv playback or recording */
|
||||
if (!(input_st->bsv_movie_state.flags & (BSV_FLAG_MOVIE_START_RECORDING | BSV_FLAG_MOVIE_START_PLAYBACK)))
|
||||
#endif
|
||||
{
|
||||
if (!runloop_st->entry_state_slot && settings->bools.savestate_auto_load)
|
||||
command_event_load_auto_state();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_BSV_MOVIE
|
||||
movie_stop(input_st);
|
||||
if(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_RECORDING)
|
||||
{
|
||||
configuration_set_uint(settings, settings->uints.rewind_granularity, 1);
|
||||
movie_start_record(input_st, input_st->bsv_movie_state.movie_start_path);
|
||||
}
|
||||
else if(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_START_PLAYBACK)
|
||||
{
|
||||
configuration_set_uint(settings, settings->uints.rewind_granularity, 1);
|
||||
movie_start_playback(input_st, input_st->bsv_movie_state.movie_start_path);
|
||||
}
|
||||
#endif
|
||||
|
||||
command_event(CMD_EVENT_NETPLAY_INIT, NULL);
|
||||
|
||||
return true;
|
||||
|
484
tasks/task_movie.c
Normal file
484
tasks/task_movie.c
Normal file
@ -0,0 +1,484 @@
|
||||
/* RetroArch - A frontend for libretro.
|
||||
* Copyright (C) 2011-2017 - Daniel De Matteis
|
||||
* Copyright (C) 2016-2019 - Brad Parker
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_BSV_MOVIE
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <compat/strl.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "../config.h"
|
||||
#endif
|
||||
|
||||
#include "../msg_hash.h"
|
||||
#include "../verbosity.h"
|
||||
#include "../core.h"
|
||||
#include "../content.h"
|
||||
#include "../runloop.h"
|
||||
#include "tasks_internal.h"
|
||||
#include "../input/input_driver.h"
|
||||
|
||||
#define MAGIC_INDEX 0
|
||||
#define SERIALIZER_INDEX 1
|
||||
#define CRC_INDEX 2
|
||||
#define STATE_SIZE_INDEX 3
|
||||
|
||||
#define BSV_MAGIC 0x42535631
|
||||
|
||||
/* Private functions */
|
||||
|
||||
static bool bsv_movie_init_playback(
|
||||
bsv_movie_t *handle, const char *path)
|
||||
{
|
||||
uint32_t state_size = 0;
|
||||
uint32_t header[4] = {0};
|
||||
intfstream_t *file = intfstream_open_file(path,
|
||||
RETRO_VFS_FILE_ACCESS_READ,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
|
||||
if (!file)
|
||||
{
|
||||
RARCH_ERR("Could not open BSV file for playback, path : \"%s\".\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
handle->file = file;
|
||||
handle->playback = true;
|
||||
|
||||
intfstream_read(handle->file, header, sizeof(uint32_t) * 4);
|
||||
/* Compatibility with old implementation that
|
||||
* used incorrect documentation. */
|
||||
if (swap_if_little32(header[MAGIC_INDEX]) != BSV_MAGIC
|
||||
&& swap_if_big32(header[MAGIC_INDEX]) != BSV_MAGIC)
|
||||
{
|
||||
RARCH_ERR("%s\n", msg_hash_to_str(MSG_MOVIE_FILE_IS_NOT_A_VALID_BSV1_FILE));
|
||||
return false;
|
||||
}
|
||||
|
||||
state_size = swap_if_big32(header[STATE_SIZE_INDEX]);
|
||||
|
||||
#if 0
|
||||
RARCH_ERR("----- debug %u -----\n", header[0]);
|
||||
RARCH_ERR("----- debug %u -----\n", header[1]);
|
||||
RARCH_ERR("----- debug %u -----\n", header[2]);
|
||||
RARCH_ERR("----- debug %u -----\n", header[3]);
|
||||
#endif
|
||||
|
||||
if (state_size)
|
||||
{
|
||||
retro_ctx_size_info_t info;
|
||||
retro_ctx_serialize_info_t serial_info;
|
||||
uint8_t *buf = (uint8_t*)malloc(state_size);
|
||||
|
||||
if (!buf)
|
||||
return false;
|
||||
|
||||
handle->state = buf;
|
||||
handle->state_size = state_size;
|
||||
if (intfstream_read(handle->file,
|
||||
handle->state, state_size) != state_size)
|
||||
{
|
||||
RARCH_ERR("%s\n", msg_hash_to_str(MSG_COULD_NOT_READ_STATE_FROM_MOVIE));
|
||||
return false;
|
||||
}
|
||||
core_serialize_size( &info);
|
||||
|
||||
if (info.size == state_size)
|
||||
{
|
||||
serial_info.data_const = handle->state;
|
||||
serial_info.size = state_size;
|
||||
core_unserialize(&serial_info);
|
||||
}
|
||||
else
|
||||
RARCH_WARN("%s\n",
|
||||
msg_hash_to_str(MSG_MOVIE_FORMAT_DIFFERENT_SERIALIZER_VERSION));
|
||||
}
|
||||
|
||||
handle->min_file_pos = sizeof(header) + state_size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool bsv_movie_init_record(
|
||||
bsv_movie_t *handle, const char *path)
|
||||
{
|
||||
retro_ctx_size_info_t info;
|
||||
uint32_t state_size = 0;
|
||||
uint32_t content_crc = 0;
|
||||
uint32_t header[4] = {0};
|
||||
intfstream_t *file = intfstream_open_file(path,
|
||||
RETRO_VFS_FILE_ACCESS_WRITE,
|
||||
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
||||
|
||||
if (!file)
|
||||
{
|
||||
RARCH_ERR("Could not open BSV file for recording, path : \"%s\".\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
handle->file = file;
|
||||
|
||||
content_crc = content_get_crc();
|
||||
|
||||
/* This value is supposed to show up as
|
||||
* BSV1 in a HEX editor, big-endian. */
|
||||
header[MAGIC_INDEX] = swap_if_little32(BSV_MAGIC);
|
||||
header[CRC_INDEX] = swap_if_big32(content_crc);
|
||||
core_serialize_size(&info);
|
||||
|
||||
state_size = (unsigned)info.size;
|
||||
|
||||
header[STATE_SIZE_INDEX] = swap_if_big32(state_size);
|
||||
|
||||
intfstream_write(handle->file, header, 4 * sizeof(uint32_t));
|
||||
|
||||
handle->min_file_pos = sizeof(header) + state_size;
|
||||
handle->state_size = state_size;
|
||||
|
||||
if (state_size)
|
||||
{
|
||||
retro_ctx_serialize_info_t serial_info;
|
||||
uint8_t *st = (uint8_t*)malloc(state_size);
|
||||
|
||||
if (!st)
|
||||
return false;
|
||||
|
||||
handle->state = st;
|
||||
|
||||
serial_info.data = handle->state;
|
||||
serial_info.size = state_size;
|
||||
|
||||
core_serialize(&serial_info);
|
||||
|
||||
intfstream_write(handle->file,
|
||||
handle->state, state_size);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void bsv_movie_free(bsv_movie_t *handle)
|
||||
{
|
||||
intfstream_close(handle->file);
|
||||
free(handle->file);
|
||||
|
||||
free(handle->state);
|
||||
free(handle->frame_pos);
|
||||
free(handle);
|
||||
}
|
||||
|
||||
static bsv_movie_t *bsv_movie_init_internal(const char *path, enum rarch_movie_type type)
|
||||
{
|
||||
size_t *frame_pos = NULL;
|
||||
bsv_movie_t *handle = (bsv_movie_t*)calloc(1, sizeof(*handle));
|
||||
|
||||
if (!handle)
|
||||
return NULL;
|
||||
|
||||
if (type == RARCH_MOVIE_PLAYBACK)
|
||||
{
|
||||
if (!bsv_movie_init_playback(handle, path))
|
||||
goto error;
|
||||
}
|
||||
else if (!bsv_movie_init_record(handle, path))
|
||||
goto error;
|
||||
|
||||
/* Just pick something really large
|
||||
* ~1 million frames rewind should do the trick. */
|
||||
if (!(frame_pos = (size_t*)calloc((1 << 20), sizeof(size_t))))
|
||||
goto error;
|
||||
|
||||
handle->frame_pos = frame_pos;
|
||||
|
||||
handle->frame_pos[0] = handle->min_file_pos;
|
||||
handle->frame_mask = (1 << 20) - 1;
|
||||
|
||||
return handle;
|
||||
|
||||
error:
|
||||
if (handle)
|
||||
bsv_movie_free(handle);
|
||||
return NULL;
|
||||
}
|
||||
bool bsv_movie_start_record(input_driver_state_t * input_st, char *path)
|
||||
{
|
||||
bsv_movie_t *state = NULL;
|
||||
const char *movie_rec_str = msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO);
|
||||
char msg[8192];
|
||||
|
||||
/* this should trigger a start recording task which on failure or
|
||||
success prints a message and on success sets the
|
||||
input_st->bsv_movie_state_handle. */
|
||||
if (!(state = bsv_movie_init_internal(path, RARCH_MOVIE_RECORD)))
|
||||
{
|
||||
const char *movie_rec_fail_str =
|
||||
msg_hash_to_str(MSG_FAILED_TO_START_MOVIE_RECORD);
|
||||
runloop_msg_queue_push(movie_rec_fail_str,
|
||||
1, 180, true,
|
||||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||||
RARCH_ERR("%s.\n", movie_rec_fail_str);
|
||||
return false;
|
||||
}
|
||||
|
||||
input_st->bsv_movie_state_handle = state;
|
||||
snprintf(msg, sizeof(msg),
|
||||
"%s \"%s\".", movie_rec_str,
|
||||
path);
|
||||
|
||||
runloop_msg_queue_push(msg, 2, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||||
RARCH_LOG("%s \"%s\".\n", movie_rec_str, path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool bsv_movie_start_playback(input_driver_state_t *input_st, char *path) {
|
||||
bsv_movie_t *state = NULL;
|
||||
const char *starting_movie_str = NULL;
|
||||
|
||||
/* this should trigger a start playback task which on failure or
|
||||
success prints a message and on success sets the
|
||||
input_st->bsv_movie_state_handle. */
|
||||
|
||||
if (!(state = bsv_movie_init_internal(path, RARCH_MOVIE_PLAYBACK)))
|
||||
{
|
||||
RARCH_ERR("%s: \"%s\".\n",
|
||||
msg_hash_to_str(MSG_FAILED_TO_LOAD_MOVIE_FILE),
|
||||
path);
|
||||
return false;
|
||||
}
|
||||
|
||||
input_st->bsv_movie_state_handle = state;
|
||||
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_PLAYBACK;
|
||||
starting_movie_str =
|
||||
msg_hash_to_str(MSG_STARTING_MOVIE_PLAYBACK);
|
||||
|
||||
runloop_msg_queue_push(starting_movie_str,
|
||||
2, 180, false,
|
||||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||||
RARCH_LOG("%s.\n", starting_movie_str);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* Task infrastructure (also private) */
|
||||
|
||||
/* Future: replace stop functions with tasks that do the same. then
|
||||
later we can replace the start_record/start_playback flags and
|
||||
remove the entirety of input_driver_st bsv_state, which is only
|
||||
needed due to mixing sync and async during initialization. */
|
||||
|
||||
typedef struct bsv_state moviectl_task_state_t;
|
||||
|
||||
static void task_moviectl_playback_handler(retro_task_t *task)
|
||||
{
|
||||
/* trivial handler */
|
||||
task_set_finished(task, true);
|
||||
if (!task_get_error(task) && task_get_cancelled(task))
|
||||
task_set_error(task, strdup("Task canceled"));
|
||||
|
||||
task_set_data(task, task->state);
|
||||
task->state = NULL;
|
||||
/* no need to free state here since I'm recycling it as data */
|
||||
}
|
||||
static void moviectl_start_playback_cb(retro_task_t *task,
|
||||
void *task_data,
|
||||
void *user_data, const char *error)
|
||||
{
|
||||
struct bsv_state *state = (struct bsv_state *)task_data;
|
||||
input_driver_state_t *input_st = input_state_get_ptr();
|
||||
input_st->bsv_movie_state = *state;
|
||||
bsv_movie_start_playback(input_st, state->movie_path);
|
||||
free(state);
|
||||
}
|
||||
bool content_load_state_in_progress(void* data);
|
||||
static void task_moviectl_record_handler(retro_task_t *task)
|
||||
{
|
||||
if(content_load_state_in_progress(NULL))
|
||||
{
|
||||
/* hang on until the state is loaded */
|
||||
return;
|
||||
}
|
||||
/* trivial handler */
|
||||
task_set_finished(task, true);
|
||||
if (!task_get_error(task) && task_get_cancelled(task))
|
||||
task_set_error(task, strdup("Task canceled"));
|
||||
|
||||
task_set_data(task, task->state);
|
||||
task->state = NULL;
|
||||
/* no need to free state here since I'm recycling it as data */
|
||||
}
|
||||
static void moviectl_start_record_cb(retro_task_t *task,
|
||||
void *task_data,
|
||||
void *user_data, const char *error)
|
||||
{
|
||||
struct bsv_state *state = (struct bsv_state *)task_data;
|
||||
input_driver_state_t *input_st = input_state_get_ptr();
|
||||
input_st->bsv_movie_state = *state;
|
||||
bsv_movie_start_record(input_st, state->movie_path);
|
||||
free(state);
|
||||
}
|
||||
|
||||
/* Public functions */
|
||||
|
||||
bool movie_toggle_record(input_driver_state_t *input_st, settings_t *settings)
|
||||
{
|
||||
if (!input_st->bsv_movie_state_handle)
|
||||
{
|
||||
size_t _len;
|
||||
char path[8192];
|
||||
int state_slot = settings->ints.state_slot;
|
||||
|
||||
configuration_set_uint(settings, settings->uints.rewind_granularity, 1);
|
||||
|
||||
_len = strlcpy(path,
|
||||
input_st->bsv_movie_state.movie_path, sizeof(path));
|
||||
if (state_slot > 0)
|
||||
snprintf(path + _len, sizeof(path) - _len, "%d", state_slot);
|
||||
strlcat(path, ".bsv", sizeof(path));
|
||||
|
||||
return movie_start_record(input_st, path);
|
||||
}
|
||||
|
||||
return movie_stop(input_st);
|
||||
}
|
||||
|
||||
/* in the future this should probably be a deferred task as well */
|
||||
bool movie_stop_playback(input_driver_state_t *input_st){
|
||||
const char *movie_playback_end_str = NULL;
|
||||
/* Checks if movie is being played back. */
|
||||
if (!(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_PLAYBACK))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_END))
|
||||
return false;
|
||||
movie_playback_end_str = msg_hash_to_str(MSG_MOVIE_PLAYBACK_ENDED);
|
||||
runloop_msg_queue_push(
|
||||
movie_playback_end_str, 2, 180, false,
|
||||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||||
RARCH_LOG("%s\n", movie_playback_end_str);
|
||||
|
||||
bsv_movie_deinit(input_st);
|
||||
|
||||
input_st->bsv_movie_state.flags &= ~(
|
||||
BSV_FLAG_MOVIE_END
|
||||
| BSV_FLAG_MOVIE_PLAYBACK);
|
||||
return true;
|
||||
}
|
||||
/* in the future this should probably be a deferred task as well */
|
||||
bool movie_stop_record(input_driver_state_t *input_st) {
|
||||
const char *movie_rec_stopped_str = NULL;
|
||||
movie_rec_stopped_str = msg_hash_to_str(MSG_MOVIE_RECORD_STOPPED);
|
||||
if(!(input_st->bsv_movie_state_handle))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
runloop_msg_queue_push(movie_rec_stopped_str,
|
||||
2, 180, true,
|
||||
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
||||
RARCH_LOG("%s\n", movie_rec_stopped_str);
|
||||
bsv_movie_deinit(input_st);
|
||||
return true;
|
||||
|
||||
}
|
||||
bool movie_stop(input_driver_state_t *input_st) {
|
||||
if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_PLAYBACK)
|
||||
{
|
||||
return movie_stop_playback(input_st);
|
||||
}
|
||||
else if(input_st->bsv_movie_state_handle)
|
||||
{
|
||||
return movie_stop_record(input_st);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool movie_start_playback(input_driver_state_t *input_st, char *path)
|
||||
{
|
||||
retro_task_t *task = task_init();
|
||||
moviectl_task_state_t *state = (moviectl_task_state_t *) calloc(1, sizeof(*state));
|
||||
if (!task || !state)
|
||||
goto error;
|
||||
*state = input_st->bsv_movie_state;
|
||||
strlcpy(state->movie_path, path, sizeof(state->movie_path));
|
||||
task->type = TASK_TYPE_NONE;
|
||||
task->state = state;
|
||||
task->handler = task_moviectl_playback_handler;
|
||||
task->callback = moviectl_start_playback_cb;
|
||||
task->title = strdup(msg_hash_to_str(MSG_STARTING_MOVIE_PLAYBACK));
|
||||
|
||||
if (!task_queue_push(task))
|
||||
goto error;
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (state)
|
||||
free(state);
|
||||
if (task)
|
||||
free(task);
|
||||
|
||||
return false;
|
||||
}
|
||||
bool movie_start_record(input_driver_state_t *input_st, char*path)
|
||||
{
|
||||
const char *movie_rec_str = msg_hash_to_str(MSG_STARTING_MOVIE_RECORD_TO);
|
||||
char msg[8192];
|
||||
retro_task_t *task = task_init();
|
||||
moviectl_task_state_t *state = (moviectl_task_state_t *) calloc(1, sizeof(*state));
|
||||
if (!task || !state)
|
||||
goto error;
|
||||
|
||||
*state = input_st->bsv_movie_state;
|
||||
strlcpy(state->movie_path, path, sizeof(state->movie_path));
|
||||
|
||||
msg[0] = '\0';
|
||||
snprintf(msg, sizeof(msg),
|
||||
"%s \"%s\".", movie_rec_str,
|
||||
state->movie_path);
|
||||
|
||||
task->type = TASK_TYPE_NONE;
|
||||
task->state = state;
|
||||
task->handler = task_moviectl_record_handler;
|
||||
task->callback = moviectl_start_record_cb;
|
||||
|
||||
task->title = strdup(msg);
|
||||
|
||||
if (!task_queue_push(task))
|
||||
goto error;
|
||||
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (state)
|
||||
free(state);
|
||||
if (task)
|
||||
free(task);
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
@ -1611,6 +1611,28 @@ void content_wait_for_save_state_task(void)
|
||||
task_queue_wait(content_save_state_in_progress, NULL);
|
||||
}
|
||||
|
||||
|
||||
static bool task_load_state_finder(retro_task_t *task, void *user_data)
|
||||
{
|
||||
return (task && task->handler == task_load_handler);
|
||||
}
|
||||
|
||||
/* Returns true if a load state task is in progress */
|
||||
bool content_load_state_in_progress(void* data)
|
||||
{
|
||||
task_finder_data_t find_data;
|
||||
|
||||
find_data.func = task_load_state_finder;
|
||||
find_data.userdata = NULL;
|
||||
|
||||
return task_queue_find(&find_data);
|
||||
}
|
||||
|
||||
void content_wait_for_load_state_task(void)
|
||||
{
|
||||
task_queue_wait(content_load_state_in_progress, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* content_load_state:
|
||||
* @path : path that state will be loaded from.
|
||||
|
Loading…
Reference in New Issue
Block a user