NetPlay: Implement chunked data transfer

This sends arbitrary packets in chunks to be reassembled at the other
end, allowing large data transfers to be speed-limited and interleaved
with other packets being sent. It also enables tracking the progress of
large data transfers.
This commit is contained in:
Techjar 2018-10-18 04:33:05 -04:00
parent e6b2758ab4
commit d94922002b
16 changed files with 569 additions and 30 deletions

View File

@ -10,6 +10,7 @@
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Common/Random.h"
#include "Core/NetPlayProto.h"
TraversalClient::TraversalClient(ENetHost* netHost, const std::string& server, const u16 port)
: m_NetHost(netHost), m_Server(server), m_port(port)
@ -316,11 +317,11 @@ bool EnsureTraversalClient(const std::string& server, u16 server_port, u16 liste
g_OldListenPort = listen_port;
ENetAddress addr = {ENET_HOST_ANY, listen_port};
ENetHost* host = enet_host_create(&addr, // address
50, // peerCount
1, // channelLimit
0, // incomingBandwidth
0); // outgoingBandwidth
ENetHost* host = enet_host_create(&addr, // address
50, // peerCount
NetPlay::CHANNEL_COUNT, // channelLimit
0, // incomingBandwidth
0); // outgoingBandwidth
if (!host)
{
g_MainNetHost.reset();

View File

@ -33,6 +33,11 @@ const ConfigInfo<bool> NETPLAY_USE_UPNP{{System::Main, "NetPlay", "UseUPNP"}, fa
const ConfigInfo<bool> NETPLAY_ENABLE_QOS{{System::Main, "NetPlay", "EnableQoS"}, true};
const ConfigInfo<bool> NETPLAY_ENABLE_CHUNKED_UPLOAD_LIMIT{
{System::Main, "NetPlay", "EnableChunkedUploadLimit"}, false};
const ConfigInfo<u32> NETPLAY_CHUNKED_UPLOAD_LIMIT{{System::Main, "NetPlay", "ChunkedUploadLimit"},
3000};
const ConfigInfo<u32> NETPLAY_BUFFER_SIZE{{System::Main, "NetPlay", "BufferSize"}, 5};
const ConfigInfo<u32> NETPLAY_CLIENT_BUFFER_SIZE{{System::Main, "NetPlay", "BufferSizeClient"}, 1};

View File

@ -30,6 +30,9 @@ extern const ConfigInfo<bool> NETPLAY_USE_UPNP;
extern const ConfigInfo<bool> NETPLAY_ENABLE_QOS;
extern const ConfigInfo<bool> NETPLAY_ENABLE_CHUNKED_UPLOAD_LIMIT;
extern const ConfigInfo<u32> NETPLAY_CHUNKED_UPLOAD_LIMIT;
extern const ConfigInfo<u32> NETPLAY_BUFFER_SIZE;
extern const ConfigInfo<u32> NETPLAY_CLIENT_BUFFER_SIZE;

View File

@ -112,7 +112,7 @@ NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlay
if (!traversal_config.use_traversal)
{
// Direct Connection
m_client = enet_host_create(nullptr, 1, 3, 0, 0);
m_client = enet_host_create(nullptr, 1, CHANNEL_COUNT, 0, 0);
if (m_client == nullptr)
{
@ -124,7 +124,7 @@ NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlay
enet_address_set_host(&addr, address.c_str());
addr.port = port;
m_server = enet_host_connect(m_client, &addr, 3, 0);
m_server = enet_host_connect(m_client, &addr, CHANNEL_COUNT, 0);
if (m_server == nullptr)
{
@ -338,6 +338,61 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
}
break;
case NP_MSG_CHUNKED_DATA_START:
{
u32 cid;
packet >> cid;
std::string title;
packet >> title;
u64 data_size = Common::PacketReadU64(packet);
m_chunked_data_receive_queue.emplace(cid, sf::Packet{});
std::vector<int> players;
players.push_back(m_local_player->pid);
m_dialog->ShowChunkedProgressDialog(title, data_size, players);
}
break;
case NP_MSG_CHUNKED_DATA_END:
{
u32 cid;
packet >> cid;
OnData(m_chunked_data_receive_queue[cid]);
m_chunked_data_receive_queue.erase(m_chunked_data_receive_queue.find(cid));
m_dialog->HideChunkedProgressDialog();
sf::Packet complete_packet;
complete_packet << static_cast<MessageId>(NP_MSG_CHUNKED_DATA_COMPLETE);
complete_packet << cid;
Send(complete_packet, CHUNKED_DATA_CHANNEL);
}
break;
case NP_MSG_CHUNKED_DATA_PAYLOAD:
{
u32 cid;
packet >> cid;
while (!packet.endOfPacket())
{
u8 byte;
packet >> byte;
m_chunked_data_receive_queue[cid] << byte;
}
m_dialog->SetChunkedProgress(m_local_player->pid,
m_chunked_data_receive_queue[cid].getDataSize());
sf::Packet progress_packet;
progress_packet << static_cast<MessageId>(NP_MSG_CHUNKED_DATA_PROGRESS);
progress_packet << cid;
progress_packet << sf::Uint64{m_chunked_data_receive_queue[cid].getDataSize()};
Send(progress_packet, CHUNKED_DATA_CHANNEL);
}
break;
case NP_MSG_PAD_MAPPING:
{
for (PadMapping& mapping : m_pad_map)
@ -1050,11 +1105,11 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
return 0;
}
void NetPlayClient::Send(const sf::Packet& packet)
void NetPlayClient::Send(const sf::Packet& packet, const u8 channel_id)
{
ENetPacket* epac =
enet_packet_create(packet.getData(), packet.getDataSize(), ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(m_server, 0, epac);
enet_peer_send(m_server, channel_id, epac);
}
void NetPlayClient::DisplayPlayersPing()
@ -1104,11 +1159,11 @@ void NetPlayClient::Disconnect()
m_server = nullptr;
}
void NetPlayClient::SendAsync(sf::Packet&& packet)
void NetPlayClient::SendAsync(sf::Packet&& packet, const u8 channel_id)
{
{
std::lock_guard<std::recursive_mutex> lkq(m_crit.async_queue_write);
m_async_queue.Push(std::move(packet));
m_async_queue.Push(AsyncQueueEntry{std::move(packet), channel_id});
}
ENetUtil::WakeupThread(m_client);
}
@ -1136,7 +1191,10 @@ void NetPlayClient::ThreadFunc()
net = enet_host_service(m_client, &netEvent, 250);
while (!m_async_queue.Empty())
{
Send(m_async_queue.Front());
{
auto& e = m_async_queue.Front();
Send(e.packet, e.channel_id);
}
m_async_queue.Pop();
}
if (net > 0)
@ -1557,7 +1615,7 @@ void NetPlayClient::OnConnectReady(ENetAddress addr)
if (m_connection_state == ConnectionState::WaitingForTraversalClientConnectReady)
{
m_connection_state = ConnectionState::Connecting;
enet_host_connect(m_client, &addr, 0, 0);
enet_host_connect(m_client, &addr, CHANNEL_COUNT, 0);
}
}

View File

@ -13,6 +13,8 @@
#include <optional>
#include <string>
#include <thread>
#include <unordered_map>
#include <utility>
#include <vector>
#include "Common/CommonTypes.h"
@ -60,6 +62,11 @@ public:
virtual void SetMD5Progress(int pid, int progress) = 0;
virtual void SetMD5Result(int pid, const std::string& result) = 0;
virtual void AbortMD5() = 0;
virtual void ShowChunkedProgressDialog(const std::string& title, u64 data_size,
const std::vector<int>& players) = 0;
virtual void HideChunkedProgressDialog() = 0;
virtual void SetChunkedProgress(int pid, u64 progress) = 0;
};
enum class PlayerGameStatus
@ -85,7 +92,7 @@ class NetPlayClient : public TraversalClientClient
{
public:
void ThreadFunc();
void SendAsync(sf::Packet&& packet);
void SendAsync(sf::Packet&& packet, u8 channel_id = DEFAULT_CHANNEL);
NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog,
const std::string& name, const NetTraversalConfig& traversal_config);
@ -130,6 +137,12 @@ public:
void AdjustPadBufferSize(unsigned int size);
protected:
struct AsyncQueueEntry
{
sf::Packet packet;
u8 channel_id;
};
void ClearBuffers();
struct
@ -140,7 +153,7 @@ protected:
std::recursive_mutex async_queue_write;
} m_crit;
Common::SPSCQueue<sf::Packet, false> m_async_queue;
Common::SPSCQueue<AsyncQueueEntry, false> m_async_queue;
std::array<Common::SPSCQueue<GCPadStatus>, 4> m_pad_buffer;
std::array<Common::SPSCQueue<NetWiimote>, 4> m_wiimote_buffer;
@ -203,7 +216,7 @@ private:
void AddPadStateToPacket(int in_game_pad, const GCPadStatus& np, sf::Packet& packet);
void SendWiimoteState(int in_game_pad, const NetWiimote& nw);
unsigned int OnData(sf::Packet& packet);
void Send(const sf::Packet& packet);
void Send(const sf::Packet& packet, u8 channel_id = DEFAULT_CHANNEL);
void Disconnect();
bool Connect();
void ComputeMD5(const std::string& file_identifier);
@ -233,6 +246,7 @@ private:
u16 m_sync_ar_codes_count = 0;
u16 m_sync_ar_codes_success_count = 0;
bool m_sync_ar_codes_complete = false;
std::unordered_map<u32, sf::Packet> m_chunked_data_receive_queue;
u64 m_initial_rtc = 0;
u32 m_timebase_frame = 0;

View File

@ -110,6 +110,12 @@ enum
NP_MSG_CHAT_MESSAGE = 0x30,
NP_MSG_CHUNKED_DATA_START = 0x40,
NP_MSG_CHUNKED_DATA_END = 0x41,
NP_MSG_CHUNKED_DATA_PAYLOAD = 0x42,
NP_MSG_CHUNKED_DATA_PROGRESS = 0x43,
NP_MSG_CHUNKED_DATA_COMPLETE = 0x44,
NP_MSG_PAD_DATA = 0x60,
NP_MSG_PAD_MAPPING = 0x61,
NP_MSG_PAD_BUFFER = 0x62,
@ -179,6 +185,10 @@ enum
constexpr u32 NETPLAY_LZO_IN_LEN = 1024 * 64;
constexpr u32 NETPLAY_LZO_OUT_LEN = NETPLAY_LZO_IN_LEN + (NETPLAY_LZO_IN_LEN / 16) + 64 + 3;
constexpr size_t CHUNKED_DATA_UNIT_SIZE = 16384;
constexpr u8 CHANNEL_COUNT = 2;
constexpr u8 DEFAULT_CHANNEL = 0;
constexpr u8 CHUNKED_DATA_CHANNEL = 1;
using NetWiimote = std::vector<u8>;
using MessageId = u8;

View File

@ -5,6 +5,7 @@
#include "Core/NetPlayServer.h"
#include <algorithm>
#include <chrono>
#include <cinttypes>
#include <cstddef>
#include <cstdio>
@ -66,6 +67,10 @@ NetPlayServer::~NetPlayServer()
if (is_connected)
{
m_do_loop = false;
m_chunked_data_event.Set();
m_chunked_data_complete_event.Set();
if (m_chunked_data_thread.joinable())
m_chunked_data_thread.join();
m_thread.join();
enet_host_destroy(m_server);
@ -118,7 +123,7 @@ NetPlayServer::NetPlayServer(const u16 port, const bool forward_port,
ENetAddress serverAddr;
serverAddr.host = ENET_HOST_ANY;
serverAddr.port = port;
m_server = enet_host_create(&serverAddr, 10, 3, 0, 0);
m_server = enet_host_create(&serverAddr, 10, CHANNEL_COUNT, 0, 0);
if (m_server != nullptr)
m_server->intercept = ENetUtil::InterceptCallback;
}
@ -128,6 +133,7 @@ NetPlayServer::NetPlayServer(const u16 port, const bool forward_port,
m_do_loop = true;
m_thread = std::thread(&NetPlayServer::ThreadFunc, this);
m_target_buffer_size = 5;
m_chunked_data_thread = std::thread(&NetPlayServer::ChunkedDataThreadFunc, this);
#ifdef USE_UPNP
if (forward_port)
@ -164,7 +170,16 @@ void NetPlayServer::ThreadFunc()
{
{
std::lock_guard<std::recursive_mutex> lkp(m_crit.players);
SendToClients(m_async_queue.Front());
auto& e = m_async_queue.Front();
if (e.target_mode == TargetMode::Only)
{
if (m_players.find(e.target_pid) != m_players.end())
Send(m_players.at(e.target_pid).socket, e.packet, e.channel_id);
}
else
{
SendToClients(e.packet, e.target_pid, e.channel_id);
}
}
m_async_queue.Pop();
}
@ -529,15 +544,47 @@ void NetPlayServer::SetHostInputAuthority(const bool enable)
AdjustPadBufferSize(m_target_buffer_size);
}
void NetPlayServer::SendAsyncToClients(sf::Packet&& packet)
void NetPlayServer::SendAsync(sf::Packet&& packet, const PlayerId pid, const u8 channel_id)
{
{
std::lock_guard<std::recursive_mutex> lkq(m_crit.async_queue_write);
m_async_queue.Push(std::move(packet));
m_async_queue.Push(AsyncQueueEntry{std::move(packet), pid, TargetMode::Only, channel_id});
}
ENetUtil::WakeupThread(m_server);
}
void NetPlayServer::SendAsyncToClients(sf::Packet&& packet, const PlayerId skip_pid,
const u8 channel_id)
{
{
std::lock_guard<std::recursive_mutex> lkq(m_crit.async_queue_write);
m_async_queue.Push(
AsyncQueueEntry{std::move(packet), skip_pid, TargetMode::AllExcept, channel_id});
}
ENetUtil::WakeupThread(m_server);
}
void NetPlayServer::SendChunked(sf::Packet&& packet, const PlayerId pid, const std::string& title)
{
{
std::lock_guard<std::recursive_mutex> lkq(m_crit.chunked_data_queue_write);
m_chunked_data_queue.Push(
ChunkedDataQueueEntry{std::move(packet), pid, TargetMode::Only, title});
}
m_chunked_data_event.Set();
}
void NetPlayServer::SendChunkedToClients(sf::Packet&& packet, const PlayerId skip_pid,
const std::string& title)
{
{
std::lock_guard<std::recursive_mutex> lkq(m_crit.chunked_data_queue_write);
m_chunked_data_queue.Push(
ChunkedDataQueueEntry{std::move(packet), skip_pid, TargetMode::AllExcept, title});
}
m_chunked_data_event.Set();
}
// called from ---NETPLAY--- thread
unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
{
@ -566,6 +613,29 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
}
break;
case NP_MSG_CHUNKED_DATA_PROGRESS:
{
u32 cid;
packet >> cid;
u64 progress = Common::PacketReadU64(packet);
m_dialog->SetChunkedProgress(player.pid, progress);
}
break;
case NP_MSG_CHUNKED_DATA_COMPLETE:
{
u32 cid;
packet >> cid;
if (m_chunked_data_complete_count.find(cid) != m_chunked_data_complete_count.end())
{
m_chunked_data_complete_count[cid]++;
m_chunked_data_complete_event.Set();
}
}
break;
case NP_MSG_PAD_DATA:
{
// if this is pad data from the last game still being received, ignore it
@ -1611,22 +1681,23 @@ u64 NetPlayServer::GetInitialNetPlayRTC() const
}
// called from multiple threads
void NetPlayServer::SendToClients(const sf::Packet& packet, const PlayerId skip_pid)
void NetPlayServer::SendToClients(const sf::Packet& packet, const PlayerId skip_pid,
const u8 channel_id)
{
for (auto& p : m_players)
{
if (p.second.pid && p.second.pid != skip_pid)
{
Send(p.second.socket, packet);
Send(p.second.socket, packet, channel_id);
}
}
}
void NetPlayServer::Send(ENetPeer* socket, const sf::Packet& packet)
void NetPlayServer::Send(ENetPeer* socket, const sf::Packet& packet, const u8 channel_id)
{
ENetPacket* epac =
enet_packet_create(packet.getData(), packet.getDataSize(), ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(socket, 0, epac);
enet_peer_send(socket, channel_id, epac);
}
void NetPlayServer::KickPlayer(PlayerId player)
@ -1714,4 +1785,110 @@ std::vector<std::pair<std::string, std::string>> NetPlayServer::GetInterfaceList
result.emplace_back(std::make_pair("!local!", "127.0.0.1"));
return result;
}
// called from ---Chunked Data--- thread
void NetPlayServer::ChunkedDataThreadFunc()
{
while (m_do_loop)
{
m_chunked_data_event.Wait();
while (!m_chunked_data_queue.Empty())
{
if (!m_do_loop)
return;
auto& e = m_chunked_data_queue.Front();
const u32 id = m_next_chunked_data_id++;
m_chunked_data_complete_count[id] = 0;
size_t player_count;
{
std::vector<int> players;
if (e.target_mode == TargetMode::Only)
{
players.push_back(e.target_pid);
}
else
{
for (auto& pl : m_players)
{
if (pl.second.pid != e.target_pid)
players.push_back(pl.second.pid);
}
}
player_count = players.size();
sf::Packet pac;
pac << static_cast<MessageId>(NP_MSG_CHUNKED_DATA_START);
pac << id << e.title << sf::Uint64{e.packet.getDataSize()};
ChunkedDataSend(std::move(pac), e.target_pid, e.target_mode);
if (e.target_mode == TargetMode::AllExcept && e.target_pid == 1)
m_dialog->ShowChunkedProgressDialog(e.title, e.packet.getDataSize(), players);
}
const bool enable_limit = Config::Get(Config::NETPLAY_ENABLE_CHUNKED_UPLOAD_LIMIT);
const float bytes_per_second =
(std::max(Config::Get(Config::NETPLAY_CHUNKED_UPLOAD_LIMIT), 1u) / 8.0f) * 1024.0f;
const std::chrono::duration<double> send_interval(CHUNKED_DATA_UNIT_SIZE / bytes_per_second);
size_t index = 0;
do
{
if (!m_do_loop)
return;
if (e.target_mode == TargetMode::Only)
{
if (m_players.find(e.target_pid) == m_players.end())
break;
}
auto start = std::chrono::steady_clock::now();
sf::Packet pac;
pac << static_cast<MessageId>(NP_MSG_CHUNKED_DATA_PAYLOAD);
pac << id;
size_t len = std::min(CHUNKED_DATA_UNIT_SIZE, e.packet.getDataSize() - index);
pac.append(static_cast<const u8*>(e.packet.getData()) + index, len);
ChunkedDataSend(std::move(pac), e.target_pid, e.target_mode);
index += CHUNKED_DATA_UNIT_SIZE;
if (enable_limit)
{
std::chrono::duration<double> delta = std::chrono::steady_clock::now() - start;
std::this_thread::sleep_for(send_interval - delta);
}
} while (index < e.packet.getDataSize());
{
sf::Packet pac;
pac << static_cast<MessageId>(NP_MSG_CHUNKED_DATA_END);
pac << id;
ChunkedDataSend(std::move(pac), e.target_pid, e.target_mode);
}
while (m_chunked_data_complete_count[id] < player_count && m_do_loop)
m_chunked_data_complete_event.Wait();
m_chunked_data_complete_count.erase(m_chunked_data_complete_count.find(id));
m_dialog->HideChunkedProgressDialog();
m_chunked_data_queue.Pop();
}
}
}
// called from ---Chunked Data--- thread
void NetPlayServer::ChunkedDataSend(sf::Packet&& packet, const PlayerId pid,
const TargetMode target_mode)
{
if (target_mode == TargetMode::Only)
{
SendAsync(std::move(packet), pid, CHUNKED_DATA_CHANNEL);
}
else
{
SendAsyncToClients(std::move(packet), pid, CHUNKED_DATA_CHANNEL);
}
}
} // namespace NetPlay

View File

@ -13,6 +13,8 @@
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include "Common/Event.h"
#include "Common/QoSSession.h"
#include "Common/SPSCQueue.h"
#include "Common/Timer.h"
@ -29,7 +31,12 @@ class NetPlayServer : public TraversalClientClient
{
public:
void ThreadFunc();
void SendAsyncToClients(sf::Packet&& packet);
void SendAsync(sf::Packet&& packet, PlayerId pid, u8 channel_id = DEFAULT_CHANNEL);
void SendAsyncToClients(sf::Packet&& packet, PlayerId skip_pid = 0,
u8 channel_id = DEFAULT_CHANNEL);
void SendChunked(sf::Packet&& packet, PlayerId pid, const std::string& title = "");
void SendChunkedToClients(sf::Packet&& packet, PlayerId skip_pid = 0,
const std::string& title = "");
NetPlayServer(u16 port, bool forward_port, const NetTraversalConfig& traversal_config);
~NetPlayServer();
@ -84,6 +91,28 @@ private:
bool IsHost() const { return pid == 1; }
};
enum class TargetMode
{
Only,
AllExcept
};
struct AsyncQueueEntry
{
sf::Packet packet;
PlayerId target_pid;
TargetMode target_mode;
u8 channel_id;
};
struct ChunkedDataQueueEntry
{
sf::Packet packet;
PlayerId target_pid;
TargetMode target_mode;
std::string title;
};
bool SyncSaveData();
bool SyncCodes();
void CheckSyncAndStartGame();
@ -93,8 +122,9 @@ private:
u64 GetInitialNetPlayRTC() const;
void SendToClients(const sf::Packet& packet, const PlayerId skip_pid = 0);
void Send(ENetPeer* socket, const sf::Packet& packet);
void SendToClients(const sf::Packet& packet, PlayerId skip_pid = 0,
u8 channel_id = DEFAULT_CHANNEL);
void Send(ENetPeer* socket, const sf::Packet& packet, u8 channel_id = DEFAULT_CHANNEL);
unsigned int OnConnect(ENetPeer* socket);
unsigned int OnDisconnect(const Client& player);
unsigned int OnData(sf::Packet& packet, Client& player);
@ -105,6 +135,8 @@ private:
void UpdatePadMapping();
void UpdateWiimoteMapping();
std::vector<std::pair<std::string, std::string>> GetInterfaceListInternal() const;
void ChunkedDataThreadFunc();
void ChunkedDataSend(sf::Packet&& packet, PlayerId pid, const TargetMode target_mode);
NetSettings m_settings;
@ -138,11 +170,19 @@ private:
// lock order
std::recursive_mutex players;
std::recursive_mutex async_queue_write;
std::recursive_mutex chunked_data_queue_write;
} m_crit;
Common::SPSCQueue<AsyncQueueEntry, false> m_async_queue;
Common::SPSCQueue<ChunkedDataQueueEntry, false> m_chunked_data_queue;
std::string m_selected_game;
std::thread m_thread;
Common::SPSCQueue<sf::Packet, false> m_async_queue;
Common::Event m_chunked_data_event;
Common::Event m_chunked_data_complete_event;
std::thread m_chunked_data_thread;
u32 m_next_chunked_data_id;
std::unordered_map<u32, unsigned int> m_chunked_data_complete_count;
ENetHost* m_server = nullptr;
TraversalClient* m_traversal_client = nullptr;

View File

@ -93,6 +93,7 @@ add_executable(dolphin-emu
GameList/ListProxyModel.cpp
GCMemcardManager.cpp
QtUtils/BlockUserInputFilter.cpp
NetPlay/ChunkedProgressDialog.cpp
NetPlay/GameListDialog.cpp
NetPlay/MD5Dialog.cpp
NetPlay/NetPlayDialog.cpp

View File

@ -141,6 +141,7 @@
<QtMoc Include="Settings\WiiPane.h" />
<QtMoc Include="MainWindow.h" />
<QtMoc Include="MenuBar.h" />
<QtMoc Include="NetPlay\ChunkedProgressDialog.h" />
<QtMoc Include="NetPlay\GameListDialog.h" />
<QtMoc Include="NetPlay\MD5Dialog.h" />
<QtMoc Include="NetPlay\NetPlayDialog.h" />
@ -174,6 +175,7 @@
<ClCompile Include="$(QtMocOutPrefix)CheatCodeEditor.cpp" />
<ClCompile Include="$(QtMocOutPrefix)CheatWarningWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)CheatsManager.cpp" />
<ClCompile Include="$(QtMocOutPrefix)ChunkedProgressDialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)CodeViewWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)CodeWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)ControllersWindow.cpp" />
@ -350,6 +352,7 @@
<ClCompile Include="Main.cpp" />
<ClCompile Include="MainWindow.cpp" />
<ClCompile Include="MenuBar.cpp" />
<ClCompile Include="NetPlay\ChunkedProgressDialog.cpp" />
<ClCompile Include="NetPlay\GameListDialog.cpp" />
<ClCompile Include="NetPlay\MD5Dialog.cpp" />
<ClCompile Include="NetPlay\NetPlayDialog.cpp" />

View File

@ -0,0 +1,123 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt/NetPlay/ChunkedProgressDialog.h"
#include <algorithm>
#include <cmath>
#include <functional>
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QLabel>
#include <QProgressBar>
#include <QPushButton>
#include <QVBoxLayout>
#include "Common/StringUtil.h"
#include "Core/NetPlayClient.h"
#include "Core/NetPlayServer.h"
#include "DolphinQt/Settings.h"
static QString GetPlayerNameFromPID(int pid)
{
QString player_name = QObject::tr("Invalid Player ID");
auto client = Settings::Instance().GetNetPlayClient();
if (!client)
return player_name;
for (const auto* player : client->GetPlayers())
{
if (player->pid == pid)
{
player_name = QString::fromStdString(player->name);
break;
}
}
return player_name;
}
ChunkedProgressDialog::ChunkedProgressDialog(QWidget* parent) : QDialog(parent)
{
CreateWidgets();
ConnectWidgets();
setWindowTitle(tr("Data Transfer"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
}
void ChunkedProgressDialog::CreateWidgets()
{
m_main_layout = new QVBoxLayout;
m_progress_box = new QGroupBox;
m_progress_layout = new QVBoxLayout;
m_progress_box->setLayout(m_progress_layout);
m_main_layout->addWidget(m_progress_box);
setLayout(m_main_layout);
}
void ChunkedProgressDialog::ConnectWidgets()
{
}
void ChunkedProgressDialog::show(const QString& title, const u64 data_size,
const std::vector<int>& players)
{
m_progress_box->setTitle(title);
m_data_size = data_size;
for (auto& pair : m_progress_bars)
{
m_progress_layout->removeWidget(pair.second);
pair.second->deleteLater();
}
for (auto& pair : m_status_labels)
{
m_progress_layout->removeWidget(pair.second);
pair.second->deleteLater();
}
m_progress_bars.clear();
m_status_labels.clear();
auto client = Settings::Instance().GetNetPlayClient();
if (!client)
return;
for (const auto* player : client->GetPlayers())
{
if (std::find(players.begin(), players.end(), player->pid) == players.end())
continue;
m_progress_bars[player->pid] = new QProgressBar;
m_status_labels[player->pid] = new QLabel;
m_progress_layout->addWidget(m_progress_bars[player->pid]);
m_progress_layout->addWidget(m_status_labels[player->pid]);
}
QDialog::show();
}
void ChunkedProgressDialog::SetProgress(const int pid, const u64 progress)
{
QString player_name = GetPlayerNameFromPID(pid);
if (!m_status_labels.count(pid))
return;
const float acquired = progress / 1024.0f / 1024.0f;
const float total = m_data_size / 1024.0f / 1024.0f;
const int prog = std::lround((static_cast<float>(progress) / m_data_size) * 100.0f);
m_status_labels[pid]->setText(tr("%1[%2]: %3/%4 MiB")
.arg(player_name, QString::number(pid),
QString::fromStdString(StringFromFormat("%.2f", acquired)),
QString::fromStdString(StringFromFormat("%.2f", total))));
m_progress_bars[pid]->setValue(prog);
}

View File

@ -0,0 +1,41 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <map>
#include <string>
#include <vector>
#include <QDialog>
#include "Common/CommonTypes.h"
class QGroupBox;
class QLabel;
class QProgressBar;
class QVBoxLayout;
class QWidget;
class ChunkedProgressDialog : public QDialog
{
Q_OBJECT
public:
explicit ChunkedProgressDialog(QWidget* parent);
void show(const QString& title, u64 data_size, const std::vector<int>& players);
void SetProgress(int pid, u64 progress);
private:
void CreateWidgets();
void ConnectWidgets();
std::map<int, QProgressBar*> m_progress_bars;
std::map<int, QLabel*> m_status_labels;
u64 m_data_size = 0;
QGroupBox* m_progress_box;
QVBoxLayout* m_progress_layout;
QVBoxLayout* m_main_layout;
};

View File

@ -43,6 +43,7 @@
#include "Core/NetPlayServer.h"
#include "DolphinQt/GameList/GameListModel.h"
#include "DolphinQt/NetPlay/ChunkedProgressDialog.h"
#include "DolphinQt/NetPlay/GameListDialog.h"
#include "DolphinQt/NetPlay/MD5Dialog.h"
#include "DolphinQt/NetPlay/PadMappingDialog.h"
@ -68,6 +69,7 @@ NetPlayDialog::NetPlayDialog(QWidget* parent)
m_pad_mapping = new PadMappingDialog(this);
m_md5_dialog = new MD5Dialog(this);
m_chunked_progress_dialog = new ChunkedProgressDialog(this);
ResetExternalIP();
CreateChatLayout();
@ -1046,3 +1048,27 @@ void NetPlayDialog::AbortMD5()
m_md5_button->setEnabled(true);
});
}
void NetPlayDialog::ShowChunkedProgressDialog(const std::string& title, const u64 data_size,
const std::vector<int>& players)
{
QueueOnObject(this, [this, title, data_size, players] {
if (m_chunked_progress_dialog->isVisible())
m_chunked_progress_dialog->close();
m_chunked_progress_dialog->show(QString::fromStdString(title), data_size, players);
});
}
void NetPlayDialog::HideChunkedProgressDialog()
{
QueueOnObject(this, [this] { m_chunked_progress_dialog->close(); });
}
void NetPlayDialog::SetChunkedProgress(const int pid, const u64 progress)
{
QueueOnObject(this, [this, pid, progress] {
if (m_chunked_progress_dialog->isVisible())
m_chunked_progress_dialog->SetProgress(pid, progress);
});
}

View File

@ -10,6 +10,7 @@
#include "Core/NetPlayClient.h"
#include "VideoCommon/OnScreenDisplay.h"
class ChunkedProgressDialog;
class MD5Dialog;
class GameListModel;
class PadMappingDialog;
@ -67,6 +68,11 @@ public:
void SetMD5Progress(int pid, int progress) override;
void SetMD5Result(int pid, const std::string& result) override;
void AbortMD5() override;
void ShowChunkedProgressDialog(const std::string& title, u64 data_size,
const std::vector<int>& players) override;
void HideChunkedProgressDialog() override;
void SetChunkedProgress(int pid, u64 progress) override;
signals:
void Boot(const QString& filename);
void Stop();
@ -122,6 +128,7 @@ private:
QGridLayout* m_main_layout;
MD5Dialog* m_md5_dialog;
ChunkedProgressDialog* m_chunked_progress_dialog;
PadMappingDialog* m_pad_mapping;
std::string m_current_game;
Common::Lazy<std::string> m_external_ip_address;

View File

@ -35,6 +35,8 @@ NetPlaySetupDialog::NetPlaySetupDialog(QWidget* parent)
int connect_port = Config::Get(Config::NETPLAY_CONNECT_PORT);
int host_port = Config::Get(Config::NETPLAY_HOST_PORT);
int host_listen_port = Config::Get(Config::NETPLAY_LISTEN_PORT);
bool enable_chunked_upload_limit = Config::Get(Config::NETPLAY_ENABLE_CHUNKED_UPLOAD_LIMIT);
u32 chunked_upload_limit = Config::Get(Config::NETPLAY_CHUNKED_UPLOAD_LIMIT);
#ifdef USE_UPNP
bool use_upnp = Config::Get(Config::NETPLAY_USE_UPNP);
@ -50,6 +52,10 @@ NetPlaySetupDialog::NetPlaySetupDialog(QWidget* parent)
m_host_force_port_box->setValue(host_listen_port);
m_host_force_port_box->setEnabled(false);
m_host_chunked_upload_limit_check->setChecked(enable_chunked_upload_limit);
m_host_chunked_upload_limit_box->setValue(chunked_upload_limit);
m_host_chunked_upload_limit_box->setEnabled(enable_chunked_upload_limit);
OnConnectionTypeChanged(m_connection_type->currentIndex());
ConnectWidgets();
@ -101,6 +107,8 @@ void NetPlaySetupDialog::CreateMainLayout()
m_host_port_box = new QSpinBox;
m_host_force_port_check = new QCheckBox(tr("Force Listen Port:"));
m_host_force_port_box = new QSpinBox;
m_host_chunked_upload_limit_check = new QCheckBox(tr("Limit Chunked Upload Speed:"));
m_host_chunked_upload_limit_box = new QSpinBox;
#ifdef USE_UPNP
m_host_upnp = new QCheckBox(tr("Forward port (UPnP)"));
@ -110,6 +118,12 @@ void NetPlaySetupDialog::CreateMainLayout()
m_host_port_box->setMaximum(65535);
m_host_force_port_box->setMaximum(65535);
m_host_chunked_upload_limit_box->setRange(1, 1000000);
m_host_chunked_upload_limit_box->setSingleStep(100);
m_host_chunked_upload_limit_box->setSuffix(QStringLiteral(" kbps"));
m_host_chunked_upload_limit_check->setToolTip(tr(
"This will limit the speed of chunked uploading per client, which is used for save sync."));
host_layout->addWidget(m_host_port_label, 0, 0);
host_layout->addWidget(m_host_port_box, 0, 1);
@ -119,7 +133,9 @@ void NetPlaySetupDialog::CreateMainLayout()
host_layout->addWidget(m_host_games, 1, 0, 1, -1);
host_layout->addWidget(m_host_force_port_check, 2, 0);
host_layout->addWidget(m_host_force_port_box, 2, 1, Qt::AlignLeft);
host_layout->addWidget(m_host_button, 2, 2, Qt::AlignRight);
host_layout->addWidget(m_host_chunked_upload_limit_check, 3, 0);
host_layout->addWidget(m_host_chunked_upload_limit_box, 3, 1, Qt::AlignLeft);
host_layout->addWidget(m_host_button, 2, 2, 2, 1, Qt::AlignRight);
host_widget->setLayout(host_layout);
@ -163,7 +179,14 @@ void NetPlaySetupDialog::ConnectWidgets()
connect(m_host_games, &QListWidget::itemDoubleClicked, this, &NetPlaySetupDialog::accept);
connect(m_host_force_port_check, &QCheckBox::toggled,
[this](int value) { m_host_force_port_box->setEnabled(value); });
[this](bool value) { m_host_force_port_box->setEnabled(value); });
connect(m_host_chunked_upload_limit_check, &QCheckBox::toggled, this, [this](bool value) {
m_host_chunked_upload_limit_box->setEnabled(value);
SaveSettings();
});
connect(m_host_chunked_upload_limit_box,
static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
&NetPlaySetupDialog::SaveSettings);
#ifdef USE_UPNP
connect(m_host_upnp, &QCheckBox::stateChanged, this, &NetPlaySetupDialog::SaveSettings);
#endif
@ -191,6 +214,11 @@ void NetPlaySetupDialog::SaveSettings()
if (m_host_force_port_check->isChecked())
Config::SetBaseOrCurrent(Config::NETPLAY_LISTEN_PORT,
static_cast<u16>(m_host_force_port_box->value()));
Config::SetBaseOrCurrent(Config::NETPLAY_ENABLE_CHUNKED_UPLOAD_LIMIT,
m_host_chunked_upload_limit_check->isChecked());
Config::SetBaseOrCurrent(Config::NETPLAY_CHUNKED_UPLOAD_LIMIT,
m_host_chunked_upload_limit_box->value());
}
void NetPlaySetupDialog::OnConnectionTypeChanged(int index)

View File

@ -63,6 +63,8 @@ private:
QPushButton* m_host_button;
QCheckBox* m_host_force_port_check;
QSpinBox* m_host_force_port_box;
QCheckBox* m_host_chunked_upload_limit_check;
QSpinBox* m_host_chunked_upload_limit_box;
#ifdef USE_UPNP
QCheckBox* m_host_upnp;