mirror of
https://github.com/CTCaer/RetroArch.git
synced 2025-02-09 21:22:36 +00:00
Merge pull request #3600 from GregorR/netplay-new-features
New Netplay features
This commit is contained in:
commit
e4cf93cff4
@ -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;
|
||||
|
@ -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
|
||||
|
18
core_impl.c
18
core_impl.c
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
*
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
18
retroarch.c
18
retroarch.c
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
|
Loading…
x
Reference in New Issue
Block a user