#include "ipc_state.h" #define _WEBSOCKETPP_CPP11_STRICT_ #include #include #include #include #include #include #include #include #include #include #include "logger.h" #include "tracker_frame.h" namespace { constexpr const char* kIpcAddress = "ws://127.0.0.1:41253"; struct IPCState { std::mutex state_mutex; TrackerFrame* tracker_frame = nullptr; // Protected state std::optional status_message; bool slot_matches = false; std::string tracker_ap_server; std::string tracker_ap_user; std::string game_ap_server; std::string game_ap_user; std::optional> player_position; int backoff_amount = 0; // Thread state std::unique_ptr ws; bool connected = false; void Start(TrackerFrame* frame) { tracker_frame = frame; std::thread([this]() { Thread(); }).detach(); } std::optional GetStatusMessage() { std::lock_guard state_guard(state_mutex); return status_message; } void SetTrackerSlot(std::string server, std::string user) { std::lock_guard state_guard(state_mutex); tracker_ap_server = std::move(server); tracker_ap_user = std::move(user); CheckIfSlotMatches(); backoff_amount = 0; } bool IsConnected() { std::lock_guard state_guard(state_mutex); return slot_matches; } std::optional> GetPlayerPosition() { std::lock_guard state_guard(state_mutex); return player_position; } private: void Thread() { for (;;) { player_position = std::nullopt; TrackerLog("Looking for game over IPC..."); { std::lock_guard state_guard(state_mutex); backoff_amount = 0; } while (!TryConnect() || !connected) { int backoff_limit; { std::lock_guard state_guard(state_mutex); backoff_limit = (backoff_amount + 1) * 10; } for (int i = 0; i < backoff_limit && !connected; i++) { ws->poll(); // Back off std::this_thread::sleep_for(std::chrono::milliseconds(100)); } { std::lock_guard state_guard(state_mutex); backoff_amount = std::min(9, backoff_amount + 1); if (!connected) { TrackerLog(fmt::format("Retrying IPC in {} second(s)...", backoff_amount + 1)); } } } while (connected) { ws->poll(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } SetStatusMessage("Disconnected from game."); } } bool TryConnect() { try { ws = std::make_unique( kIpcAddress, [this]() { OnConnect(); }, [this]() { OnClose(); }, [this](const std::string& s) { OnMessage(s); }, [this](const std::string& s) { OnError(s); }); return true; } catch (const std::exception& ex) { ws.reset(); return false; } } void OnConnect() { connected = true; } void OnClose() { connected = false; { std::lock_guard state_guard(state_mutex); slot_matches = false; } } void OnMessage(const std::string& s) { TrackerLog(s); auto msg = nlohmann::json::parse(s); if (msg["cmd"] == "Connect") { std::lock_guard state_guard(state_mutex); game_ap_server = msg["slot"]["server"]; game_ap_user = msg["slot"]["player"]; CheckIfSlotMatches(); } else if (msg["cmd"] == "UpdatePosition") { std::lock_guard state_guard(state_mutex); player_position = std::make_tuple(msg["position"]["x"], msg["position"]["z"]); tracker_frame->RedrawPosition(); } } void OnError(const std::string& s) {} void CheckIfSlotMatches() { slot_matches = (tracker_ap_server == game_ap_server && tracker_ap_user == game_ap_user); if (slot_matches) { status_message = "Connected to game."; Sync(); } else if (tracker_ap_server.empty()) { status_message = std::nullopt; } else if (connected) { status_message = "Local game doesn't match AP slot."; } tracker_frame->UpdateStatusMessage(); } void SetStatusMessage(std::optional msg) { { std::lock_guard state_guard(state_mutex); status_message = msg; } tracker_frame->UpdateStatusMessage(); } void Sync() { nlohmann::json msg; msg["cmd"] = "Sync"; ws->send_text(msg.dump()); } }; IPCState& GetState() { static IPCState* instance = new IPCState(); return *instance; } } // namespace void IPC_Start(TrackerFrame* tracker_frame) { GetState().Start(tracker_frame); } std::optional IPC_GetStatusMessage() { return GetState().GetStatusMessage(); } void IPC_SetTrackerSlot(std::string server, std::string user) { GetState().SetTrackerSlot(server, user); } bool IPC_IsConnected() { return GetState().IsConnected(); } std::optional> IPC_GetPlayerPosition() { return GetState().GetPlayerPosition(); }