mirror of
https://github.com/libretro/RetroArch
synced 2025-01-30 21:32:45 +00:00
c7d0bf90f6
working": (1) Fixups to the stall logic to make sure it always receives frames while stalling :) (2) Disused the used_real field. It was misconfigured and would frequently claim to be using real data when real data hadn't been used... this means more replays for now, but used_real will be readded. (TODO) (3) Stall duration is now related to sync frames, and thus configurable. (4) Delta frames were having the good ol' initialization problem, as frame==0 was indistinguishable from unused. Quickfixed by adding a "used" field, but maybe there's a better way. (5) If serialization fails, switch immediately to blocking mode (stall_frames = 0). Blocking mode barely works, but if serialization fails, no mode will work! (6) I'm not sure which bug my replaying-from-previous-frame was trying to fix, but the correct behavior is to replay from the last frame we had vital information, not the frame prior. Notionally this should just be an efficiency thing, but unsigned arithmetic at 0 made this a "just ignore all input from now on" thing.
255 lines
7.3 KiB
C
255 lines
7.3 KiB
C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
|
|
* Copyright (C) 2011-2016 - Daniel De Matteis
|
|
*
|
|
* 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 <compat/strl.h>
|
|
|
|
#include "netplay_private.h"
|
|
|
|
#include "../../autosave.h"
|
|
|
|
/**
|
|
* pre_frame:
|
|
* @netplay : pointer to netplay object
|
|
*
|
|
* Pre-frame for Netplay (normal version).
|
|
**/
|
|
static void netplay_net_pre_frame(netplay_t *netplay)
|
|
{
|
|
retro_ctx_serialize_info_t serial_info;
|
|
|
|
if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count))
|
|
{
|
|
serial_info.data_const = NULL;
|
|
serial_info.data = netplay->buffer[netplay->self_ptr].state;
|
|
serial_info.size = netplay->state_size;
|
|
|
|
if (!core_serialize(&serial_info))
|
|
{
|
|
/* If the core can't serialize properly, we must stall for the
|
|
* remote input on EVERY frame, because we can't recover */
|
|
netplay->stall_frames = 0;
|
|
}
|
|
}
|
|
|
|
netplay->can_poll = true;
|
|
|
|
input_poll_net();
|
|
}
|
|
|
|
/**
|
|
* post_frame:
|
|
* @netplay : pointer to netplay object
|
|
*
|
|
* Post-frame for Netplay (normal version).
|
|
* We check if we have new input and replay from recorded input.
|
|
**/
|
|
static void netplay_net_post_frame(netplay_t *netplay)
|
|
{
|
|
netplay->self_frame_count++;
|
|
|
|
#if 0
|
|
/* Nothing to do... */
|
|
if (netplay->other_frame_count == netplay->read_frame_count)
|
|
return;
|
|
#endif
|
|
|
|
/* Skip ahead if we predicted correctly.
|
|
* Skip until our simulation failed. */
|
|
while (netplay->other_frame_count < netplay->read_frame_count)
|
|
{
|
|
const struct delta_frame *ptr = &netplay->buffer[netplay->other_ptr];
|
|
|
|
if (memcmp(ptr->simulated_input_state, ptr->real_input_state,
|
|
sizeof(ptr->real_input_state)) != 0
|
|
&& !ptr->used_real)
|
|
break;
|
|
netplay->other_ptr = NEXT_PTR(netplay->other_ptr);
|
|
netplay->other_frame_count++;
|
|
}
|
|
|
|
/* Now replay the real input if we've gotten ahead of it */
|
|
if (netplay->other_frame_count < netplay->read_frame_count)
|
|
{
|
|
retro_ctx_serialize_info_t serial_info;
|
|
|
|
/* Replay frames. */
|
|
netplay->is_replay = true;
|
|
netplay->replay_ptr = netplay->other_ptr;
|
|
netplay->replay_frame_count = netplay->other_frame_count;
|
|
|
|
if (netplay->replay_frame_count < netplay->self_frame_count)
|
|
{
|
|
serial_info.data = NULL;
|
|
serial_info.data_const = netplay->buffer[netplay->replay_ptr].state;
|
|
serial_info.size = netplay->state_size;
|
|
|
|
core_unserialize(&serial_info);
|
|
}
|
|
|
|
while (netplay->replay_frame_count < netplay->self_frame_count)
|
|
{
|
|
serial_info.data = netplay->buffer[netplay->replay_ptr].state;
|
|
serial_info.size = netplay->state_size;
|
|
serial_info.data_const = NULL;
|
|
|
|
core_serialize(&serial_info);
|
|
|
|
#if defined(HAVE_THREADS)
|
|
autosave_lock();
|
|
#endif
|
|
core_run();
|
|
#if defined(HAVE_THREADS)
|
|
autosave_unlock();
|
|
#endif
|
|
netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr);
|
|
netplay->replay_frame_count++;
|
|
}
|
|
|
|
/* For the remainder of the frames up to the read count, we can use the real data */
|
|
while (netplay->replay_frame_count < netplay->read_frame_count)
|
|
{
|
|
/*netplay->buffer[netplay->replay_ptr].used_real = true;*/
|
|
netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr);
|
|
netplay->replay_frame_count++;
|
|
}
|
|
|
|
netplay->other_ptr = netplay->read_ptr;
|
|
netplay->other_frame_count = netplay->read_frame_count;
|
|
netplay->is_replay = false;
|
|
}
|
|
|
|
#if 0
|
|
/* And if the other side has gotten too far ahead of /us/, skip to catch up
|
|
* FIXME: Make this configurable */
|
|
if (netplay->read_frame_count > netplay->self_frame_count + 10 ||
|
|
netplay->must_fast_forward)
|
|
{
|
|
/* "replay" into the future */
|
|
netplay->is_replay = true;
|
|
netplay->replay_ptr = netplay->self_ptr;
|
|
netplay->replay_frame_count = netplay->self_frame_count;
|
|
|
|
/* just assume input doesn't change for the intervening frames */
|
|
while (netplay->replay_frame_count < netplay->read_frame_count)
|
|
{
|
|
size_t cur = netplay->replay_ptr;
|
|
size_t prev = PREV_PTR(cur);
|
|
|
|
memcpy(netplay->buffer[cur].self_state, netplay->buffer[prev].self_state,
|
|
sizeof(netplay->buffer[prev].self_state));
|
|
|
|
#if defined(HAVE_THREADS)
|
|
autosave_lock();
|
|
#endif
|
|
core_run();
|
|
#if defined(HAVE_THREADS)
|
|
autosave_unlock();
|
|
#endif
|
|
|
|
netplay->replay_ptr = NEXT_PTR(cur);
|
|
netplay->replay_frame_count++;
|
|
}
|
|
|
|
/* at this point, other = read = self */
|
|
netplay->self_ptr = netplay->replay_ptr;
|
|
netplay->self_frame_count = netplay->replay_frame_count;
|
|
netplay->other_ptr = netplay->read_ptr;
|
|
netplay->other_frame_count = netplay->read_frame_count;
|
|
netplay->is_replay = false;
|
|
}
|
|
#endif
|
|
|
|
/* If we're supposed to stall, rewind */
|
|
if (netplay->stall)
|
|
{
|
|
retro_ctx_serialize_info_t serial_info;
|
|
|
|
netplay->self_ptr = PREV_PTR(netplay->self_ptr);
|
|
netplay->self_frame_count--;
|
|
|
|
serial_info.data = NULL;
|
|
serial_info.data_const = netplay->buffer[netplay->self_ptr].state;
|
|
serial_info.size = netplay->state_size;
|
|
|
|
core_unserialize(&serial_info);
|
|
}
|
|
}
|
|
static bool netplay_net_init_buffers(netplay_t *netplay)
|
|
{
|
|
unsigned i;
|
|
retro_ctx_size_info_t info;
|
|
|
|
if (!netplay)
|
|
return false;
|
|
|
|
netplay->buffer = (struct delta_frame*)calloc(netplay->buffer_size,
|
|
sizeof(*netplay->buffer));
|
|
|
|
if (!netplay->buffer)
|
|
return false;
|
|
|
|
core_serialize_size(&info);
|
|
|
|
netplay->state_size = info.size;
|
|
|
|
for (i = 0; i < netplay->buffer_size; i++)
|
|
{
|
|
netplay->buffer[i].state = malloc(netplay->state_size);
|
|
|
|
if (!netplay->buffer[i].state)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool netplay_net_info_cb(netplay_t* netplay, unsigned frames)
|
|
{
|
|
if (netplay_is_server(netplay))
|
|
{
|
|
if (!netplay_send_info(netplay))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (!netplay_get_info(netplay))
|
|
return false;
|
|
}
|
|
|
|
/* * 2 + 1 because:
|
|
* Self sits in the middle,
|
|
* Other is allowed to drift as much as 'frames' frames behind
|
|
* Read is allowed to drift as much as 'frames' frames ahead */
|
|
netplay->buffer_size = frames * 2 + 1;
|
|
|
|
if (!netplay_net_init_buffers(netplay))
|
|
return false;
|
|
|
|
netplay->has_connection = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
struct netplay_callbacks* netplay_get_cbs_net(void)
|
|
{
|
|
static struct netplay_callbacks cbs = {
|
|
&netplay_net_pre_frame,
|
|
&netplay_net_post_frame,
|
|
&netplay_net_info_cb
|
|
};
|
|
return &cbs;
|
|
}
|