Path: blob/main/RSDKv4/Networking.cpp
817 views
#include "RetroEngine.hpp"1#if RETRO_USE_NETWORKING23#include <cstdlib>4#include <deque>5#include <iostream>6#include <thread>7#include <chrono>8#if RETRO_PLATFORM == RETRO_ANDROID9#define ASIO_NO_TYPEID10#endif11#include <asio.hpp>1213char networkHost[64];14char networkGame[7] = "SONIC2";15int networkPort = 50;16int dcError = 0;17float lastPing = 0;1819bool waitingForPing = false;2021uint64_t lastTime = 0;2223using asio::ip::udp;2425typedef std::deque<ServerPacket> DataQueue;2627class NetworkSession28{29public:30bool running = false;31NetworkSession(asio::io_context &io_context, const udp::endpoint &endpoint)32: io_context(io_context), socket(io_context), endpoint(endpoint), timer(io_context)33{34socket.open(udp::v4());35}3637~NetworkSession() {}3839void write(const ServerPacket &msg, bool repeat = false)40{41ServerPacket sent(msg);42sent.player = code;43sent.room = room;44write_msgs_.push_back(sent);45if (repeat) {46this->repeat = sent;47}48}4950void start()51{52repeat.header = 0x80;53running = true;54ServerPacket sent;55sent.header = CL_REQUEST_CODE;56sent.room = 0x1F2F3F4F;57write_msgs_.push_back(sent);58}5960void close()61{62if (running)63running = false;64if (socket.is_open())65socket.close();66}6768NetworkSession &operator=(const NetworkSession &s)69{70close();71memcpy(this, &s, sizeof(NetworkSession));72return *this;73}7475uint code = 0;76uint partner = 0;77uint room = 0;7879bool awaitingReceive = false;80bool verifyReceived = false;8182bool retried = true;83uint retries = 0;8485ServerPacket repeat;8687void run()88{89// do we write anything90while (!write_msgs_.empty()) {91ServerPacket *send = &write_msgs_.front();92StrCopy(send->game, networkGame);93socket.send_to(asio::buffer(send, sizeof(ServerPacket)), endpoint);94write_msgs_.pop_front();95if (send->header == 0xFF)96session->running = false;97}98if (!awaitingReceive)99do_read();100if (repeat.header != 0x80 && retried) {101retried = false;102timer.expires_from_now(asio::chrono::seconds(1));103timer.async_wait([&](const asio::error_code &) {104retried = true;105repeat.room = room;106StrCopy(repeat.game, networkGame);107if (retries++ == 10)108socket.send_to(asio::buffer(&repeat, sizeof(ServerPacket)), endpoint);109});110}111else if (repeat.header == 0x80)112retries = 0;113if (retries > 10) {114switch (repeat.header) {115case 0x01: {116dcError = 4;117vsPlaying = false;118session->running = false;119break;120}121case 0x00: {122if (!room) {123dcError = 4;124vsPlaying = false;125session->running = false;126}127break;128}129}130}131132io_context.poll();133io_context.restart();134}135136void leave()137{138ServerPacket send;139send.header = CL_LEAVE;140vsPlaying = false;141write(send);142}143144private:145void do_read()146{147if (awaitingReceive)148return;149awaitingReceive = true;150socket.async_receive(asio::buffer(&read_msg_, sizeof(ServerPacket)), [&](const asio::error_code &ec, size_t bytes) {151awaitingReceive = false; // async, not threaded. this is safe152if (ec || !session->running)153return;154// it's ok to use preformace counter; we're in a different thread and slowdown is safe155lastPing = ((SDL_GetPerformanceCounter() - lastTime) * 1000.0 / SDL_GetPerformanceFrequency());156lastTime = SDL_GetPerformanceCounter();157waitingForPing = false;158if (!code) {159if (read_msg_.header == SV_CODES && read_msg_.player) {160code = read_msg_.player;161}162return;163}164165switch (read_msg_.header) {166case SV_CODES: {167if (vsPlaying)168return;169room = read_msg_.room;170if (read_msg_.data.multiData.type > 2) {171dcError = 3;172leave();173return;174}175176if (read_msg_.data.multiData.type - 1) {177partner = *read_msg_.data.multiData.data;178Receive2PVSMatchCode(room);179repeat.header = 0x80;180return;181}182break;183}184case SV_NEW_PLAYER: {185if (partner)186return;187repeat.header = 0x80;188vsPlayerID = 0;189partner = read_msg_.player;190Receive2PVSMatchCode(room);191return;192}193case SV_DATA_VERIFIED:194// fallthrough195case SV_DATA: {196Receive2PVSData(&read_msg_.data.multiData);197return;198}199case SV_RECEIVED: {200if (repeat.header == CL_DATA_VERIFIED)201repeat.header = CL_QUERY_VERIFICATION;202return;203}204case SV_VERIFY_CLEAR: {205repeat.header = 0x80;206return;207}208case SV_NO_ROOM: {209leave();210dcError = 5;211return;212}213case SV_LEAVE: {214if (read_msg_.player != partner)215return;216leave();217dcError = 1;218return;219}220}221});222}223224asio::io_context &io_context;225asio::steady_timer timer;226udp::socket socket;227udp::endpoint endpoint;228ServerPacket read_msg_;229DataQueue write_msgs_;230231bool writing = false;232233int attempts;234};235236std::shared_ptr<NetworkSession> session;237asio::io_context io_context;238std::thread loopThread;239240void InitNetwork()241{242try {243udp::resolver resolver(io_context);244asio::error_code ec;245auto endpoint = *resolver.resolve(udp::v4(), networkHost, std::to_string(networkPort), ec).begin();246session.reset();247auto newsession = std::make_shared<NetworkSession>(io_context, endpoint);248session.swap(newsession);249} catch (std::exception &e) {250Engine.onlineActive = false;251PrintLog("Failed to initialize networking: %s", e.what());252}253}254255void networkLoop()256{257try {258session->start();259while (session->running) session->run();260session->close();261} catch (std::exception &e) {262std::cerr << "Exception: " << e.what() << "\n";263}264}265266void RunNetwork()267{268if (loopThread.joinable()) {269DisconnectNetwork();270InitNetwork();271}272loopThread = std::thread(networkLoop);273}274275void SendData(bool verify)276{277ServerPacket send;278send.header = CL_DATA + verify;279send.data.multiData = multiplayerDataOUT;280session->write(send, verify);281}282283void DisconnectNetwork(bool finalClose)284{285if (session->running)286session->leave();287if (loopThread.joinable())288loopThread.join();289290if (finalClose) {291if (session)292session.reset();293}294}295296void SendServerPacket(ServerPacket &send, bool repeat) { session->write(send, repeat); }297int GetRoomCode() { return session->room; }298void SetRoomCode(int code) { session->room = code; }299300void SetNetworkGameName(int *a1, const char *name) { StrCopy(networkGame, name); }301#endif302303