about summary refs log tree commit diff stats
path: root/src/ipc_state.cpp
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2024-12-18 13:36:52 -0500
committerStar Rauchenberger <fefferburbia@gmail.com>2024-12-18 13:36:52 -0500
commit2844eecb65501f7dafa4de15d7377bfb810e1158 (patch)
tree7b2530cba82e54655a74bdcd6e31d9724583e83b /src/ipc_state.cpp
parent4fb25ff5efe48ca8f594ce5b5d2839cb244018a9 (diff)
downloadlingo-ap-tracker-2844eecb65501f7dafa4de15d7377bfb810e1158.tar.gz
lingo-ap-tracker-2844eecb65501f7dafa4de15d7377bfb810e1158.tar.bz2
lingo-ap-tracker-2844eecb65501f7dafa4de15d7377bfb810e1158.zip
Make IPC opt-in and configurable
Diffstat (limited to 'src/ipc_state.cpp')
-rw-r--r--src/ipc_state.cpp164
1 files changed, 135 insertions, 29 deletions
diff --git a/src/ipc_state.cpp b/src/ipc_state.cpp index c0bdc9b..24d0115 100644 --- a/src/ipc_state.cpp +++ b/src/ipc_state.cpp
@@ -15,18 +15,21 @@
15#include <tuple> 15#include <tuple>
16#include <wswrap.hpp> 16#include <wswrap.hpp>
17 17
18#include "ap_state.h"
18#include "logger.h" 19#include "logger.h"
19#include "tracker_frame.h" 20#include "tracker_frame.h"
20 21
21namespace { 22namespace {
22 23
23constexpr const char* kIpcAddress = "ws://127.0.0.1:41253";
24
25struct IPCState { 24struct IPCState {
26 std::mutex state_mutex; 25 std::mutex state_mutex;
27 TrackerFrame* tracker_frame = nullptr; 26 TrackerFrame* tracker_frame = nullptr;
28 27
29 // Protected state 28 // Protected state
29 bool initialized = false;
30 std::string address;
31 bool should_disconnect = false;
32
30 std::optional<std::string> status_message; 33 std::optional<std::string> status_message;
31 34
32 bool slot_matches = false; 35 bool slot_matches = false;
@@ -38,16 +41,27 @@ struct IPCState {
38 std::optional<std::tuple<int, int>> player_position; 41 std::optional<std::tuple<int, int>> player_position;
39 std::set<std::string> solved_panels; 42 std::set<std::string> solved_panels;
40 43
41 int backoff_amount = 0;
42
43 // Thread state 44 // Thread state
44 std::unique_ptr<wswrap::WS> ws; 45 std::unique_ptr<wswrap::WS> ws;
45 bool connected = false; 46 bool connected = false;
46 47
47 void Start(TrackerFrame* frame) { 48 void SetTrackerFrame(TrackerFrame* frame) { tracker_frame = frame; }
48 tracker_frame = frame; 49
50 void Connect(std::string a) {
51 // This is the main concurrency concern, as it mutates protected state in an
52 // important way. Thread() is documented with how it interacts with this
53 // function.
54 std::lock_guard state_guard(state_mutex);
55
56 if (!initialized) {
57 std::thread([this]() { Thread(); }).detach();
49 58
50 std::thread([this]() { Thread(); }).detach(); 59 initialized = true;
60 } else if (address != a) {
61 should_disconnect = true;
62 }
63
64 address = a;
51 } 65 }
52 66
53 std::optional<std::string> GetStatusMessage() { 67 std::optional<std::string> GetStatusMessage() {
@@ -57,6 +71,13 @@ struct IPCState {
57 } 71 }
58 72
59 void SetTrackerSlot(std::string server, std::string user) { 73 void SetTrackerSlot(std::string server, std::string user) {
74 // This is function is called from the APState thread, not the main thread,
75 // and it mutates protected state. It only really competes with OnMessage(),
76 // when a "Connect" message is received. If this is called right before,
77 // and the tracker slot does not match the old game slot, it will initiate a
78 // disconnect, and then the OnMessage() handler will see should_disconnect
79 // and stop processing the "Connect" message. If this is called right after
80 // and the slot does not match, IPC will disconnect, which is tolerable.
60 std::lock_guard state_guard(state_mutex); 81 std::lock_guard state_guard(state_mutex);
61 82
62 tracker_ap_server = std::move(server); 83 tracker_ap_server = std::move(server);
@@ -64,7 +85,10 @@ struct IPCState {
64 85
65 CheckIfSlotMatches(); 86 CheckIfSlotMatches();
66 87
67 backoff_amount = 0; 88 if (!slot_matches) {
89 should_disconnect = true;
90 address.clear();
91 }
68 } 92 }
69 93
70 bool IsConnected() { 94 bool IsConnected() {
@@ -88,54 +112,126 @@ struct IPCState {
88 private: 112 private:
89 void Thread() { 113 void Thread() {
90 for (;;) { 114 for (;;) {
91 player_position = std::nullopt; 115 SetStatusMessage("Disconnected from game.");
92
93 TrackerLog("Looking for game over IPC...");
94 116
117 // initialized is definitely true because it is set to true when the thread
118 // is created and only set to false within this block, when the thread is
119 // killed. Thus, a call to Connect would always at most set
120 // should_disconnect and address. If this happens before this block, it is
121 // as if we are starting from a new thread anyway because should_disconnect
122 // is immediately reset. If a call to Connect happens after this block,
123 // then a connection attempt will be made to the wrong address, but the
124 // thread will grab the mutex right after this and back out the wrong
125 // connection.
126 std::string ipc_address;
95 { 127 {
96 std::lock_guard state_guard(state_mutex); 128 std::lock_guard state_guard(state_mutex);
97 backoff_amount = 0;
98 }
99 129
100 while (!TryConnect() || !connected) { 130 should_disconnect = false;
101 int backoff_limit; 131
102 { 132 slot_matches = false;
103 std::lock_guard state_guard(state_mutex); 133 game_ap_server.clear();
104 backoff_limit = (backoff_amount + 1) * 10; 134 game_ap_user.clear();
135
136 player_position = std::nullopt;
137 solved_panels.clear();
138
139 if (address.empty()) {
140 initialized = false;
141 return;
105 } 142 }
106 143
144 ipc_address = address;
145 }
146
147 int backoff_amount = 0;
148
149 SetStatusMessage("Connecting to game...");
150 TrackerLog(fmt::format("Looking for game over IPC ({})...", ipc_address));
151
152 while (!TryConnect(ipc_address) || !connected) {
153 int backoff_limit = (backoff_amount + 1) * 10;
154
107 for (int i = 0; i < backoff_limit && !connected; i++) { 155 for (int i = 0; i < backoff_limit && !connected; i++) {
156 // If Connect is called right before this block, we will see and handle
157 // should_disconnect. If it is called right after, we will do one bad
158 // poll, one sleep, and then grab the mutex again right after.
159 {
160 std::lock_guard state_guard(state_mutex);
161 if (should_disconnect) {
162 break;
163 }
164 }
165
108 ws->poll(); 166 ws->poll();
109 167
110 // Back off 168 // Back off
111 std::this_thread::sleep_for(std::chrono::milliseconds(100)); 169 std::this_thread::sleep_for(std::chrono::milliseconds(100));
112 } 170 }
113 171
172 backoff_amount++;
173
174 // If Connect is called right before this block, we will see and handle
175 // should_disconnect. If it is called right after, and the connection
176 // was unsuccessful, we will grab the mutex after one bad connection
177 // attempt. If the connection was successful, we grab the mutex right
178 // after exiting the loop.
114 { 179 {
115 std::lock_guard state_guard(state_mutex); 180 std::lock_guard state_guard(state_mutex);
116 backoff_amount = std::min(9, backoff_amount + 1);
117 181
118 if (!connected) { 182 if (should_disconnect) {
119 TrackerLog(fmt::format("Retrying IPC in {} second(s)...", 183 break;
120 backoff_amount + 1)); 184 } else if (!connected) {
185 if (backoff_amount >= 10) {
186 should_disconnect = true;
187 address.clear();
188
189 TrackerLog("Giving up on IPC.");
190 SetStatusMessage("Disconnected from game.");
191
192 wxMessageBox("Connection to Lingo timed out.",
193 "Connection failed", wxOK | wxICON_ERROR);
194
195 break;
196 } else {
197 TrackerLog(fmt::format("Retrying IPC in {} second(s)...",
198 backoff_amount + 1));
199 }
121 } 200 }
122 } 201 }
123 } 202 }
124 203
204 // Pretty much every lock guard in the thread is the same. We check for
205 // should_disconnect, and if it gets set directly after the block, we do
206 // minimal bad work before checking for it again.
207 {
208 std::lock_guard state_guard(state_mutex);
209 if (should_disconnect) {
210 ws.reset();
211 continue;
212 }
213 }
214
125 while (connected) { 215 while (connected) {
126 ws->poll(); 216 ws->poll();
127 217
128 std::this_thread::sleep_for(std::chrono::milliseconds(100)); 218 std::this_thread::sleep_for(std::chrono::milliseconds(100));
129 }
130 219
131 SetStatusMessage("Disconnected from game."); 220 {
221 std::lock_guard state_guard(state_mutex);
222 if (should_disconnect) {
223 ws.reset();
224 break;
225 }
226 }
227 }
132 } 228 }
133 } 229 }
134 230
135 bool TryConnect() { 231 bool TryConnect(std::string ipc_address) {
136 try { 232 try {
137 ws = std::make_unique<wswrap::WS>( 233 ws = std::make_unique<wswrap::WS>(
138 kIpcAddress, [this]() { OnConnect(); }, [this]() { OnClose(); }, 234 ipc_address, [this]() { OnConnect(); }, [this]() { OnClose(); },
139 [this](const std::string& s) { OnMessage(s); }, 235 [this](const std::string& s) { OnMessage(s); },
140 [this](const std::string& s) { OnError(s); }); 236 [this](const std::string& s) { OnError(s); });
141 return true; 237 return true;
@@ -174,11 +270,19 @@ struct IPCState {
174 270
175 if (msg["cmd"] == "Connect") { 271 if (msg["cmd"] == "Connect") {
176 std::lock_guard state_guard(state_mutex); 272 std::lock_guard state_guard(state_mutex);
273 if (should_disconnect) {
274 return;
275 }
177 276
178 game_ap_server = msg["slot"]["server"]; 277 game_ap_server = msg["slot"]["server"];
179 game_ap_user = msg["slot"]["player"]; 278 game_ap_user = msg["slot"]["player"];
180 279
181 CheckIfSlotMatches(); 280 CheckIfSlotMatches();
281
282 if (!slot_matches) {
283 tracker_frame->ConnectToAp(game_ap_server, game_ap_user,
284 msg["slot"]["password"]);
285 }
182 } else if (msg["cmd"] == "UpdatePosition") { 286 } else if (msg["cmd"] == "UpdatePosition") {
183 std::lock_guard state_guard(state_mutex); 287 std::lock_guard state_guard(state_mutex);
184 288
@@ -207,8 +311,6 @@ struct IPCState {
207 status_message = "Connected to game."; 311 status_message = "Connected to game.";
208 312
209 Sync(); 313 Sync();
210 } else if (tracker_ap_server.empty()) {
211 status_message = std::nullopt;
212 } else if (connected) { 314 } else if (connected) {
213 status_message = "Local game doesn't match AP slot."; 315 status_message = "Local game doesn't match AP slot.";
214 } 316 }
@@ -240,7 +342,11 @@ IPCState& GetState() {
240 342
241} // namespace 343} // namespace
242 344
243void IPC_Start(TrackerFrame* tracker_frame) { GetState().Start(tracker_frame); } 345void IPC_SetTrackerFrame(TrackerFrame* tracker_frame) {
346 GetState().SetTrackerFrame(tracker_frame);
347}
348
349void IPC_Connect(std::string address) { GetState().Connect(address); }
244 350
245std::optional<std::string> IPC_GetStatusMessage() { 351std::optional<std::string> IPC_GetStatusMessage() {
246 return GetState().GetStatusMessage(); 352 return GetState().GetStatusMessage();