/ src / network / announce_multiplayer_session.cpp
announce_multiplayer_session.cpp
  1  // Copyright 2017 Citra Emulator Project
  2  // Licensed under GPLv2 or any later version
  3  // Refer to the license.txt file included.
  4  
  5  #include <chrono>
  6  #include <future>
  7  #include <vector>
  8  #include "announce_multiplayer_session.h"
  9  #include "common/announce_multiplayer_room.h"
 10  #include "common/assert.h"
 11  #include "network/network.h"
 12  #include "network/network_settings.h"
 13  
 14  #ifdef ENABLE_WEB_SERVICE
 15  #include "web_service/announce_room_json.h"
 16  #endif
 17  
 18  namespace Network {
 19  
 20  // Time between room is announced to web_service
 21  static constexpr std::chrono::seconds announce_time_interval(15);
 22  
 23  AnnounceMultiplayerSession::AnnounceMultiplayerSession() {
 24  #ifdef ENABLE_WEB_SERVICE
 25      backend = std::make_unique<WebService::RoomJson>(NetSettings::values.web_api_url,
 26                                                       NetSettings::values.citra_username,
 27                                                       NetSettings::values.citra_token);
 28  #else
 29      backend = std::make_unique<AnnounceMultiplayerRoom::NullBackend>();
 30  #endif
 31  }
 32  
 33  Common::WebResult AnnounceMultiplayerSession::Register() {
 34      std::shared_ptr<Network::Room> room = Network::GetRoom().lock();
 35      if (!room) {
 36          return Common::WebResult{Common::WebResult::Code::LibError, "Network is not initialized"};
 37      }
 38      if (room->GetState() != Network::Room::State::Open) {
 39          return Common::WebResult{Common::WebResult::Code::LibError, "Room is not open"};
 40      }
 41      UpdateBackendData(room);
 42      Common::WebResult result = backend->Register();
 43      if (result.result_code != Common::WebResult::Code::Success) {
 44          return result;
 45      }
 46      LOG_INFO(WebService, "Room has been registered");
 47      room->SetVerifyUID(result.returned_data);
 48      registered = true;
 49      return Common::WebResult{Common::WebResult::Code::Success};
 50  }
 51  
 52  void AnnounceMultiplayerSession::Start() {
 53      if (announce_multiplayer_thread) {
 54          Stop();
 55      }
 56      shutdown_event.Reset();
 57      announce_multiplayer_thread =
 58          std::make_unique<std::thread>(&AnnounceMultiplayerSession::AnnounceMultiplayerLoop, this);
 59  }
 60  
 61  void AnnounceMultiplayerSession::Stop() {
 62      if (announce_multiplayer_thread) {
 63          shutdown_event.Set();
 64          announce_multiplayer_thread->join();
 65          announce_multiplayer_thread.reset();
 66          backend->Delete();
 67          registered = false;
 68      }
 69  }
 70  
 71  AnnounceMultiplayerSession::CallbackHandle AnnounceMultiplayerSession::BindErrorCallback(
 72      std::function<void(const Common::WebResult&)> function) {
 73      std::lock_guard lock(callback_mutex);
 74      auto handle = std::make_shared<std::function<void(const Common::WebResult&)>>(function);
 75      error_callbacks.insert(handle);
 76      return handle;
 77  }
 78  
 79  void AnnounceMultiplayerSession::UnbindErrorCallback(CallbackHandle handle) {
 80      std::lock_guard lock(callback_mutex);
 81      error_callbacks.erase(handle);
 82  }
 83  
 84  AnnounceMultiplayerSession::~AnnounceMultiplayerSession() {
 85      Stop();
 86  }
 87  
 88  void AnnounceMultiplayerSession::UpdateBackendData(std::shared_ptr<Network::Room> room) {
 89      Network::RoomInformation room_information = room->GetRoomInformation();
 90      std::vector<Network::Room::Member> memberlist = room->GetRoomMemberList();
 91      backend->SetRoomInformation(
 92          room_information.name, room_information.description, room_information.port,
 93          room_information.member_slots, Network::network_version, room->HasPassword(),
 94          room_information.preferred_game, room_information.preferred_game_id);
 95      backend->ClearPlayers();
 96      for (const auto& member : memberlist) {
 97          backend->AddPlayer(member.username, member.nickname, member.avatar_url, member.mac_address,
 98                             member.game_info.id, member.game_info.name);
 99      }
100  }
101  
102  void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() {
103      // Invokes all current bound error callbacks.
104      const auto ErrorCallback = [this](Common::WebResult result) {
105          std::lock_guard<std::mutex> lock(callback_mutex);
106          for (auto callback : error_callbacks) {
107              (*callback)(result);
108          }
109      };
110  
111      if (!registered) {
112          Common::WebResult result = Register();
113          if (result.result_code != Common::WebResult::Code::Success) {
114              ErrorCallback(result);
115              return;
116          }
117      }
118  
119      auto update_time = std::chrono::steady_clock::now();
120      std::future<Common::WebResult> future;
121      while (!shutdown_event.WaitUntil(update_time)) {
122          update_time += announce_time_interval;
123          std::shared_ptr<Network::Room> room = Network::GetRoom().lock();
124          if (!room) {
125              break;
126          }
127          if (room->GetState() != Network::Room::State::Open) {
128              break;
129          }
130          UpdateBackendData(room);
131          Common::WebResult result = backend->Update();
132          if (result.result_code != Common::WebResult::Code::Success) {
133              ErrorCallback(result);
134          }
135          if (result.result_string == "404") {
136              registered = false;
137              // Needs to register the room again
138              Common::WebResult new_result = Register();
139              if (new_result.result_code != Common::WebResult::Code::Success) {
140                  ErrorCallback(new_result);
141              }
142          }
143      }
144  }
145  
146  AnnounceMultiplayerRoom::RoomList AnnounceMultiplayerSession::GetRoomList() {
147      return backend->GetRoomList();
148  }
149  
150  bool AnnounceMultiplayerSession::IsRunning() const {
151      return announce_multiplayer_thread != nullptr;
152  }
153  
154  void AnnounceMultiplayerSession::UpdateCredentials() {
155      ASSERT_MSG(!IsRunning(), "Credentials can only be updated when session is not running");
156  
157  #ifdef ENABLE_WEB_SERVICE
158      backend = std::make_unique<WebService::RoomJson>(NetSettings::values.web_api_url,
159                                                       NetSettings::values.citra_username,
160                                                       NetSettings::values.citra_token);
161  #endif
162  }
163  
164  } // namespace Network