diff --git a/src/frontend/qt_sdl/Input.h b/src/frontend/qt_sdl/Input.h index 95c300e5..9a242318 100644 --- a/src/frontend/qt_sdl/Input.h +++ b/src/frontend/qt_sdl/Input.h @@ -19,6 +19,9 @@ #ifndef INPUT_H #define INPUT_H +#include +#include + #include "types.h" namespace Input diff --git a/src/frontend/qt_sdl/Netplay.cpp b/src/frontend/qt_sdl/Netplay.cpp index 0096e505..d42827bf 100644 --- a/src/frontend/qt_sdl/Netplay.cpp +++ b/src/frontend/qt_sdl/Netplay.cpp @@ -19,10 +19,17 @@ #include #include #include +#include #include +#include "NDS.h" +#include "main.h" #include "Netplay.h" +#include "Input.h" + + +extern EmuThread* emuThread; namespace Netplay @@ -34,6 +41,14 @@ bool IsHost; ENetHost* Host; ENetPeer* Peer; +struct InputFrame +{ + u32 FrameNum; + u32 KeyMask; +}; + +std::queue InputQueue; + bool Init() { @@ -119,26 +134,158 @@ void StartClient() } +void StartGame() +{ + if (!IsHost) + { + printf("?????\n"); + return; + } + + // tell remote peers to start game + u8 cmd[1] = {0x01}; + ENetPacket* pkt = enet_packet_create(cmd, sizeof(cmd), ENET_PACKET_FLAG_RELIABLE); + enet_host_broadcast(Host, 0, pkt); + + // start game locally + NDS::Start(); + emuThread->emuRun(); +} + + void ProcessFrame() { + bool block = false; + if (emuThread->emuIsRunning()) + { + if (IsHost) + { + // TODO: prevent the clients from running too far behind + } + else + { + // block if we ran out of input frames + // TODO: in this situation, make sure we do receive an input frame + // or if we don't after X time, handle it gracefully + + if (InputQueue.empty()) + block = true; + } + } + block=false; + ENetEvent event; - while (enet_host_service(Host, &event, 0) > 0) + while (enet_host_service(Host, &event, block ? 5000 : 0) > 0) { switch (event.type) { case ENET_EVENT_TYPE_CONNECT: printf("client connected %08X %d\n", event.peer->address.host, event.peer->address.port); + Peer = event.peer; break; case ENET_EVENT_TYPE_DISCONNECT: printf("client disconnected %08X %d\n", event.peer->address.host, event.peer->address.port); + Peer = nullptr; break; case ENET_EVENT_TYPE_RECEIVE: - printf("received shit\n"); + { + if (event.packet->dataLength < 1) + { + printf("?????\n"); + break; + } + + u8* data = (u8*)event.packet->data; + switch (data[0]) + { + case 0x01: // start game + NDS::Start(); + emuThread->emuRun(); + break; + + case 0x02: // input frame + { + if (event.packet->dataLength != (sizeof(InputFrame)+1)) + break; + + InputFrame frame; + memcpy(&frame, &data[1], sizeof(InputFrame)); + InputQueue.push(frame); + break; + } + } + } break; } } } +void ProcessInput() +{ + // netplay input processing + // + // N = current frame # + // L = amount of lag frames + // + // host side: + // we take the current input (normally meant for frame N) + // and delay it to frame N+L + // + // client side: + // we receive input from the host + // apply each input to the frame it's assigned to + // before running a frame, we need to wait to have received input for it + // TODO: alert host if we are running too far behind + + if (IsHost) + { + u32 lag = 4; // TODO: make configurable!! + + InputFrame frame; + frame.FrameNum = NDS::NumFrames + lag; + frame.KeyMask = Input::InputMask; + // TODO: touchscreen input and other shit! + + InputQueue.push(frame); + + u8 cmd[1+sizeof(InputFrame)]; + cmd[0] = 0x02; + memcpy(&cmd[1], &frame, sizeof(InputFrame)); + ENetPacket* pkt = enet_packet_create(cmd, sizeof(cmd), ENET_PACKET_FLAG_RELIABLE); + enet_host_broadcast(Host, 0, pkt); + } + + if (InputQueue.empty()) + { + printf("Netplay: BAD! INPUT QUEUE EMPTY\n"); + return; + } + + InputFrame& frame = InputQueue.front(); + + if (frame.FrameNum < NDS::NumFrames) + { + printf("Netplay: BAD! LAGGING BEHIND\n"); + while (frame.FrameNum < NDS::NumFrames) + { + if (InputQueue.size() < 2) break; + InputQueue.pop(); + frame = InputQueue.front(); + } + } + + if (frame.FrameNum > NDS::NumFrames) + { + // frame in the future, ignore + return; + } + + // apply this input frame + printf("[%08d] INPUT=%08X (%08d) (backlog=%d)\n", NDS::NumFrames, frame.KeyMask, frame.FrameNum, InputQueue.size()); + NDS::SetKeyMask(frame.KeyMask); + InputQueue.pop(); +} + } diff --git a/src/frontend/qt_sdl/Netplay.h b/src/frontend/qt_sdl/Netplay.h index 7cef744a..cd4665d3 100644 --- a/src/frontend/qt_sdl/Netplay.h +++ b/src/frontend/qt_sdl/Netplay.h @@ -32,7 +32,10 @@ void DeInit(); void StartHost(); void StartClient(); +void StartGame(); + void ProcessFrame(); +void ProcessInput(); } diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 42db6788..caa3f0a4 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -472,7 +472,14 @@ void EmuThread::run() } // process input and hotkeys - NDS::SetKeyMask(Input::InputMask); + if (Netplay::Active) + { + Netplay::ProcessInput(); + } + else + { + NDS::SetKeyMask(Input::InputMask); + } if (Input::HotkeyPressed(HK_Lid)) { @@ -1576,6 +1583,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actMPStartClient = submenu->addAction("NETPLAY CLIENT"); connect(actMPStartClient, &QAction::triggered, this, &MainWindow::onMPStartClient); + + actMPTest = submenu->addAction("NETPLAY GO"); + connect(actMPTest, &QAction::triggered, this, &MainWindow::onMPTest); } } { @@ -2337,8 +2347,11 @@ void MainWindow::onOpenFile() recentFileList.prepend(filename); updateRecentFilesMenu(); - NDS::Start(); - emuThread->emuRun(); + if (!Netplay::Active) + { + NDS::Start(); + emuThread->emuRun(); + } updateCartInserted(false); } @@ -2434,8 +2447,11 @@ void MainWindow::onClickRecentFile() recentFileList.prepend(filename); updateRecentFilesMenu(); - NDS::Start(); - emuThread->emuRun(); + if (!Netplay::Active) + { + NDS::Start(); + emuThread->emuRun(); + } updateCartInserted(false); } @@ -2458,8 +2474,11 @@ void MainWindow::onBootFirmware() return; } - NDS::Start(); - emuThread->emuRun(); + if (!Netplay::Active) + { + NDS::Start(); + emuThread->emuRun(); + } } void MainWindow::onInsertCart() @@ -2844,6 +2863,12 @@ void MainWindow::onMPStartClient() Netplay::StartClient(); } +void MainWindow::onMPTest() +{ + // HAX + Netplay::StartGame(); +} + void MainWindow::onOpenEmuSettings() { emuThread->emuPause(); diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index 338af664..2d6ddae7 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -313,6 +313,7 @@ private slots: void onMPNewInstance(); void onMPStartHost(); void onMPStartClient(); + void onMPTest(); void onOpenEmuSettings(); void onEmuSettingsDialogFinished(int res); @@ -412,6 +413,7 @@ public: QAction* actMPNewInstance; QAction* actMPStartHost; QAction* actMPStartClient; + QAction* actMPTest; QAction* actEmuSettings; #ifdef __APPLE__