Netplay: OSD for messages/chat

This commit is contained in:
Stenzek 2023-05-07 17:18:43 +10:00
parent e214fa3a44
commit e1f97277b2
11 changed files with 247 additions and 41 deletions

View File

@ -543,62 +543,46 @@ void Netplay::NpFreeBuffCb(void* ctx, void* buffer, int frame)
bool Netplay::NpOnEventCb(void* ctx, GGPOEvent* ev)
{
char buff[128];
std::string msg, filename;
switch (ev->code)
{
case GGPOEventCode::GGPO_EVENTCODE_CONNECTED_TO_PEER:
sprintf(buff, "Netplay Connected To Player: %d", ev->u.connected.player);
msg = buff;
Host::OnNetplayMessage(fmt::format("Netplay Connected To Player: {}", ev->u.connected.player));
break;
case GGPOEventCode::GGPO_EVENTCODE_SYNCHRONIZING_WITH_PEER:
sprintf(buff, "Netplay Synchronzing: %d/%d", ev->u.synchronizing.count, ev->u.synchronizing.total);
msg = buff;
Host::OnNetplayMessage(fmt::format("Netplay Synchronzing: {}/{}", ev->u.synchronizing.count, ev->u.synchronizing.total));
break;
case GGPOEventCode::GGPO_EVENTCODE_SYNCHRONIZED_WITH_PEER:
sprintf(buff, "Netplay Synchronized With Player: %d", ev->u.synchronized.player);
msg = buff;
Host::OnNetplayMessage(fmt::format("Netplay Synchronized With Player: {}", ev->u.synchronized.player));
break;
case GGPOEventCode::GGPO_EVENTCODE_DISCONNECTED_FROM_PEER:
sprintf(buff, "Netplay Player: %d Disconnected", ev->u.disconnected.player);
msg = buff;
Host::OnNetplayMessage(fmt::format("Netplay Player: %d Disconnected", ev->u.disconnected.player));
break;
case GGPOEventCode::GGPO_EVENTCODE_RUNNING:
msg = "Netplay Is Running";
Host::OnNetplayMessage("Netplay Is Running");
break;
case GGPOEventCode::GGPO_EVENTCODE_CONNECTION_INTERRUPTED:
sprintf(buff, "Netplay Player: %d Connection Interupted, Timeout: %d", ev->u.connection_interrupted.player,
ev->u.connection_interrupted.disconnect_timeout);
msg = buff;
Host::OnNetplayMessage(fmt::format("Netplay Player: {} Connection Interupted, Timeout: {}", ev->u.connection_interrupted.player,
ev->u.connection_interrupted.disconnect_timeout));
break;
case GGPOEventCode::GGPO_EVENTCODE_CONNECTION_RESUMED:
sprintf(buff, "Netplay Player: %d Connection Resumed", ev->u.connection_resumed.player);
msg = buff;
Host::OnNetplayMessage(fmt::format("Netplay Player: {} Connection Resumed", ev->u.connection_resumed.player));
break;
case GGPOEventCode::GGPO_EVENTCODE_CHAT:
sprintf(buff, "%s", ev->u.chat.msg);
msg = buff;
Host::OnNetplayMessage(ev->u.chat.msg);
break;
case GGPOEventCode::GGPO_EVENTCODE_TIMESYNC:
HandleTimeSyncEvent(ev->u.timesync.frames_ahead, ev->u.timesync.timeSyncPeriodInFrames);
break;
case GGPOEventCode::GGPO_EVENTCODE_DESYNC:
sprintf(buff, "Desync Detected: Current Frame: %d, Desync Frame: %d, Diff: %d, L:%u, R:%u", CurrentFrame(),
Host::OnNetplayMessage(fmt::format("Desync Detected: Current Frame: {}, Desync Frame: {}, Diff: {}, L:{}, R:{}", CurrentFrame(),
ev->u.desync.nFrameOfDesync, CurrentFrame() - ev->u.desync.nFrameOfDesync, ev->u.desync.ourCheckSum,
ev->u.desync.remoteChecksum);
msg = buff;
ev->u.desync.remoteChecksum));
GenerateDesyncReport(ev->u.desync.nFrameOfDesync);
Host::AddKeyedOSDMessage("Netplay", msg, 5);
return true;
break;
default:
sprintf(buff, "Netplay Event Code: %d", ev->code);
msg = buff;
}
if (!msg.empty())
{
Host::OnNetplayMessage(msg);
Log_InfoPrintf("%s", msg.c_str());
Host::OnNetplayMessage(fmt::format("Netplay Event Code: {}", static_cast<int>(ev->code)));
break;
}
return true;
}

View File

@ -512,5 +512,5 @@ bool IsFullscreen();
/// Alters fullscreen state of hosting application.
void SetFullscreen(bool enabled);
// netplay
void OnNetplayMessage(std::string& message);
void OnNetplayMessage(std::string message);
} // namespace Host

View File

@ -38,8 +38,6 @@ void NetplayWidget::FillGameList()
void NetplayWidget::SetupConnections()
{
// connect netplay window messages
connect(g_emu_thread, &EmuThread::onNetplayMessage, this, &NetplayWidget::OnMsgReceived);
// connect sending messages when the chat button has been pressed
connect(m_ui->btnSendMsg, &QPushButton::pressed, [this]() {
// check if message aint empty and the complete message ( message + name + ":" + space) is below 120 characters

View File

@ -465,12 +465,6 @@ void EmuThread::startFullscreenUI()
wakeThread();
}
void Host::OnNetplayMessage(std::string& message)
{
QString msg(message.c_str());
emit g_emu_thread->onNetplayMessage(msg);
}
void EmuThread::stopFullscreenUI()
{
if (!isOnThread())
@ -1513,6 +1507,8 @@ void EmuThread::renderDisplay(bool skip_present)
if (!skip_present)
{
FullscreenUI::Render();
if (Netplay::IsActive())
ImGuiManager::RenderNetplayOverlays();
ImGuiManager::RenderTextOverlays();
ImGuiManager::RenderOSDMessages();
}

View File

@ -143,7 +143,6 @@ Q_SIGNALS:
void achievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total, quint32 points);
void achievementsChallengeModeChanged();
void cheatEnabled(quint32 index, bool enabled);
void onNetplayMessage(const QString& message);
public Q_SLOTS:
void setDefaultSettings(bool system = true, bool controller = true);

View File

@ -16,6 +16,7 @@ add_library(frontend-common
imgui_fullscreen.h
imgui_manager.cpp
imgui_manager.h
imgui_netplay.cpp
imgui_overlays.cpp
imgui_overlays.h
platform_misc.h

View File

@ -677,6 +677,11 @@ DEFINE_HOTKEY("OpenPauseMenu", TRANSLATABLE("Hotkeys", "General"), TRANSLATABLE(
[](s32 pressed) {
if (!pressed)
FullscreenUI::OpenPauseMenu();
})
DEFINE_HOTKEY("OpenNetplayChat", TRANSLATABLE("Hotkeys", "General"), TRANSLATABLE("Hotkeys", "Open Netplay Chat"),
[](s32 pressed) {
if (!pressed)
ImGuiManager::OpenNetplayChat();
})
#endif

View File

@ -22,6 +22,7 @@
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="imgui_manager.cpp" />
<ClCompile Include="imgui_netplay.cpp" />
<ClCompile Include="imgui_overlays.cpp" />
<ClCompile Include="input_manager.cpp" />
<ClCompile Include="input_source.cpp" />

View File

@ -30,6 +30,7 @@
<ClCompile Include="dinput_source.cpp" />
<ClCompile Include="imgui_overlays.cpp" />
<ClCompile Include="platform_misc_win32.cpp" />
<ClCompile Include="imgui_netplay.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="icon.h" />

View File

@ -0,0 +1,218 @@
// SPDX-FileCopyrightText: 2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#define IMGUI_DEFINE_MATH_OPERATORS
#include "IconsFontAwesome5.h"
#include "common/align.h"
#include "common/assert.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/string_util.h"
#include "common/timer.h"
#include "common_host.h"
#include "core/controller.h"
#include "core/gpu.h"
#include "core/host.h"
#include "core/host_display.h"
#include "core/host_settings.h"
#include "core/netplay.h"
#include "core/settings.h"
#include "core/spu.h"
#include "core/system.h"
#include "fmt/chrono.h"
#include "fmt/format.h"
#include "fullscreen_ui.h"
#include "gsl/span"
#include "icon.h"
#include "imgui.h"
#include "imgui_fullscreen.h"
#include "imgui_internal.h"
#include "imgui_manager.h"
#include "imgui_overlays.h"
#include "imgui_stdlib.h"
#include "input_manager.h"
#include "util/audio_stream.h"
#include <atomic>
#include <chrono>
#include <cmath>
#include <deque>
#include <mutex>
#include <unordered_map>
#if defined(CPU_X64)
#include <emmintrin.h>
#elif defined(CPU_AARCH64)
#ifdef _MSC_VER
#include <arm64_neon.h>
#else
#include <arm_neon.h>
#endif
#endif
Log_SetChannel(ImGuiManager);
namespace ImGuiManager {
static void DrawNetplayMessages();
static void DrawNetplayStats();
static void DrawNetplayChatDialog();
} // namespace ImGuiManager
static std::deque<std::pair<std::string, Common::Timer::Value>> s_netplay_messages;
static constexpr u32 MAX_NETPLAY_MESSAGES = 15;
static constexpr float NETPLAY_MESSAGE_DURATION = 15.0f;
static constexpr float NETPLAY_MESSAGE_FADE_TIME = 2.0f;
static bool s_netplay_chat_dialog_open = false;
static bool s_netplay_chat_dialog_opening = false;
static std::string s_netplay_chat_message;
void Host::OnNetplayMessage(std::string message)
{
Log_InfoPrintf("Netplay: %s", message.c_str());
while (s_netplay_messages.size() >= MAX_NETPLAY_MESSAGES)
s_netplay_messages.pop_front();
s_netplay_messages.emplace_back(std::move(message), Common::Timer::GetCurrentValue() +
Common::Timer::ConvertSecondsToValue(NETPLAY_MESSAGE_DURATION));
}
void ImGuiManager::RenderNetplayOverlays()
{
DrawNetplayMessages();
DrawNetplayStats();
DrawNetplayChatDialog();
}
void ImGuiManager::DrawNetplayMessages()
{
if (s_netplay_messages.empty())
return;
const Common::Timer::Value ticks = Common::Timer::GetCurrentValue();
const ImGuiIO& io = ImGui::GetIO();
const float scale = ImGuiManager::GetGlobalScale();
const float shadow_offset = 1.0f * scale;
const float margin = 10.0f * scale;
const float spacing = 5.0f * scale;
const float msg_spacing = 2.0f * scale;
ImFont* font = ImGuiManager::GetFixedFont();
float position_y = io.DisplaySize.y - margin - ((spacing + font->FontSize) * 2.0f) - font->FontSize;
ImDrawList* dl = ImGui::GetBackgroundDrawList();
// drop expired messages.. because of the reverse iteration below, we can't do it in there :/
for (auto iter = s_netplay_messages.begin(); iter != s_netplay_messages.end();)
{
if (ticks >= iter->second)
iter = s_netplay_messages.erase(iter);
else
++iter;
}
for (auto iter = s_netplay_messages.rbegin(); iter != s_netplay_messages.rend(); ++iter)
{
const float remainder = static_cast<float>(Common::Timer::ConvertValueToSeconds(iter->second - ticks));
const float opacity = std::min(remainder / NETPLAY_MESSAGE_FADE_TIME, 1.0f);
const u32 alpha = static_cast<u32>(opacity * 255.0f);
const u32 shadow_alpha = static_cast<u32>(opacity * 100.0f);
// TODO: line wrapping..
const char* text_start = iter->first.c_str();
const char* text_end = text_start + iter->first.length();
const ImVec2 text_size = font->CalcTextSizeA(font->FontSize, io.DisplaySize.x, 0.0f, text_start, text_end, nullptr);
dl->AddText(font, font->FontSize,
ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x + shadow_offset, position_y + shadow_offset),
IM_COL32(0, 0, 0, shadow_alpha), text_start, text_end);
dl->AddText(font, font->FontSize, ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x, position_y),
IM_COL32(255, 255, 255, alpha), text_start, text_end);
position_y -= text_size.y + msg_spacing;
}
}
void ImGuiManager::DrawNetplayStats()
{
// Not much yet.. eventually we'll render chat and such here too.
// We'll probably want to draw a graph too..
LargeString text;
text.AppendFmtString("Ping: {}", Netplay::GetPing());
const float scale = ImGuiManager::GetGlobalScale();
const float shadow_offset = 1.0f * scale;
const float margin = 10.0f * scale;
const float spacing = 5.0f * scale;
ImFont* font = ImGuiManager::GetFixedFont();
const float position_y = ImGui::GetIO().DisplaySize.y - margin - font->FontSize - spacing - font->FontSize;
ImDrawList* dl = ImGui::GetBackgroundDrawList();
ImVec2 text_size = font->CalcTextSizeA(font->FontSize, std::numeric_limits<float>::max(), -1.0f, text,
text.GetCharArray() + text.GetLength(), nullptr);
dl->AddText(font, font->FontSize,
ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x + shadow_offset, position_y + shadow_offset),
IM_COL32(0, 0, 0, 100), text, text.GetCharArray() + text.GetLength());
dl->AddText(font, font->FontSize, ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x, position_y),
IM_COL32(255, 255, 255, 255), text, text.GetCharArray() + text.GetLength());
}
void ImGuiManager::DrawNetplayChatDialog()
{
// TODO: This needs to block controller input...
if (s_netplay_chat_dialog_opening)
{
ImGui::OpenPopup("Netplay Chat");
s_netplay_chat_dialog_open = true;
s_netplay_chat_dialog_opening = false;
}
else if (!s_netplay_chat_dialog_open)
{
return;
}
const bool send_message = ImGui::IsKeyPressed(ImGuiKey_Enter);
const bool close_chat =
send_message || (s_netplay_chat_message.empty() && (ImGui::IsKeyPressed(ImGuiKey_Backspace)) ||
ImGui::IsKeyPressed(ImGuiKey_Escape));
if (send_message && !s_netplay_chat_message.empty())
Netplay::SendMsg(s_netplay_chat_message.c_str());
const ImGuiIO& io = ImGui::GetIO();
const ImGuiStyle& style = ImGui::GetStyle();
const float scale = ImGuiManager::GetGlobalScale();
const float width = 600.0f * scale;
const float height = 60.0f * scale;
ImGui::SetNextWindowSize(ImVec2(width, height));
ImGui::SetNextWindowPos(io.DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGui::SetNextWindowFocus();
if (ImGui::BeginPopupModal("Netplay Chat", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize))
{
ImGui::SetNextItemWidth(width - style.WindowPadding.x * 2.0f);
ImGui::SetKeyboardFocusHere();
ImGui::InputText("##chatmsg", &s_netplay_chat_message);
if (!s_netplay_chat_dialog_open)
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
if (close_chat)
{
s_netplay_chat_message.clear();
s_netplay_chat_dialog_open = false;
}
s_netplay_chat_dialog_opening = false;
}
void ImGuiManager::OpenNetplayChat()
{
if (s_netplay_chat_dialog_open)
return;
s_netplay_chat_dialog_opening = true;
}

View File

@ -8,6 +8,9 @@
namespace ImGuiManager {
void RenderTextOverlays();
void RenderOverlayWindows();
void RenderNetplayOverlays();
void OpenNetplayChat();
}
namespace SaveStateSelectorUI {