about summary refs log blame commit diff stats
path: root/src/ipc_state.cpp
blob: 18f318f7247d5b6cdfc8fa703ef5e8df0747fd3b (plain) (tree)





































































































































































































































                                                                                
#include "ipc_state.h"

#define _WEBSOCKETPP_CPP11_STRICT_

#include <fmt/core.h>

#include <chrono>
#include <memory>
#include <mutex>
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
#include <thread>
#include <tuple>
#include <wswrap.hpp>

#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<std::string> 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<std::tuple<int, int>> player_position;

  int backoff_amount = 0;

  // Thread state
  std::unique_ptr<wswrap::WS> ws;
  bool connected = false;

  void Start(TrackerFrame* frame) {
    tracker_frame = frame;

    std::thread([this]() { Thread(); }).detach();
  }

  std::optional<std::string> 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<std::tuple<int, int>> 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<wswrap::WS>(
          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<int, int>(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<std::string> 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<std::string> 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<std::tuple<int, int>> IPC_GetPlayerPosition() {
  return GetState().GetPlayerPosition();
}