Merge pull request #3600 from GregorR/netplay-new-features

New Netplay features
This commit is contained in:
Twinaphex 2016-09-16 13:35:12 +02:00 committed by GitHub
commit e4cf93cff4
18 changed files with 862 additions and 303 deletions

View File

@ -1919,11 +1919,6 @@ bool command_event(enum event_command cmd, void *data)
if (bsv_movie_ctl(BSV_MOVIE_CTL_IS_INITED, NULL))
return false;
#ifdef HAVE_NETPLAY
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL))
return false;
#endif
#ifdef HAVE_CHEEVOS
if (settings->cheevos.hardcore_mode_enable)
return false;

View File

@ -911,7 +911,8 @@ static int populate_settings_int(settings_t *settings, struct config_int_setting
SETTING_INT("state_slot", (unsigned*)&settings->state_slot, false, 0 /* TODO */, false);
#ifdef HAVE_NETPLAY
SETTING_INT("netplay_ip_port", &global->netplay.port, false, 0 /* TODO */, false);
SETTING_INT("netplay_delay_frames", &global->netplay.sync_frames, false, 0 /* TODO */, false);
SETTING_INT("netplay_delay_frames", &global->netplay.sync_frames, false, 16, false);
SETTING_INT("netplay_check_frames", &global->netplay.check_frames, false, 0, false);
#endif
#ifdef HAVE_LANGEXTRA
SETTING_INT("user_language", &settings->user_language, true, RETRO_LANGUAGE_ENGLISH, false);
@ -1788,6 +1789,8 @@ static bool config_load_file(const char *path, bool set_defaults,
#ifdef HAVE_NETPLAY
if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES))
CONFIG_GET_INT_BASE(conf, global, netplay.sync_frames, "netplay_delay_frames");
if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES))
CONFIG_GET_INT_BASE(conf, global, netplay.sync_frames, "netplay_check_frames");
if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT))
CONFIG_GET_INT_BASE(conf, global, netplay.port, "netplay_ip_port");
#endif

View File

@ -109,20 +109,10 @@ static bool core_init_libretro_cbs(void *data)
/* Force normal poll type for netplay. */
core_poll_type = POLL_TYPE_NORMAL;
if (global->netplay.is_spectate)
{
core.retro_set_input_state(
(global->netplay.is_client ?
input_state_spectate_client : input_state_spectate)
);
}
else
{
core.retro_set_video_refresh(video_frame_net);
core.retro_set_audio_sample(audio_sample_net);
core.retro_set_audio_sample_batch(audio_sample_batch_net);
core.retro_set_input_state(input_state_net);
}
core.retro_set_video_refresh(video_frame_net);
core.retro_set_audio_sample(audio_sample_net);
core.retro_set_audio_sample_batch(audio_sample_batch_net);
core.retro_set_input_state(input_state_net);
#endif
return true;

View File

@ -1558,6 +1558,22 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len)
"Increasing this value will increase \n"
"performance, but introduce more latency.");
break;
case MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES:
snprintf(s, len,
"The frequency in frames with which netplay \n"
"will verify that the host and client are in \n"
"sync. \n"
" \n"
"With most cores, this value will have no \n"
"visible effect and can be ignored. With \n"
"nondeterminstic cores, this value determines \n"
"how often the netplay peers will be brought \n"
"into sync. With buggy cores, setting this \n"
"to any non-zero value will cause severe \n"
"performance issues. Set to zero to perform \n"
"no checks. This value is only used on the \n"
"netplay host. \n");
break;
case MENU_ENUM_LABEL_VIDEO_MAX_SWAPCHAIN_IMAGES:
snprintf(s, len,
"Maximum amount of swapchain images. This \n"
@ -2424,6 +2440,8 @@ static const char *menu_hash_to_str_us_label_enum(enum msg_hash_enums msg)
return "bluetooth_enable";
case MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES:
return "netplay_delay_frames";
case MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES:
return "netplay_check_frames";
case MENU_ENUM_LABEL_NETPLAY_MODE:
return "netplay_mode";
case MENU_ENUM_LABEL_RGUI_SHOW_START_SCREEN:
@ -3747,6 +3765,8 @@ const char *msg_hash_to_str_us(enum msg_hash_enums msg)
return "Bluetooth Enable";
case MENU_ENUM_LABEL_VALUE_NETPLAY_DELAY_FRAMES:
return "Netplay Delay Frames";
case MENU_ENUM_LABEL_VALUE_NETPLAY_CHECK_FRAMES:
return "Netplay Check Frames";
case MENU_ENUM_LABEL_VALUE_NETPLAY_MODE:
return "Netplay Client Enable";
case MENU_ENUM_LABEL_VALUE_RGUI_SHOW_START_SCREEN:

View File

@ -4684,6 +4684,10 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data)
MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES,
PARSE_ONLY_UINT, false) != -1)
count++;
if (menu_displaylist_parse_settings_enum(menu, info,
MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES,
PARSE_ONLY_UINT, false) != -1)
count++;
if (menu_displaylist_parse_settings_enum(menu, info,
MENU_ENUM_LABEL_NETPLAY_TCP_UDP_PORT,
PARSE_ONLY_UINT, false) != -1)

View File

@ -1675,6 +1675,17 @@ void general_write_handler(void *data)
}
#endif
break;
case MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES:
#ifdef HAVE_NETPLAY
{
bool val = (global->netplay.check_frames > 0);
if (val)
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES);
else
retroarch_override_setting_unset(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES);
}
#endif
default:
break;
}
@ -5814,6 +5825,21 @@ static bool setting_append_list(
settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED);
menu_settings_list_current_add_enum_idx(list, list_info, MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES);
CONFIG_UINT(
list, list_info,
&global->netplay.check_frames,
msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES),
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_CHECK_FRAMES),
0,
&group_info,
&subgroup_info,
parent_group,
general_write_handler,
general_read_handler);
menu_settings_list_current_add_range(list, list_info, 0, 10, 1, true, false);
settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED);
menu_settings_list_current_add_enum_idx(list, list_info, MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES);
CONFIG_UINT(
list, list_info,
&global->netplay.port,

View File

@ -1111,6 +1111,8 @@ enum msg_hash_enums
MENU_ENUM_LABEL_VALUE_NETPLAY_CLIENT_SWAP_INPUT,
MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES,
MENU_ENUM_LABEL_VALUE_NETPLAY_DELAY_FRAMES,
MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES,
MENU_ENUM_LABEL_VALUE_NETPLAY_CHECK_FRAMES,
MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE,
MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATOR_MODE_ENABLE,
MENU_ENUM_LABEL_NETPLAY_TCP_UDP_PORT,

View File

@ -41,15 +41,15 @@ ring, other is the first frame that it's unsafe to overwrite.
Read is where it's read up to, which can be slightly ahead of other since it
can't always immediately act upon new data.
In general, other ≤ read and other ≤ self. In all likelyhood, read ≤ self, but
In general, other ≤ read and other ≤ self. In all likelihood, read ≤ self, but
it is both possible and supported for the remote host to get ahead of the local
host.
Pre-frame, Netplay serializes the core's state, polls for local input, and
polls for input from the other side. If the input from the other side is too
far behind, it stalls to allow the other side to catch up. To assure that this
stalling does not block the UI thread, it is implemented by rewinding the
thread every frame until data is ready.
stalling does not block the UI thread, it is implemented similarly to pausing,
rather than by blocking on the socket.
If input has not been received for the other side up to the current frame (the
usual case), the remote input is simulated in a simplistic manner. Each
@ -57,12 +57,13 @@ frame's local serialized state and simulated or real input goes into the frame
buffers.
During the frame of execution, when the core requests input, it receives the
input from the thread buffer, both local and real or simulated remote.
input from the state buffer, both local and real or simulated remote.
Post-frame, it checks whether it's read more than it's actioned, i.e. if read >
other. If so, it rewinds to other (by loading the serialized state there) and
runs the core in replay mode with the real data up to read, then sets other =
read.
other self > other. If so, it first checks whether its simulated remote data
was correct. If it was, it simply moves other up. If not, it rewinds to other
(by loading the serialized state there) and runs the core in replay mode with
the real data up to the least of self and read, then sets other to that.
When in Netplay mode, the callback for receiving input is replaced by
input_state_net. It is the role of input_state_net to combine the true local
@ -79,3 +80,74 @@ which we've actually read self_frame_count+1 frames of local input.
* Guarantee not actually a guarantee.
Netplay's command format
Netplay commands consist of a 32-bit command identifier, followed by a 32-bit
payload size, both in network byte order, followed by a payload. The command
identifiers are listed in netplay.h. The commands are described below. Unless
specified otherwise, all payload values are in network byte order.
Command: ACK
Payload: None
Description:
Acknowledgement. Not used.
Command: NAK
Payload: None
Description:
Negative Acknowledgement. If received, the connection is terminated. Sent
whenever a command is malformed or otherwise not understood.
Command: INPUT
Payload:
{
frame number: uint32
joypad input: uint32
analog 1 input: uint32
analog 2 input: uint32
OPTIONAL state CRC: uint32
}
Description:
Input state for each frame. Netplay must send an INPUT command for every
frame in order to function at all.
Command: FLIP_PLAYERS
Payload:
{
frame number: uint32
}
Description:
Flip players at the requested frame.
Command: DISCONNECT
Payload: None
Description:
Gracefully disconnect. Not used.
Command: CRC
Payload:
{
frame number: uint32
hash: uint32
}
Description:
Informs the peer of the correct CRC hash for the specified frame. If the
receiver's hash doesn't match, they should send a REQUEST_SAVESTATE
command.
Command: REQUEST_SAVESTATE
Payload: None
Description:
Requests that the peer send a savestate.
Command: LOAD_SAVESTATE
Payload:
{
frame number: uint32
serialized save state: blob (variable size)
}
Description:
Cause the other side to load a savestate, notionally one which the sending
side has also loaded.

View File

@ -157,11 +157,14 @@ static bool get_self_input_state(netplay_t *netplay)
netplay->packet_buffer[4] = htonl(state[1]);
netplay->packet_buffer[5] = htonl(state[2]);
if (!socket_send_all_blocking(netplay->fd, netplay->packet_buffer, sizeof(netplay->packet_buffer), false))
if (!netplay->spectate.enabled) /* Spectate sends in its own way */
{
warn_hangup();
netplay->has_connection = false;
return false;
if (!socket_send_all_blocking(netplay->fd, netplay->packet_buffer, sizeof(netplay->packet_buffer), false))
{
warn_hangup();
netplay->has_connection = false;
return false;
}
}
memcpy(ptr->self_state, state, sizeof(state));
@ -192,6 +195,22 @@ static bool netplay_cmd_nak(netplay_t *netplay)
return netplay_send_raw_cmd(netplay, NETPLAY_CMD_NAK, NULL, 0);
}
bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta)
{
uint32_t payload[2];
payload[0] = htonl(delta->frame);
payload[1] = htonl(delta->crc);
return netplay_send_raw_cmd(netplay, NETPLAY_CMD_CRC, payload, sizeof(payload));
}
bool netplay_cmd_request_savestate(netplay_t *netplay)
{
if (netplay->savestate_request_outstanding)
return true;
netplay->savestate_request_outstanding = true;
return netplay_send_raw_cmd(netplay, NETPLAY_CMD_REQUEST_SAVESTATE, NULL, 0);
}
static bool netplay_get_cmd(netplay_t *netplay)
{
uint32_t cmd;
@ -242,7 +261,12 @@ static bool netplay_get_cmd(netplay_t *netplay)
for (i = 0; i < WORDS_PER_FRAME; i++)
buffer[i] = ntohl(buffer[i]);
if (buffer[0] != netplay->read_frame_count)
if (buffer[0] < netplay->read_frame_count)
{
/* We already had this, so ignore the new transmission */
return true;
}
else if (buffer[0] > netplay->read_frame_count)
{
/* Out of order = out of luck */
return netplay_cmd_nak(netplay);
@ -272,7 +296,7 @@ static bool netplay_get_cmd(netplay_t *netplay)
flip_frame = ntohl(flip_frame);
if (flip_frame < netplay->flip_frame)
if (flip_frame < netplay->read_frame_count)
{
RARCH_ERR("Host asked us to flip users in the past. Not possible ...\n");
return netplay_cmd_nak(netplay);
@ -281,6 +305,12 @@ static bool netplay_get_cmd(netplay_t *netplay)
netplay->flip ^= true;
netplay->flip_frame = flip_frame;
/* Force a rewind to assure the flip happens: This just prevents us
* from skipping other past the flip because our prediction was
* correct */
if (flip_frame < netplay->self_frame_count)
netplay->force_rewind = true;
RARCH_LOG("Netplay users are flipped.\n");
runloop_msg_queue_push("Netplay users are flipped.", 1, 180, false);
@ -294,16 +324,138 @@ static bool netplay_get_cmd(netplay_t *netplay)
warn_hangup();
return true;
case NETPLAY_CMD_CRC:
{
uint32_t buffer[2];
size_t tmp_ptr = netplay->self_ptr;
bool found = false;
if (cmd_size != sizeof(buffer))
{
RARCH_ERR("NETPLAY_CMD_CRC received unexpected payload size.\n");
return netplay_cmd_nak(netplay);
}
if (!socket_receive_all_blocking(netplay->fd, buffer, sizeof(buffer)))
{
RARCH_ERR("NETPLAY_CMD_CRC failed to receive payload.\n");
return netplay_cmd_nak(netplay);
}
buffer[0] = ntohl(buffer[0]);
buffer[1] = ntohl(buffer[1]);
/* Received a CRC for some frame. If we still have it, check if it
* matched. This approach could be improved with some quick modular
* arithmetic. */
do {
if (netplay->buffer[tmp_ptr].frame == buffer[0])
{
found = true;
break;
}
tmp_ptr = PREV_PTR(tmp_ptr);
} while (tmp_ptr != netplay->self_ptr);
if (!found)
{
/* Oh well, we got rid of it! */
return true;
}
if (buffer[0] <= netplay->other_frame_count)
{
/* We've already replayed up to this frame, so we can check it
* directly */
uint32_t local_crc = netplay_delta_frame_crc(netplay, &netplay->buffer[tmp_ptr]);
if (buffer[1] != local_crc)
{
/* Problem! */
netplay_cmd_request_savestate(netplay);
}
}
else
{
/* We'll have to check it when we catch up */
netplay->buffer[tmp_ptr].crc = buffer[1];
}
return true;
}
case NETPLAY_CMD_REQUEST_SAVESTATE:
/* Delay until next frame so we don't send the savestate after the
* input */
netplay->force_send_savestate = true;
return true;
case NETPLAY_CMD_LOAD_SAVESTATE:
RARCH_ERR("NETPLAY_CMD_LOAD_SAVESTATE unimplemented.\n");
return netplay_cmd_nak(netplay);
{
uint32_t frame;
/* There is a subtlty in whether the load comes before or after the
* current frame:
*
* If it comes before the current frame, then we need to force a
* rewind to that point.
*
* If it comes after the current frame, we need to jump ahead, then
* (strangely) force a rewind to the frame we're already on, so it
* gets loaded. This is just to avoid having reloading implemented in
* too many places. */
if (cmd_size > netplay->state_size + sizeof(uint32_t))
{
RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected save state size.\n");
return netplay_cmd_nak(netplay);
}
if (!socket_receive_all_blocking(netplay->fd, &frame, sizeof(frame)))
{
RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate frame.\n");
return netplay_cmd_nak(netplay);
}
frame = ntohl(frame);
if (frame != netplay->read_frame_count)
{
RARCH_ERR("CMD_LOAD_SAVESTATE loading a state out of order!\n");
return netplay_cmd_nak(netplay);
}
if (!socket_receive_all_blocking(netplay->fd,
netplay->buffer[netplay->read_ptr].state, cmd_size - sizeof(uint32_t)))
{
RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate.\n");
return netplay_cmd_nak(netplay);
}
/* Skip ahead if it's past where we are */
if (frame > netplay->self_frame_count)
{
/* This is squirrely: We need to assure that when we advance the
* frame in post_frame, THEN we're referring to the frame to
* load into. If we refer directly to read_ptr, then we'll end
* up never reading the input for read_frame_count itself, which
* will make the other side unhappy. */
netplay->self_ptr = PREV_PTR(netplay->read_ptr);
netplay->self_frame_count = frame - 1;
}
/* And force rewind to it */
netplay->force_rewind = true;
netplay->savestate_request_outstanding = false;
netplay->other_ptr = netplay->read_ptr;
netplay->other_frame_count = frame;
return true;
}
case NETPLAY_CMD_PAUSE:
command_event(CMD_EVENT_PAUSE, NULL);
netplay->remote_paused = true;
return true;
case NETPLAY_CMD_RESUME:
command_event(CMD_EVENT_UNPAUSE, NULL);
netplay->remote_paused = false;
return true;
default: break;
@ -360,7 +512,7 @@ static int poll_input(netplay_t *netplay, bool block)
RARCH_LOG("Network is stalling at frame %u, count %u of %d ...\n",
netplay->self_frame_count, netplay->timeout_cnt, MAX_RETRIES);
if (netplay->timeout_cnt >= MAX_RETRIES)
if (netplay->timeout_cnt >= MAX_RETRIES && !netplay->remote_paused)
return -1;
} while (had_input || (block && (netplay->read_frame_count <= netplay->self_frame_count)));
@ -401,6 +553,10 @@ static bool netplay_poll(netplay_t *netplay)
get_self_input_state(netplay);
/* No network side in spectate mode */
if (netplay_is_server(netplay) && netplay->spectate.enabled)
return true;
/* Read Netplay input, block if we're configured to stall for input every
* frame */
res = poll_input(netplay, (netplay->stall_frames == 0) && (netplay->read_frame_count <= netplay->self_frame_count));
@ -434,7 +590,12 @@ static bool netplay_poll(netplay_t *netplay)
if (netplay->stall)
{
retro_time_t now = cpu_features_get_time_usec();
if (now - netplay->stall_time >= MAX_STALL_TIME_USEC)
if (netplay->remote_paused)
{
/* Don't stall out while they're paused */
netplay->stall_time = now;
}
else if (now - netplay->stall_time >= MAX_STALL_TIME_USEC)
{
/* Stalled out! */
netplay->has_connection = false;
@ -745,11 +906,47 @@ static bool init_socket(netplay_t *netplay, const char *server, uint16_t port)
return true;
}
static bool netplay_init_buffers(netplay_t *netplay, unsigned frames)
{
unsigned i;
retro_ctx_size_info_t info;
if (!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;
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 = calloc(netplay->state_size, 1);
if (!netplay->buffer[i].state)
return false;
}
return true;
}
/**
* netplay_new:
* @server : IP address of server.
* @port : Port of server.
* @frames : Amount of lag frames.
* @check_frames : Frequency with which to check CRCs.
* @cb : Libretro callbacks.
* @spectate : If true, enable spectator mode.
* @nick : Nickname of user.
@ -760,9 +957,8 @@ static bool init_socket(netplay_t *netplay, const char *server, uint16_t port)
* Returns: new netplay handle.
**/
netplay_t *netplay_new(const char *server, uint16_t port,
unsigned frames, const struct retro_callbacks *cb,
bool spectate,
const char *nick)
unsigned frames, unsigned check_frames, const struct retro_callbacks *cb,
bool spectate, const char *nick)
{
netplay_t *netplay = (netplay_t*)calloc(1, sizeof(*netplay));
if (!netplay)
@ -775,6 +971,13 @@ netplay_t *netplay_new(const char *server, uint16_t port,
netplay->is_server = server == NULL;
strlcpy(netplay->nick, nick, sizeof(netplay->nick));
netplay->stall_frames = frames;
netplay->check_frames = check_frames;
if (!netplay_init_buffers(netplay, frames))
{
free(netplay);
return NULL;
}
if(spectate)
netplay->net_cbs = netplay_get_cbs_spectate();
@ -859,7 +1062,8 @@ error:
**/
static void netplay_flip_users(netplay_t *netplay)
{
uint32_t flip_frame = netplay->self_frame_count + 32; /* FIXME: This value is now arbitrary */
/* Must be in the future because we may have already sent this frame's data */
uint32_t flip_frame = netplay->self_frame_count + 1;
uint32_t flip_frame_net = htonl(flip_frame);
bool command = netplay_command(
netplay, NETPLAY_CMD_FLIP_PLAYERS,
@ -908,67 +1112,27 @@ void netplay_free(netplay_t *netplay)
free(netplay);
}
static void netplay_set_spectate_input(netplay_t *netplay, int16_t input)
{
if (netplay->spectate.input_ptr >= netplay->spectate.input_sz)
{
netplay->spectate.input_sz++;
netplay->spectate.input_sz *= 2;
netplay->spectate.input = (uint16_t*)realloc(netplay->spectate.input,
netplay->spectate.input_sz * sizeof(uint16_t));
}
netplay->spectate.input[netplay->spectate.input_ptr++] = swap_if_big16(input);
}
int16_t input_state_spectate(unsigned port, unsigned device,
unsigned idx, unsigned id)
{
netplay_t *netplay = (netplay_t*)netplay_data;
int16_t res = netplay->cbs.state_cb(port, device, idx, id);
netplay_set_spectate_input(netplay, res);
return res;
}
static int16_t netplay_get_spectate_input(netplay_t *netplay, bool port,
unsigned device, unsigned idx, unsigned id)
{
int16_t inp;
retro_ctx_input_state_info_t input_info;
if (socket_receive_all_blocking(netplay->fd, (char*)&inp, sizeof(inp)))
return swap_if_big16(inp);
RARCH_ERR("Connection with host was cut.\n");
runloop_msg_queue_push("Connection with host was cut.", 1, 180, true);
input_info.cb = netplay->cbs.state_cb;
core_set_input_state(&input_info);
return netplay->cbs.state_cb(port, device, idx, id);
}
int16_t input_state_spectate_client(unsigned port, unsigned device,
unsigned idx, unsigned id)
{
return netplay_get_spectate_input((netplay_t*)netplay_data, port,
device, idx, id);
}
/**
* netplay_pre_frame:
* @netplay : pointer to netplay object
*
* Pre-frame for Netplay.
* Call this before running retro_run().
*
* Returns: true (1) if the frontend is cleared to emulate the frame, false (0)
* if we're stalled or paused
**/
void netplay_pre_frame(netplay_t *netplay)
bool netplay_pre_frame(netplay_t *netplay)
{
retro_assert(netplay && netplay->net_cbs->pre_frame);
netplay->net_cbs->pre_frame(netplay);
if (netplay->local_paused)
{
/* FIXME: This is an ugly way to learn we're not paused anymore */
netplay_frontend_paused(netplay, false);
}
if (!netplay->net_cbs->pre_frame(netplay))
return false;
return (!netplay->has_connection || (!netplay->stall && !netplay->remote_paused));
}
/**
@ -985,6 +1149,80 @@ void netplay_post_frame(netplay_t *netplay)
netplay->net_cbs->post_frame(netplay);
}
/**
* netplay_frontend_paused
* @netplay : pointer to netplay object
* @paused : true if frontend is paused
*
* Inform Netplay of the frontend's pause state (paused or otherwise)
**/
void netplay_frontend_paused(netplay_t *netplay, bool paused)
{
/* Nothing to do if we already knew this */
if (netplay->local_paused == paused)
return;
netplay->local_paused = paused;
if (netplay->has_connection && !netplay->spectate.enabled)
netplay_send_raw_cmd(netplay, paused ? NETPLAY_CMD_PAUSE : NETPLAY_CMD_RESUME, NULL, 0);
}
/**
* netplay_load_savestate
* @netplay : pointer to netplay object
* @serial_info : the savestate being loaded
* @save : whether to save the provided serial_info into the frame buffer
*
* Inform Netplay of a savestate load and send it to the other side
**/
void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info, bool save)
{
uint32_t header[3];
if (!netplay->has_connection)
return;
/* Record it in our own buffer */
if (save && netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count))
{
if (serial_info->size <= netplay->state_size)
{
memcpy(netplay->buffer[netplay->self_ptr].state,
serial_info->data_const, serial_info->size);
}
}
/* We need to ignore any intervening data from the other side, and never rewind past this */
if (netplay->read_frame_count < netplay->self_frame_count)
{
netplay->read_ptr = netplay->self_ptr;
netplay->read_frame_count = netplay->self_frame_count;
}
if (netplay->other_frame_count < netplay->self_frame_count)
{
netplay->other_ptr = netplay->self_ptr;
netplay->other_frame_count = netplay->self_frame_count;
}
/* And send it to the peer (FIXME: this is an ugly way to do this) */
header[0] = htonl(NETPLAY_CMD_LOAD_SAVESTATE);
header[1] = htonl(serial_info->size + sizeof(uint32_t));
header[2] = htonl(netplay->self_frame_count);
if (!socket_send_all_blocking(netplay->fd, header, sizeof(header), false))
{
warn_hangup();
netplay->has_connection = false;
return;
}
if (!socket_send_all_blocking(netplay->fd, serial_info->data_const, serial_info->size, false))
{
warn_hangup();
netplay->has_connection = false;
return;
}
}
void deinit_netplay(void)
{
netplay_t *netplay = (netplay_t*)netplay_data;
@ -1032,8 +1270,8 @@ bool init_netplay(void)
netplay_data = (netplay_t*)netplay_new(
global->netplay.is_client ? global->netplay.server : NULL,
global->netplay.port ? global->netplay.port : RARCH_DEFAULT_PORT,
global->netplay.sync_frames, &cbs, global->netplay.is_spectate,
settings->username);
global->netplay.sync_frames, global->netplay.check_frames, &cbs,
global->netplay.is_spectate, settings->username);
if (netplay_data)
return true;
@ -1050,7 +1288,12 @@ bool init_netplay(void)
bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data)
{
if (!netplay_data)
return false;
{
if (state == RARCH_NETPLAY_CTL_IS_DATA_INITED)
return false;
else
return true;
}
switch (state)
{
@ -1060,8 +1303,7 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data)
netplay_post_frame((netplay_t*)netplay_data);
break;
case RARCH_NETPLAY_CTL_PRE_FRAME:
netplay_pre_frame((netplay_t*)netplay_data);
break;
return netplay_pre_frame((netplay_t*)netplay_data);
case RARCH_NETPLAY_CTL_FLIP_PLAYERS:
{
bool *state = (bool*)data;
@ -1076,6 +1318,15 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data)
command_event(CMD_EVENT_FULLSCREEN_TOGGLE, NULL);
}
break;
case RARCH_NETPLAY_CTL_PAUSE:
netplay_frontend_paused((netplay_t*)netplay_data, true);
break;
case RARCH_NETPLAY_CTL_UNPAUSE:
netplay_frontend_paused((netplay_t*)netplay_data, false);
break;
case RARCH_NETPLAY_CTL_LOAD_SAVESTATE:
netplay_load_savestate((netplay_t*)netplay_data, (retro_ctx_serialize_info_t*)data, true);
break;
default:
case RARCH_NETPLAY_CTL_NONE:
break;

View File

@ -35,7 +35,10 @@ enum rarch_netplay_ctl_state
RARCH_NETPLAY_CTL_FULLSCREEN_TOGGLE,
RARCH_NETPLAY_CTL_POST_FRAME,
RARCH_NETPLAY_CTL_PRE_FRAME,
RARCH_NETPLAY_CTL_IS_DATA_INITED
RARCH_NETPLAY_CTL_IS_DATA_INITED,
RARCH_NETPLAY_CTL_PAUSE,
RARCH_NETPLAY_CTL_UNPAUSE,
RARCH_NETPLAY_CTL_LOAD_SAVESTATE
};
enum netplay_cmd
@ -73,8 +76,15 @@ enum netplay_cmd
/* Loading and synchronization */
/* Send the CRC hash of a frame's state */
NETPLAY_CMD_CRC = 0x0010,
/* Request a savestate */
NETPLAY_CMD_REQUEST_SAVESTATE = 0x0011,
/* Send a savestate for the client to load */
NETPLAY_CMD_LOAD_SAVESTATE = 0x0012,
/* Sends over cheats enabled on client */
NETPLAY_CMD_CHEATS = 0x0013,
@ -115,17 +125,12 @@ void audio_sample_net(int16_t left, int16_t right);
size_t audio_sample_batch_net(const int16_t *data, size_t frames);
int16_t input_state_spectate(unsigned port, unsigned device,
unsigned idx, unsigned id);
int16_t input_state_spectate_client(unsigned port, unsigned device,
unsigned idx, unsigned id);
/**
* netplay_new:
* @server : IP address of server.
* @port : Port of server.
* @frames : Amount of lag frames.
* @check_frames : Frequency with which to check CRCs.
* @cb : Libretro callbacks.
* @spectate : If true, enable spectator mode.
* @nick : Nickname of user.
@ -136,7 +141,7 @@ int16_t input_state_spectate_client(unsigned port, unsigned device,
* Returns: new netplay handle.
**/
netplay_t *netplay_new(const char *server,
uint16_t port, unsigned frames,
uint16_t port, unsigned frames, unsigned check_frames,
const struct retro_callbacks *cb, bool spectate,
const char *nick);
@ -154,8 +159,11 @@ void netplay_free(netplay_t *handle);
*
* Pre-frame for Netplay.
* Call this before running retro_run().
*
* Returns: true (1) if the frontend is clear to emulate the frame, false (0)
* if we're stalled or paused
**/
void netplay_pre_frame(netplay_t *handle);
bool netplay_pre_frame(netplay_t *handle);
/**
* netplay_post_frame:
@ -167,6 +175,25 @@ void netplay_pre_frame(netplay_t *handle);
**/
void netplay_post_frame(netplay_t *handle);
/**
* netplay_frontend_paused
* @netplay : pointer to netplay object
* @paused : true if frontend is paused
*
* Inform Netplay of the frontend's pause state (paused or otherwise)
**/
void netplay_frontend_paused(netplay_t *netplay, bool paused);
/**
* netplay_load_savestate
* @netplay : pointer to netplay object
* @serial_info : the savestate being loaded
* @save : whether to save the provided serial_info into the frame buffer
*
* Inform Netplay of a savestate load and send it to the other side
**/
void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info, bool save);
/**
* init_netplay:
*

View File

@ -20,6 +20,8 @@
#include "netplay_private.h"
#include <net/net_socket.h>
#include "compat/zlib.h"
#include "../../movie.h"
#include "../../msg_hash.h"
#include "../../content.h"
@ -365,3 +367,10 @@ bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, ui
delta->state = remember_state;
return true;
}
uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta)
{
if (!netplay->state_size)
return 0;
return crc32(0L, delta->state, netplay->state_size);
}

View File

@ -24,13 +24,35 @@
#include "../../autosave.h"
static void netplay_handle_frame_hash(netplay_t *netplay, struct delta_frame *delta)
{
if (netplay_is_server(netplay))
{
if (netplay->check_frames && delta->frame % netplay->check_frames == 0)
{
delta->crc = netplay_delta_frame_crc(netplay, delta);
netplay_cmd_crc(netplay, delta);
}
}
else if (delta->crc)
{
/* We have a remote CRC, so check it */
uint32_t local_crc = netplay_delta_frame_crc(netplay, delta);
if (local_crc != delta->crc)
{
/* Fix this! */
netplay_cmd_request_savestate(netplay);
}
}
}
/**
* pre_frame:
* netplay_net_pre_frame:
* @netplay : pointer to netplay object
*
* Pre-frame for Netplay (normal version).
**/
static void netplay_net_pre_frame(netplay_t *netplay)
static bool netplay_net_pre_frame(netplay_t *netplay)
{
retro_ctx_serialize_info_t serial_info;
@ -40,7 +62,17 @@ static void netplay_net_pre_frame(netplay_t *netplay)
serial_info.data = netplay->buffer[netplay->self_ptr].state;
serial_info.size = netplay->state_size;
if (!core_serialize(&serial_info))
if (core_serialize(&serial_info))
{
if (netplay->force_send_savestate)
{
/* Send this along to the other side */
serial_info.data_const = netplay->buffer[netplay->self_ptr].state;
netplay_load_savestate(netplay, &serial_info, false);
netplay->force_send_savestate = false;
}
}
else
{
/* If the core can't serialize properly, we must stall for the
* remote input on EVERY frame, because we can't recover */
@ -51,10 +83,12 @@ static void netplay_net_pre_frame(netplay_t *netplay)
netplay->can_poll = true;
input_poll_net();
return true;
}
/**
* post_frame:
* netplay_net_post_frame:
* @netplay : pointer to netplay object
*
* Post-frame for Netplay (normal version).
@ -69,24 +103,29 @@ static void netplay_net_post_frame(netplay_t *netplay)
if (!netplay->has_connection)
return;
/* Skip ahead if we predicted correctly.
* Skip until our simulation failed. */
while (netplay->other_frame_count < netplay->read_frame_count &&
netplay->other_frame_count < netplay->self_frame_count)
if (!netplay->force_rewind)
{
const struct delta_frame *ptr = &netplay->buffer[netplay->other_ptr];
/* Skip ahead if we predicted correctly.
* Skip until our simulation failed. */
while (netplay->other_frame_count < netplay->read_frame_count &&
netplay->other_frame_count < netplay->self_frame_count)
{
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++;
if (memcmp(ptr->simulated_input_state, ptr->real_input_state,
sizeof(ptr->real_input_state)) != 0
&& !ptr->used_real)
break;
netplay_handle_frame_hash(netplay, ptr);
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 &&
netplay->other_frame_count < netplay->self_frame_count)
if (netplay->force_rewind ||
(netplay->other_frame_count < netplay->read_frame_count &&
netplay->other_frame_count < netplay->self_frame_count))
{
retro_ctx_serialize_info_t serial_info;
@ -95,23 +134,23 @@ static void netplay_net_post_frame(netplay_t *netplay)
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;
serial_info.data = NULL;
serial_info.data_const = netplay->buffer[netplay->replay_ptr].state;
serial_info.size = netplay->state_size;
core_unserialize(&serial_info);
}
core_unserialize(&serial_info);
while (netplay->replay_frame_count < netplay->self_frame_count)
{
serial_info.data = netplay->buffer[netplay->replay_ptr].state;
struct delta_frame *ptr = &netplay->buffer[netplay->replay_ptr];
serial_info.data = ptr->state;
serial_info.size = netplay->state_size;
serial_info.data_const = NULL;
core_serialize(&serial_info);
netplay_handle_frame_hash(netplay, ptr);
#if defined(HAVE_THREADS)
autosave_lock();
#endif
@ -134,9 +173,11 @@ static void netplay_net_post_frame(netplay_t *netplay)
netplay->other_frame_count = netplay->self_frame_count;
}
netplay->is_replay = false;
netplay->force_rewind = false;
}
/* If we're supposed to stall, rewind */
/* If we're supposed to stall, rewind (we shouldn't get this far if we're
* stalled, so this is a last resort) */
if (netplay->stall)
{
retro_ctx_serialize_info_t serial_info;
@ -151,34 +192,6 @@ static void netplay_net_post_frame(netplay_t *netplay)
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)
{
@ -193,15 +206,6 @@ static bool netplay_net_info_cb(netplay_t* netplay, unsigned frames)
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;

View File

@ -45,8 +45,12 @@ struct delta_frame
bool used; /* a bit derpy, but this is how we know if the delta's been used at all */
uint32_t frame;
/* The serialized state of the core at this frame, before input */
void *state;
/* The CRC-32 of the serialized state if we've calculated it, else 0 */
uint32_t crc;
uint32_t real_input_state[WORDS_PER_FRAME - 1];
uint32_t simulated_input_state[WORDS_PER_FRAME - 1];
uint32_t self_state[WORDS_PER_FRAME - 1];
@ -62,7 +66,7 @@ struct delta_frame
};
struct netplay_callbacks {
void (*pre_frame) (netplay_t *netplay);
bool (*pre_frame) (netplay_t *netplay);
void (*post_frame)(netplay_t *netplay);
bool (*info_cb) (netplay_t *netplay, unsigned frames);
};
@ -103,10 +107,20 @@ struct netplay
/* Are we replaying old frames? */
bool is_replay;
/* We don't want to poll several times on a frame. */
bool can_poll;
/* If we end up having to drop remote frame data because it's ahead of us, fast-forward is URGENT */
bool must_fast_forward;
/* Force a rewind to other_frame_count/other_ptr. This is for synchronized
* events, such as player flipping or savestate loading. */
bool force_rewind;
/* Force our state to be sent to the other side. Used when they request a
* savestate, to send at the next pre-frame. */
bool force_send_savestate;
/* Have we requested a savestate as a sync point? */
bool savestate_request_outstanding;
/* A buffer for outgoing input packets. */
uint32_t packet_buffer[2 + WORDS_PER_FRAME];
@ -124,29 +138,32 @@ struct netplay
struct {
bool enabled;
int fds[MAX_SPECTATORS];
uint32_t frames[MAX_SPECTATORS];
uint16_t *input;
size_t input_ptr;
size_t input_sz;
} spectate;
bool is_server;
/* User flipping
* Flipping state. If ptr >= flip_frame, we apply the flip.
* If not, we apply the opposite, effectively creating a trigger point.
* To avoid collition we need to make sure our client/host is synced up
* well after flip_frame before allowing another flip. */
* Flipping state. If frame >= flip_frame, we apply the flip.
* If not, we apply the opposite, effectively creating a trigger point. */
bool flip;
uint32_t flip_frame;
/* Netplay pausing
*/
bool pause;
uint32_t pause_frame;
bool local_paused;
bool remote_paused;
/* And stalling */
uint32_t stall_frames;
int stall;
retro_time_t stall_time;
/* Frequency with which to check CRCs */
uint32_t check_frames;
struct netplay_callbacks* net_cbs;
};
@ -181,4 +198,10 @@ bool netplay_is_spectate(netplay_t* netplay);
bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame);
uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta);
bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta);
bool netplay_cmd_request_savestate(netplay_t *netplay);
#endif

View File

@ -1,6 +1,7 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2016 - Daniel De Matteis
* Copyright (C) 2016 - Gregor Richards
*
* 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-
@ -14,10 +15,8 @@
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <compat/strl.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <net/net_compat.h>
#include <net/net_socket.h>
@ -25,157 +24,266 @@
#include "netplay_private.h"
#include "../../runloop.h"
#include "retro_assert.h"
#include "../../autosave.h"
/**
* netplay_pre_frame_spectate:
* netplay_spectate_pre_frame:
* @netplay : pointer to netplay object
*
* Pre-frame for Netplay (spectate mode version).
* Pre-frame for Netplay (spectator version).
**/
static void netplay_spectate_pre_frame(netplay_t *netplay)
static bool netplay_spectate_pre_frame(netplay_t *netplay)
{
unsigned i;
uint32_t *header;
int new_fd, idx, bufsize;
size_t header_size;
struct sockaddr_storage their_addr;
socklen_t addr_size;
fd_set fds;
struct timeval tmp_tv = {0};
if (!netplay_is_server(netplay))
return;
FD_ZERO(&fds);
FD_SET(netplay->fd, &fds);
if (socket_select(netplay->fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0)
return;
if (!FD_ISSET(netplay->fd, &fds))
return;
addr_size = sizeof(their_addr);
new_fd = accept(netplay->fd, (struct sockaddr*)&their_addr, &addr_size);
if (new_fd < 0)
if (netplay_is_server(netplay))
{
RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_ACCEPT_INCOMING_SPECTATOR));
return;
}
fd_set fds;
struct timeval tmp_tv = {0};
int new_fd, idx, i;
struct sockaddr_storage their_addr;
socklen_t addr_size;
retro_ctx_serialize_info_t serial_info;
uint32_t header[3];
idx = -1;
for (i = 0; i < MAX_SPECTATORS; i++)
{
if (netplay->spectate.fds[i] == -1)
netplay->can_poll = true;
input_poll_net();
/* Send our input to any connected spectators */
for (i = 0; i < MAX_SPECTATORS; i++)
{
idx = i;
break;
if (netplay->spectate.fds[i] >= 0)
{
netplay->packet_buffer[2] = htonl(netplay->self_frame_count - netplay->spectate.frames[i]);
if (!socket_send_all_blocking(netplay->spectate.fds[i], netplay->packet_buffer, sizeof(netplay->packet_buffer), false))
{
socket_close(netplay->spectate.fds[i]);
netplay->spectate.fds[i] = -1;
}
}
}
}
/* No vacant client streams :( */
if (idx == -1)
/* Check for connections */
FD_ZERO(&fds);
FD_SET(netplay->fd, &fds);
if (socket_select(netplay->fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0)
return true;
if (!FD_ISSET(netplay->fd, &fds))
return true;
addr_size = sizeof(their_addr);
new_fd = accept(netplay->fd, (struct sockaddr*)&their_addr, &addr_size);
if (new_fd < 0)
{
RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_ACCEPT_INCOMING_SPECTATOR));
return true;
}
idx = -1;
for (i = 0; i < MAX_SPECTATORS; i++)
{
if (netplay->spectate.fds[i] == -1)
{
idx = i;
break;
}
}
/* No vacant client streams :( */
if (idx == -1)
{
socket_close(new_fd);
return true;
}
if (!netplay_get_nickname(netplay, new_fd))
{
RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT));
socket_close(new_fd);
return true;
}
if (!netplay_send_nickname(netplay, new_fd))
{
RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_TO_CLIENT));
socket_close(new_fd);
return true;
}
/* Start them at the current frame */
netplay->spectate.frames[idx] = 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))
{
/* Send them the savestate */
header[0] = htonl(NETPLAY_CMD_LOAD_SAVESTATE);
header[1] = htonl(serial_info.size + sizeof(uint32_t));
header[2] = htonl(0);
if (!socket_send_all_blocking(new_fd, header, sizeof(header), false))
{
socket_close(new_fd);
return true;
}
if (!socket_send_all_blocking(new_fd, serial_info.data, serial_info.size, false))
{
socket_close(new_fd);
return true;
}
}
/* And send them this frame's input */
netplay->packet_buffer[2] = htonl(0);
if (!socket_send_all_blocking(new_fd, netplay->packet_buffer, sizeof(netplay->packet_buffer), false))
{
socket_close(new_fd);
return true;
}
netplay->spectate.fds[idx] = new_fd;
}
else
{
socket_close(new_fd);
return;
if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count))
{
/* Mark our own data as already read, so we ignore local input */
netplay->buffer[netplay->self_ptr].have_local = true;
}
netplay->can_poll = true;
input_poll_net();
/* Only proceed if we have data */
if (netplay->read_frame_count <= netplay->self_frame_count)
return false;
}
if (!netplay_get_nickname(netplay, new_fd))
{
RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT));
socket_close(new_fd);
return;
}
if (!netplay_send_nickname(netplay, new_fd))
{
RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_TO_CLIENT));
socket_close(new_fd);
return;
}
header = netplay_bsv_header_generate(&header_size,
netplay_impl_magic());
if (!header)
{
RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_GENERATE_BSV_HEADER));
socket_close(new_fd);
return;
}
bufsize = header_size;
setsockopt(new_fd, SOL_SOCKET, SO_SNDBUF, (const char*)&bufsize,
sizeof(int));
if (!socket_send_all_blocking(new_fd, header, header_size, false))
{
RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_SEND_HEADER_TO_CLIENT));
socket_close(new_fd);
free(header);
return;
}
free(header);
netplay->spectate.fds[idx] = new_fd;
#ifndef HAVE_SOCKET_LEGACY
netplay_log_connection(&their_addr, idx, netplay->other_nick);
#endif
return true;
}
/**
* netplay_post_frame_spectate:
* netplay_spectate_post_frame:
* @netplay : pointer to netplay object
*
* Post-frame for Netplay (spectate mode version).
* We check if we have new input and replay from recorded input.
* Post-frame for Netplay (spectator version).
* Not much here, just fast forward if we're behind the server.
**/
static void netplay_spectate_post_frame(netplay_t *netplay)
{
unsigned i;
netplay->self_ptr = NEXT_PTR(netplay->self_ptr);
netplay->self_frame_count++;
if (!netplay_is_server(netplay))
return;
for (i = 0; i < MAX_SPECTATORS; i++)
if (netplay_is_server(netplay))
{
char msg[128];
/* Not expecting any client data */
netplay->read_ptr = netplay->other_ptr = netplay->self_ptr;
netplay->read_frame_count = netplay->other_frame_count = netplay->self_frame_count;
if (netplay->spectate.fds[i] == -1)
continue;
if (socket_send_all_blocking(netplay->spectate.fds[i],
netplay->spectate.input,
netplay->spectate.input_ptr * sizeof(int16_t),
false))
continue;
RARCH_LOG("Client (#%u) disconnected ...\n", i);
snprintf(msg, sizeof(msg), "Client (#%u) disconnected.", i);
runloop_msg_queue_push(msg, 1, 180, false);
socket_close(netplay->spectate.fds[i]);
netplay->spectate.fds[i] = -1;
break;
}
else
{
/* If we must rewind, it's because we got a save state */
if (netplay->force_rewind)
{
retro_ctx_serialize_info_t serial_info;
netplay->spectate.input_ptr = 0;
/* Replay frames. */
netplay->is_replay = true;
netplay->replay_ptr = netplay->other_ptr;
netplay->replay_frame_count = netplay->other_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)
{
#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++;
}
netplay->is_replay = false;
netplay->force_rewind = false;
}
/* We're in sync by definition */
if (netplay->read_frame_count < netplay->self_frame_count)
{
netplay->other_ptr = netplay->read_ptr;
netplay->other_frame_count = netplay->read_frame_count;
}
else
{
netplay->other_ptr = netplay->self_ptr;
netplay->other_frame_count = netplay->self_frame_count;
}
/* If the server gets significantly ahead, skip to catch up */
if (netplay->self_frame_count + netplay->stall_frames <= netplay->read_frame_count)
{
/* "Replay" into the future */
netplay->is_replay = true;
netplay->replay_ptr = netplay->self_ptr;
netplay->replay_frame_count = netplay->self_frame_count;
while (netplay->replay_frame_count < netplay->read_frame_count - 1)
{
#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++;
netplay->self_ptr = netplay->replay_ptr;
netplay->self_frame_count = netplay->replay_frame_count;
}
netplay->is_replay = false;
}
}
}
static bool netplay_spectate_info_cb(netplay_t *netplay, unsigned frames)
static bool netplay_spectate_info_cb(netplay_t* netplay, unsigned frames)
{
unsigned i;
if(netplay_is_server(netplay))
if (netplay_is_server(netplay))
{
if(!netplay_get_info(netplay))
int i;
for (i = 0; i < MAX_SPECTATORS; i++)
{
netplay->spectate.fds[i] = -1;
}
}
else
{
if (!netplay_send_nickname(netplay, netplay->fd))
return false;
if (!netplay_get_nickname(netplay, netplay->fd))
return false;
}
for (i = 0; i < MAX_SPECTATORS; i++)
netplay->spectate.fds[i] = -1;
netplay->has_connection = true;
return true;
}
@ -186,6 +294,5 @@ struct netplay_callbacks* netplay_get_cbs_spectate(void)
&netplay_spectate_post_frame,
&netplay_spectate_info_cb
};
return &cbs;
}

View File

@ -90,6 +90,7 @@
enum
{
RA_OPT_MENU = 256, /* must be outside the range of a char */
RA_OPT_CHECK_FRAMES,
RA_OPT_PORT,
RA_OPT_SPECTATE,
RA_OPT_NICK,
@ -306,6 +307,8 @@ static void retroarch_print_help(const char *arg0)
puts(" -C, --connect=HOST Connect to netplay server as user 2.");
puts(" --port=PORT Port used to netplay. Default is 55435.");
puts(" -F, --frames=NUMBER Sync frames when using netplay.");
puts(" --check-frames=NUMBER\n"
" Check frames when using netplay.");
puts(" --spectate Connect to netplay server as spectator.");
#endif
puts(" --nick=NICK Picks a username (for use with netplay). "
@ -677,6 +680,7 @@ static void retroarch_parse_input(int argc, char *argv[])
{ "host", 0, NULL, 'H' },
{ "connect", 1, NULL, 'C' },
{ "frames", 1, NULL, 'F' },
{ "check-frames", 1, NULL, RA_OPT_CHECK_FRAMES },
{ "port", 1, NULL, RA_OPT_PORT },
{ "spectate", 0, NULL, RA_OPT_SPECTATE },
#endif
@ -978,6 +982,11 @@ static void retroarch_parse_input(int argc, char *argv[])
break;
#ifdef HAVE_NETPLAY
case RA_OPT_CHECK_FRAMES:
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES);
global->netplay.check_frames = strtoul(optarg, NULL, 0);
break;
case RA_OPT_PORT:
retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT);
global->netplay.port = strtoul(optarg, NULL, 0);
@ -1645,6 +1654,7 @@ static bool has_set_netplay_mode = false;
static bool has_set_netplay_ip_address = false;
static bool has_set_netplay_ip_port = false;
static bool has_set_netplay_delay_frames= false;
static bool has_set_netplay_check_frames= false;
static bool has_set_ups_pref = false;
static bool has_set_bps_pref = false;
static bool has_set_ips_pref = false;
@ -1671,6 +1681,8 @@ bool retroarch_override_setting_is_set(enum rarch_override_setting enum_idx)
return has_set_netplay_ip_port;
case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES:
return has_set_netplay_delay_frames;
case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
return has_set_netplay_check_frames;
case RARCH_OVERRIDE_SETTING_UPS_PREF:
return has_set_ups_pref;
case RARCH_OVERRIDE_SETTING_BPS_PREF:
@ -1717,6 +1729,9 @@ void retroarch_override_setting_set(enum rarch_override_setting enum_idx)
case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES:
has_set_netplay_delay_frames = true;
break;
case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
has_set_netplay_check_frames = true;
break;
case RARCH_OVERRIDE_SETTING_UPS_PREF:
has_set_ups_pref = true;
break;
@ -1763,6 +1778,9 @@ void retroarch_override_setting_unset(enum rarch_override_setting enum_idx)
case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES:
has_set_netplay_delay_frames = false;
break;
case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES:
has_set_netplay_check_frames = false;
break;
case RARCH_OVERRIDE_SETTING_UPS_PREF:
has_set_ups_pref = false;
break;

View File

@ -110,6 +110,7 @@ enum rarch_override_setting
RARCH_OVERRIDE_SETTING_NETPLAY_IP_ADDRESS,
RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT,
RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES,
RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES,
RARCH_OVERRIDE_SETTING_UPS_PREF,
RARCH_OVERRIDE_SETTING_BPS_PREF,
RARCH_OVERRIDE_SETTING_IPS_PREF,

View File

@ -1577,7 +1577,13 @@ int runloop_iterate(unsigned *sleep_ms)
#endif
#ifdef HAVE_NETPLAY
netplay_driver_ctl(RARCH_NETPLAY_CTL_PRE_FRAME, NULL);
if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_PRE_FRAME, NULL))
{
/* Paused due to Netplay */
core_poll();
*sleep_ms = 10;
return 1;
}
#endif
if (bsv_movie_ctl(BSV_MOVIE_CTL_IS_INITED, NULL))

View File

@ -228,6 +228,7 @@ typedef struct global
bool is_client;
bool is_spectate;
unsigned sync_frames;
unsigned check_frames;
unsigned port;
} netplay;
#endif