Merge pull request #2974 from HeatXD/netplay_dev

Upstream Netplay Changes
This commit is contained in:
Connor McLaughlin 2023-05-10 21:59:56 +10:00 committed by GitHub
commit 5de071900d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 94 additions and 111 deletions

View File

@ -104,13 +104,9 @@ You will need a device with armv7 (32-bit ARM), AArch64 (64-bit ARM), or x86_64
Google Play is the preferred distribution mechanism and will result in smaller download sizes: https://play.google.com/store/apps/details?id=com.github.stenzek.duckstation
**No support is provided for the Android app**, it is free and your expectations should be in line with that. Please **do not** email me about issues about it, they will be ignored. This repository should also not be used to raise issues about the app, as it does not contain the app code, only the desktop versions.
**No support is provided for the Android app**, it is free and your expectations should be in line with that. Please **do not** email me about issues about it, they will be ignored.
If you must use an APK, download links are:
Download link: https://www.duckstation.org/android/duckstation-android.apk
Changelog link: https://www.duckstation.org/android/changelog.txt
If you must use an APK, download links are listed in https://www.duckstation.org/android/
To use:
1. Install and run the app for the first time.
@ -215,22 +211,6 @@ Hotkeys:
- **Tab:** Temporarily disable speed limiter
- **Space:** Pause/resume emulation
## Screenshots
<p align="center">
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/monkey.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/monkey.jpg" alt="Monkey Hero" width="400" /></a>
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/rrt4.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/rrt4.jpg" alt="Ridge Racer Type 4" width="400" /></a>
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/tr2.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/tr2.jpg" alt="Tomb Raider 2" width="400" /></a>
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/quake2.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/quake2.jpg" alt="Quake 2" width="400" /></a>
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/croc.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/croc.jpg" alt="Croc" width="400" /></a>
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/croc2.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/croc2.jpg" alt="Croc 2" width="400" /></a>
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/ff7.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/ff7.jpg" alt="Final Fantasy 7" width="400" /></a>
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/mm8.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/mm8.jpg" alt="Mega Man 8" width="400" /></a>
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/ff8.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/ff8.jpg" alt="Final Fantasy 8 in Fullscreen UI" width="400" /></a>
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/spyro.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/spyro.jpg" alt="Spyro in Fullscreen UI" width="400" /></a>
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/tof.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/tof.jpg" alt="Threads of Fate in Fullscreen UI" width="400" /></a>
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/gamegrid.png"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/gamegrid.png" alt="Game Grid" width="400" /></a>
</p>
## Disclaimers
Icon by icons8: https://icons8.com/icon/74847/platforms.undefined.short-title

View File

@ -53,7 +53,7 @@ void AnalogController::Reset()
if (m_force_analog_on_reset)
{
if (g_settings.controller_disable_analog_mode_forcing || System::IsRunningBIOS())
if (g_settings.controller_disable_analog_mode_forcing || System::IsRunningUnknownGame())
{
Host::AddIconOSDMessage(
fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD,
@ -835,8 +835,7 @@ static const char* s_invert_settings[] = {TRANSLATABLE("AnalogController", "Not
static const SettingInfo s_settings[] = {
{SettingInfo::Type::Boolean, "ForceAnalogOnReset", TRANSLATABLE("AnalogController", "Force Analog Mode on Reset"),
TRANSLATABLE("AnalogController", "Forces the controller to analog mode when the console is reset/powered on. May "
"cause issues with games, so it is recommended to leave this option off."),
TRANSLATABLE("AnalogController", "Forces the controller to analog mode when the console is reset/powered on."),
"true"},
{SettingInfo::Type::Boolean, "AnalogDPadInDigitalMode",
TRANSLATABLE("AnalogController", "Use Analog Sticks for D-Pad in Digital Mode"),

View File

@ -46,6 +46,7 @@ enum class ControlMessage : u32
ConnectResponse,
SynchronizeSession,
SynchronizeComplete,
ChatMessage,
};
#pragma pack(push, 1)
@ -94,6 +95,15 @@ struct ControlSynchronizeCompleteMessage
static ControlMessage MessageType() { return ControlMessage::SynchronizeComplete; }
};
struct ControlChatMessage
{
ControlMessageHeader header;
u32 chat_message_size;
static ControlMessage MessageType() { return ControlMessage::ChatMessage; }
};
#pragma pack(pop)
using SaveStateBuffer = std::unique_ptr<System::MemorySaveState>;
@ -141,6 +151,7 @@ static void HandleControlMessage(s32 player_id, const ENetPacket* pkt);
static void HandleConnectResponseMessage(s32 player_id, const ENetPacket* pkt);
static void HandleSynchronizeSessionMessage(s32 player_id, const ENetPacket* pkt);
static void HandleSynchronizeCompleteMessage(s32 player_id, const ENetPacket* pkt);
static void HandleControlChatMessage(s32 player_id, const ENetPacket* pkt);
// l = local, r = remote
static bool CreateGGPOSession();
@ -170,7 +181,6 @@ static void Throttle();
// Desync Detection
static void GenerateChecksumForFrame(int* checksum, int frame, unsigned char* buffer, int buffer_size);
static void GenerateDesyncReport(s32 desync_frame);
//////////////////////////////////////////////////////////////////////////
// Variables
@ -252,7 +262,6 @@ static bool SendControlPacket(s32 player_id, const PacketWrapper<T>& pkt)
DebugAssert(player_id >= 0 && player_id < MAX_PLAYERS && s_peers[player_id].peer);
return SendControlPacket<T>(s_peers[player_id].peer, pkt);
}
} // namespace Netplay
// Netplay Impl
@ -505,6 +514,10 @@ void Netplay::PollEnet(Common::Timer::Value until_time)
const u32 enet_timeout = (current_time >= until_time) ?
0 :
static_cast<u32>(Common::Timer::ConvertValueToMilliseconds(until_time - current_time));
// make sure s_enet_host exists
Assert(s_enet_host);
const int res = enet_host_service(s_enet_host, &event, enet_timeout);
if (res > 0)
{
@ -514,7 +527,6 @@ void Netplay::PollEnet(Common::Timer::Value until_time)
current_time = Common::Timer::GetCurrentValue();
continue;
}
// exit once we're nonblocking
current_time = Common::Timer::GetCurrentValue();
if (enet_timeout == 0 || current_time >= until_time)
@ -693,6 +705,10 @@ void Netplay::HandleControlMessage(s32 player_id, const ENetPacket* pkt)
HandleSynchronizeCompleteMessage(player_id, pkt);
break;
case ControlMessage::ChatMessage:
HandleControlChatMessage(player_id, pkt);
break;
default:
Log_ErrorPrintf("Unhandled control packet %u from player %d of size %zu", hdr->type, player_id, pkt->dataLength);
break;
@ -1003,6 +1019,22 @@ void Netplay::HandleSynchronizeCompleteMessage(s32 player_id, const ENetPacket*
CheckForCompleteResynchronize();
}
void Netplay::HandleControlChatMessage(s32 player_id, const ENetPacket* pkt)
{
const ControlChatMessage* msg = reinterpret_cast<const ControlChatMessage*>(pkt->data);
if (pkt->dataLength < sizeof(ControlChatMessage) ||
pkt->dataLength < (sizeof(ControlChatMessage) + msg->chat_message_size))
{
// invalid chat message. ignore.
return;
}
std::string message(pkt->data + sizeof(ControlChatMessage),
pkt->data + sizeof(ControlChatMessage) + msg->chat_message_size);
Host::OnNetplayMessage(fmt::format("Player {}: {}", PlayerIdToGGPOHandle(player_id), message));
}
void Netplay::CheckForCompleteResynchronize()
{
if (s_synchronized_players == s_num_players)
@ -1065,6 +1097,9 @@ void Netplay::UpdateThrottlePeriod()
void Netplay::HandleTimeSyncEvent(float frame_delta, int update_interval)
{
// only activate timesync if its worth correcting.
if (std::abs(frame_delta) < 1.0f)
return;
// Distribute the frame difference over the next N * 0.75 frames.
// only part of the interval time is used since we want to come back to normal speed.
// otherwise we will keep spiraling into unplayable gameplay.
@ -1140,37 +1175,6 @@ void Netplay::GenerateChecksumForFrame(int* checksum, int frame, unsigned char*
// Log_VerbosePrintf("Netplay Checksum: f:%d wf:%d c:%u", frame, frame % num_group_of_pages, *checksum);
}
void Netplay::GenerateDesyncReport(s32 desync_frame)
{
std::string path = "\\netplaylogs\\desync_frame_" + std::to_string(desync_frame) + "_p" +
std::to_string(s_local_handle) + "_" + System::GetRunningSerial() + "_.txt";
std::string filename = EmuFolders::Dumps + path;
std::unique_ptr<ByteStream> stream =
ByteStream::OpenFile(filename.c_str(), BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_WRITE | BYTESTREAM_OPEN_TRUNCATE |
BYTESTREAM_OPEN_ATOMIC_UPDATE | BYTESTREAM_OPEN_STREAMED);
if (!stream)
{
Log_VerbosePrint("desync log creation failed to create stream");
return;
}
if (!ByteStream::WriteBinaryToStream(stream.get(),
s_save_buffer_pool.back().get()->state_stream.get()->GetMemoryPointer(),
s_save_buffer_pool.back().get()->state_stream.get()->GetMemorySize()))
{
Log_VerbosePrint("desync log creation failed to write the stream");
stream->Discard();
return;
}
/* stream->Write(s_save_buffer_pool.back().get()->state_stream.get()->GetMemoryPointer(),
s_save_buffer_pool.back().get()->state_stream.get()->GetMemorySize());*/
stream->Commit();
Log_VerbosePrintf("desync log created for frame %d", desync_frame);
}
void Netplay::AdvanceFrame()
{
ggpo_advance_frame(s_ggpo, 0);
@ -1178,19 +1182,13 @@ void Netplay::AdvanceFrame()
void Netplay::RunFrame()
{
PollEnet(0);
if (!s_ggpo)
return;
// housekeeping
// TODO: get rid of double polling
PollEnet(0);
if (!s_ggpo)
return;
ggpo_network_idle(s_ggpo);
PollEnet(0);
if (!s_ggpo)
return;
ggpo_idle(s_ggpo);
// run game
auto result = GGPO_OK;
int disconnect_flags = 0;
@ -1238,7 +1236,36 @@ Netplay::Input Netplay::ReadLocalInput()
return inp;
}
void Netplay::SendMsg(const char* msg) {}
void Netplay::SendMsg(std::string msg)
{
ControlChatMessage header{};
const size_t msg_size = msg.size();
header.header.type = ControlMessage::ChatMessage;
header.header.size = sizeof(ControlChatMessage) + msg_size;
header.chat_message_size = msg_size;
ENetPacket* pkt = enet_packet_create(nullptr, sizeof(header) + msg_size, ENET_PACKET_FLAG_RELIABLE);
std::memcpy(pkt->data, &header, sizeof(header));
std::memcpy(pkt->data + sizeof(header), msg.c_str(), msg_size);
for (s32 i = 0; i < MAX_PLAYERS; i++)
{
if (!s_peers[i].peer)
continue;
const int err = enet_peer_send(s_peers[i].peer, ENET_CHANNEL_CONTROL, pkt);
if (err != 0)
{
// failed to send netplay message? just clean it up.
Log_ErrorPrint("Failed to send netplay message");
enet_packet_destroy(pkt);
}
}
// add own netplay message locally to netplay messages
Host::OnNetplayMessage(fmt::format("Player {}: {}", s_local_handle, msg));
}
GGPOErrorCode Netplay::SyncInput(Netplay::Input inputs[2], int* disconnect_flags)
{
@ -1288,9 +1315,10 @@ void Netplay::StartNetplaySession(s32 local_handle, u16 local_port, std::string&
Log_ErrorPrint("Failed to Create Netplay Session!");
System::ShutdownSystem(false);
}
else
else if (IsHost())
{
// Load savestate if available
// Load savestate if available and only when you are the host.
// the other peers will get state from the host
std::string save = EmuFolders::SaveStates + "/netplay/" + System::GetRunningSerial() + ".sav";
System::LoadState(save.c_str());
}
@ -1377,8 +1405,7 @@ bool Netplay::NpAdvFrameCb(void* ctx, int flags)
bool Netplay::NpSaveFrameCb(void* ctx, unsigned char** buffer, int* len, int* checksum, int frame)
{
SaveStateBuffer our_buffer;
// min size is 2 because otherwise the desync logger doesnt have enough time to dump the state.
if (s_save_buffer_pool.size() < 2)
if (s_save_buffer_pool.empty())
{
our_buffer = std::make_unique<System::MemorySaveState>();
}
@ -1465,7 +1492,6 @@ bool Netplay::NpOnEventCb(void* ctx, GGPOEvent* ev)
CurrentFrame(), ev->u.desync.nFrameOfDesync,
CurrentFrame() - ev->u.desync.nFrameOfDesync, ev->u.desync.ourCheckSum,
ev->u.desync.remoteChecksum));
GenerateDesyncReport(ev->u.desync.nFrameOfDesync);
break;
default:
Host::OnNetplayMessage(fmt::format("Netplay Event Code: {}", static_cast<int>(ev->code)));

View File

@ -8,7 +8,7 @@ namespace Netplay {
enum : s32
{
// Maximum number of emulated controllers.
MAX_PLAYERS = 2,
MAX_PLAYERS = 2,
// Maximum netplay prediction frames
MAX_ROLLBACK_FRAMES = 8,
};
@ -36,7 +36,7 @@ void ExecuteNetplay();
void CollectInput(u32 slot, u32 bind, float value);
void SendMsg(const char* msg);
void SendMsg(std::string msg);
s32 GetPing();
u32 GetMaxPrediction();

View File

@ -138,7 +138,7 @@ static BIOS::Hash s_bios_hash = {};
static std::string s_running_game_path;
static std::string s_running_game_serial;
static std::string s_running_game_title;
static bool s_running_bios;
static bool s_running_unknown_game;
static float s_throttle_frequency = 60.0f;
static float s_target_speed = 1.0f;
@ -320,9 +320,9 @@ const std::string& System::GetRunningTitle()
return s_running_game_title;
}
bool System::IsRunningBIOS()
bool System::IsRunningUnknownGame()
{
return s_running_bios;
return s_running_unknown_game;
}
const BIOS::ImageInfo* System::GetBIOSImageInfo()
@ -958,9 +958,6 @@ void System::ResetSystem()
ResetPerformanceCounters();
ResetThrottler();
Host::AddOSDMessage(Host::TranslateStdString("OSDMessage", "System reset."));
// need to clear this here, because of eject disc -> reset.
s_running_bios = !s_running_game_path.empty();
}
void System::PauseSystem(bool paused)
@ -1239,9 +1236,6 @@ bool System::BootSystem(SystemBootParameters parameters)
return false;
}
// Allow controller analog mode for EXEs and PSFs.
s_running_bios = s_running_game_path.empty() && exe_boot.empty() && psf_boot.empty();
UpdateControllers();
UpdateMemoryCardTypes();
UpdateMultitaps();
@ -1510,7 +1504,7 @@ void System::ClearRunningGame()
s_running_game_serial.clear();
s_running_game_path.clear();
s_running_game_title.clear();
s_running_bios = false;
s_running_unknown_game = false;
s_cheat_list.reset();
s_state = State::Shutdown;
@ -3053,6 +3047,7 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting)
s_running_game_path.clear();
s_running_game_serial.clear();
s_running_game_title.clear();
s_running_unknown_game = true;
if (path && std::strlen(path) > 0)
{
@ -3070,6 +3065,7 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting)
{
s_running_game_serial = entry->serial;
s_running_game_title = entry->title;
s_running_unknown_game = false;
}
else
{

View File

@ -184,7 +184,7 @@ const std::string& GetRunningPath();
const std::string& GetRunningSerial();
const std::string& GetRunningTitle();
bool IsRunningBIOS();
bool IsRunningUnknownGame();
const BIOS::ImageInfo* GetBIOSImageInfo();
const BIOS::Hash& GetBIOSHash();

View File

@ -1568,10 +1568,6 @@ void MainWindow::setupAdditionalUi()
m_status_vps_widget->setFixedSize(120, 16);
m_status_vps_widget->hide();
m_status_ping_widget = new QLabel(m_ui.statusBar);
m_status_ping_widget->setFixedSize(110, 16);
m_status_ping_widget->hide();
m_settings_toolbar_menu = new QMenu(m_ui.toolBar);
m_settings_toolbar_menu->addAction(m_ui.actionSettings);
m_settings_toolbar_menu->addAction(m_ui.actionViewGameProperties);
@ -1777,7 +1773,6 @@ void MainWindow::updateStatusBarWidgetVisibility()
Update(m_status_resolution_widget, s_system_valid && !s_system_paused, 0);
Update(m_status_fps_widget, s_system_valid && !s_system_paused, 0);
Update(m_status_vps_widget, s_system_valid && !s_system_paused, 0);
Update(m_status_ping_widget, s_system_valid && !s_system_paused && m_netplay_window != nullptr, 0);
}
void MainWindow::updateWindowTitle()

View File

@ -90,7 +90,6 @@ public:
ALWAYS_INLINE QLabel* getStatusResolutionWidget() const { return m_status_resolution_widget; }
ALWAYS_INLINE QLabel* getStatusFPSWidget() const { return m_status_fps_widget; }
ALWAYS_INLINE QLabel* getStatusVPSWidget() const { return m_status_vps_widget; }
ALWAYS_INLINE QLabel* getStatusPingWidget() const { return m_status_ping_widget; }
public Q_SLOTS:
/// Updates debug menu visibility (hides if disabled).
@ -266,7 +265,6 @@ private:
QLabel* m_status_renderer_widget = nullptr;
QLabel* m_status_fps_widget = nullptr;
QLabel* m_status_vps_widget = nullptr;
QLabel* m_status_ping_widget = nullptr;
QLabel* m_status_resolution_widget = nullptr;
QMenu* m_settings_toolbar_menu = nullptr;

View File

@ -1079,12 +1079,6 @@ void EmuThread::startNetplaySession(int local_handle, quint16 local_port, const
Q_ARG(quint16, remote_port), Q_ARG(int, input_delay), Q_ARG(const QString&, game_path));
return;
}
// disable block linking and disable rewind and runahead during a netplay session
g_settings.cpu_recompiler_block_linking = false;
g_settings.rewind_enable = false;
g_settings.runahead_frames = 0;
Log_WarningPrintf("Disabling block linking, runahead and rewind due to rollback.");
auto remAddr = remote_addr.trimmed().toStdString();
auto gamePath = game_path.trimmed().toStdString();
@ -1101,7 +1095,8 @@ void EmuThread::sendNetplayMessage(const QString& message)
QMetaObject::invokeMethod(this, "sendNetplayMessage", Qt::QueuedConnection, Q_ARG(const QString&, message));
return;
}
Netplay::SendMsg(message.toStdString().c_str());
// TODO REDO NETPLAY UI
// Netplay::SendMsg(message.toStdString().c_str());
}
void EmuThread::stopNetplaySession()
@ -1714,14 +1709,6 @@ void EmuThread::updatePerformanceCounters()
m_last_speed = speed;
m_last_video_fps = vfps;
}
const s32 ping = Netplay::GetPing();
if (m_last_ping != ping)
{
QMetaObject::invokeMethod(g_main_window->getStatusPingWidget(), "setText", Qt::QueuedConnection,
Q_ARG(const QString&, tr("Netplay Ping: %1 ").arg(ping, 0, 'f', 0)));
m_last_ping = ping;
}
}
void EmuThread::resetPerformanceCounters()

View File

@ -175,8 +175,10 @@ void ImGuiManager::DrawNetplayChatDialog()
const bool close_chat =
send_message || (s_netplay_chat_message.empty() && (ImGui::IsKeyPressed(ImGuiKey_Backspace)) ||
ImGui::IsKeyPressed(ImGuiKey_Escape));
// sending netplay message
if (send_message && !s_netplay_chat_message.empty())
Netplay::SendMsg(s_netplay_chat_message.c_str());
Netplay::SendMsg(s_netplay_chat_message);
const ImGuiIO& io = ImGui::GetIO();
const ImGuiStyle& style = ImGui::GetStyle();