Initial Comit: First commit.
[SauerEngine.git] / src / engine / serverbrowser.cpp
blobdcdca1705a297d2d979f7e29b744d60f36927cc6
1 // serverbrowser.cpp: eihrul's concurrent resolver, and server browser window management
3 #include "pch.h"
4 #include "engine.h"
5 #include "SDL_thread.h"
7 struct resolverthread
9 SDL_Thread *thread;
10 const char *query;
11 int starttime;
14 struct resolverresult
16 const char *query;
17 ENetAddress address;
20 vector<resolverthread> resolverthreads;
21 vector<const char *> resolverqueries;
22 vector<resolverresult> resolverresults;
23 SDL_mutex *resolvermutex;
24 SDL_cond *querycond, *resultcond;
26 #define RESOLVERTHREADS 1
27 #define RESOLVERLIMIT 3000
29 int resolverloop(void * data)
31 resolverthread *rt = (resolverthread *)data;
32 SDL_LockMutex(resolvermutex);
33 SDL_Thread *thread = rt->thread;
34 SDL_UnlockMutex(resolvermutex);
35 if(!thread || SDL_GetThreadID(thread) != SDL_ThreadID())
36 return 0;
37 while(thread == rt->thread)
39 SDL_LockMutex(resolvermutex);
40 while(resolverqueries.empty()) SDL_CondWait(querycond, resolvermutex);
41 rt->query = resolverqueries.pop();
42 rt->starttime = totalmillis;
43 SDL_UnlockMutex(resolvermutex);
45 ENetAddress address = { ENET_HOST_ANY, sv->serverinfoport() };
46 enet_address_set_host(&address, rt->query);
48 SDL_LockMutex(resolvermutex);
49 if(rt->query && thread == rt->thread)
51 resolverresult &rr = resolverresults.add();
52 rr.query = rt->query;
53 rr.address = address;
54 rt->query = NULL;
55 rt->starttime = 0;
56 SDL_CondSignal(resultcond);
58 SDL_UnlockMutex(resolvermutex);
60 return 0;
63 void resolverinit()
65 resolvermutex = SDL_CreateMutex();
66 querycond = SDL_CreateCond();
67 resultcond = SDL_CreateCond();
69 SDL_LockMutex(resolvermutex);
70 loopi(RESOLVERTHREADS)
72 resolverthread &rt = resolverthreads.add();
73 rt.query = NULL;
74 rt.starttime = 0;
75 rt.thread = SDL_CreateThread(resolverloop, &rt);
77 SDL_UnlockMutex(resolvermutex);
80 void resolverstop(resolverthread &rt)
82 SDL_LockMutex(resolvermutex);
83 if(rt.query)
85 #ifndef __APPLE__
86 SDL_KillThread(rt.thread);
87 #endif
88 rt.thread = SDL_CreateThread(resolverloop, &rt);
90 rt.query = NULL;
91 rt.starttime = 0;
92 SDL_UnlockMutex(resolvermutex);
95 void resolverclear()
97 if(resolverthreads.empty()) return;
99 SDL_LockMutex(resolvermutex);
100 resolverqueries.setsize(0);
101 resolverresults.setsize(0);
102 loopv(resolverthreads)
104 resolverthread &rt = resolverthreads[i];
105 resolverstop(rt);
107 SDL_UnlockMutex(resolvermutex);
110 void resolverquery(const char *name)
112 if(resolverthreads.empty()) resolverinit();
114 SDL_LockMutex(resolvermutex);
115 resolverqueries.add(name);
116 SDL_CondSignal(querycond);
117 SDL_UnlockMutex(resolvermutex);
120 bool resolvercheck(const char **name, ENetAddress *address)
122 bool resolved = false;
123 SDL_LockMutex(resolvermutex);
124 if(!resolverresults.empty())
126 resolverresult &rr = resolverresults.pop();
127 *name = rr.query;
128 address->host = rr.address.host;
129 resolved = true;
131 else loopv(resolverthreads)
133 resolverthread &rt = resolverthreads[i];
134 if(rt.query && totalmillis - rt.starttime > RESOLVERLIMIT)
136 resolverstop(rt);
137 *name = rt.query;
138 resolved = true;
141 SDL_UnlockMutex(resolvermutex);
142 return resolved;
145 bool resolverwait(const char *name, ENetAddress *address)
147 if(resolverthreads.empty()) resolverinit();
149 s_sprintfd(text)("resolving %s... (esc to abort)", name);
150 show_out_of_renderloop_progress(0, text);
152 SDL_LockMutex(resolvermutex);
153 resolverqueries.add(name);
154 SDL_CondSignal(querycond);
155 int starttime = SDL_GetTicks(), timeout = 0;
156 bool resolved = false;
157 for(;;)
159 SDL_CondWaitTimeout(resultcond, resolvermutex, 250);
160 loopv(resolverresults) if(resolverresults[i].query == name)
162 address->host = resolverresults[i].address.host;
163 resolverresults.remove(i);
164 resolved = true;
165 break;
167 if(resolved) break;
169 timeout = SDL_GetTicks() - starttime;
170 show_out_of_renderloop_progress(min(float(timeout)/RESOLVERLIMIT, 1.0f), text);
171 if(interceptkey(SDLK_ESCAPE)) timeout = RESOLVERLIMIT + 1;
172 if(timeout > RESOLVERLIMIT) break;
174 if(!resolved && timeout > RESOLVERLIMIT)
176 loopv(resolverthreads)
178 resolverthread &rt = resolverthreads[i];
179 if(rt.query == name) { resolverstop(rt); break; }
182 SDL_UnlockMutex(resolvermutex);
183 return resolved;
186 SDL_Thread *connthread = NULL;
187 SDL_mutex *connmutex = NULL;
188 SDL_cond *conncond = NULL;
190 struct connectdata
192 ENetSocket sock;
193 ENetAddress address;
194 int result;
197 // do this in a thread to prevent timeouts
198 // could set timeouts on sockets, but this is more reliable and gives more control
199 int connectthread(void *data)
201 SDL_LockMutex(connmutex);
202 if(!connthread || SDL_GetThreadID(connthread) != SDL_ThreadID())
204 SDL_UnlockMutex(connmutex);
205 return 0;
207 connectdata cd = *(connectdata *)data;
208 SDL_UnlockMutex(connmutex);
210 int result = enet_socket_connect(cd.sock, &cd.address);
212 SDL_LockMutex(connmutex);
213 if(!connthread || SDL_GetThreadID(connthread) != SDL_ThreadID())
215 enet_socket_destroy(cd.sock);
216 SDL_UnlockMutex(connmutex);
217 return 0;
219 ((connectdata *)data)->result = result;
220 SDL_CondSignal(conncond);
221 SDL_UnlockMutex(connmutex);
223 return 0;
226 #define CONNLIMIT 20000
228 int connectwithtimeout(ENetSocket sock, const char *hostname, ENetAddress &address)
230 s_sprintfd(text)("connecting to %s... (esc to abort)", hostname);
231 show_out_of_renderloop_progress(0, text);
233 if(!connmutex) connmutex = SDL_CreateMutex();
234 if(!conncond) conncond = SDL_CreateCond();
235 SDL_LockMutex(connmutex);
236 connectdata cd = { sock, address, -1 };
237 connthread = SDL_CreateThread(connectthread, &cd);
239 int starttime = SDL_GetTicks(), timeout = 0;
240 for(;;)
242 if(!SDL_CondWaitTimeout(conncond, connmutex, 250))
244 if(cd.result<0) enet_socket_destroy(sock);
245 break;
247 timeout = SDL_GetTicks() - starttime;
248 show_out_of_renderloop_progress(min(float(timeout)/CONNLIMIT, 1.0f), text);
249 if(interceptkey(SDLK_ESCAPE)) timeout = CONNLIMIT + 1;
250 if(timeout > CONNLIMIT) break;
253 /* thread will actually timeout eventually if its still trying to connect
254 * so just leave it (and let it destroy socket) instead of causing problems on some platforms by killing it
256 connthread = NULL;
257 SDL_UnlockMutex(connmutex);
259 return cd.result;
262 enum { UNRESOLVED = 0, RESOLVING, RESOLVED };
264 struct serverinfo
266 string name;
267 string map;
268 string sdesc;
269 int numplayers, ping, resolved;
270 vector<int> attr;
271 ENetAddress address;
273 serverinfo()
274 : numplayers(0), ping(999), resolved(UNRESOLVED)
276 name[0] = map[0] = sdesc[0] = '\0';
280 vector<serverinfo *> servers;
281 ENetSocket pingsock = ENET_SOCKET_NULL;
282 int lastinfo = 0;
284 char *getservername(int n) { return servers[n]->name; }
286 static serverinfo *newserver(const char *name, uint ip = ENET_HOST_ANY, uint port = sv->serverinfoport())
288 serverinfo *si = new serverinfo;
289 si->address.host = ip;
290 si->address.port = port;
291 if(ip!=ENET_HOST_ANY) si->resolved = RESOLVED;
293 if(name) s_strcpy(si->name, name);
294 else if(ip==ENET_HOST_ANY || enet_address_get_host_ip(&si->address, si->name, sizeof(si->name)) < 0)
296 delete si;
297 return NULL;
301 servers.add(si);
303 return si;
306 void addserver(char *servername)
308 loopv(servers) if(!strcmp(servers[i]->name, servername)) return;
309 newserver(servername);
312 VAR(searchlan, 0, 0, 1);
314 void pingservers()
316 if(pingsock == ENET_SOCKET_NULL)
318 pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, NULL);
319 if(pingsock == ENET_SOCKET_NULL)
321 lastinfo = totalmillis;
322 return;
324 enet_socket_set_option(pingsock, ENET_SOCKOPT_NONBLOCK, 1);
325 enet_socket_set_option(pingsock, ENET_SOCKOPT_BROADCAST, 1);
327 ENetBuffer buf;
328 uchar ping[MAXTRANS];
329 ucharbuf p(ping, sizeof(ping));
330 putint(p, totalmillis);
331 loopv(servers)
333 serverinfo &si = *servers[i];
334 if(si.address.host == ENET_HOST_ANY) continue;
335 buf.data = ping;
336 buf.dataLength = p.length();
337 enet_socket_send(pingsock, &si.address, &buf, 1);
339 if(searchlan)
341 ENetAddress address;
342 address.host = ENET_HOST_BROADCAST;
343 address.port = sv->serverinfoport();
344 buf.data = ping;
345 buf.dataLength = p.length();
346 enet_socket_send(pingsock, &address, &buf, 1);
348 lastinfo = totalmillis;
351 void checkresolver()
353 int resolving = 0;
354 loopv(servers)
356 serverinfo &si = *servers[i];
357 if(si.resolved == RESOLVED) continue;
358 if(si.address.host == ENET_HOST_ANY)
360 if(si.resolved == UNRESOLVED) { si.resolved = RESOLVING; resolverquery(si.name); }
361 resolving++;
364 if(!resolving) return;
366 const char *name = NULL;
367 ENetAddress addr = { ENET_HOST_ANY, sv->serverinfoport() };
368 while(resolvercheck(&name, &addr))
370 loopv(servers)
372 serverinfo &si = *servers[i];
373 if(name == si.name)
375 si.resolved = RESOLVED;
376 si.address = addr;
377 addr.host = ENET_HOST_ANY;
378 break;
384 void checkpings()
386 if(pingsock==ENET_SOCKET_NULL) return;
387 enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE;
388 ENetBuffer buf;
389 ENetAddress addr;
390 uchar ping[MAXTRANS];
391 char text[MAXTRANS];
392 buf.data = ping;
393 buf.dataLength = sizeof(ping);
394 while(enet_socket_wait(pingsock, &events, 0) >= 0 && events)
396 int len = enet_socket_receive(pingsock, &addr, &buf, 1);
397 if(len <= 0) return;
398 serverinfo *si = NULL;
399 loopv(servers) if(addr.host == servers[i]->address.host) { si = servers[i]; break; }
400 if(!si && searchlan) si = newserver(NULL, addr.host);
401 if(!si) continue;
402 ucharbuf p(ping, len);
403 si->ping = totalmillis - getint(p);
404 si->numplayers = getint(p);
405 int numattr = getint(p);
406 si->attr.setsize(0);
407 loopj(numattr) si->attr.add(getint(p));
408 getstring(text, p);
409 filtertext(si->map, text);
410 getstring(text, p);
411 filtertext(si->sdesc, text);
415 int sicompare(serverinfo **ap, serverinfo **bp)
417 serverinfo *a = *ap, *b = *bp;
418 bool ac = sv->servercompatible(a->name, a->sdesc, a->map, a->ping, a->attr, a->numplayers),
419 bc = sv->servercompatible(b->name, b->sdesc, b->map, b->ping, b->attr, b->numplayers);
420 if(ac>bc) return -1;
421 if(bc>ac) return 1;
422 if(a->numplayers<b->numplayers) return 1;
423 if(a->numplayers>b->numplayers) return -1;
424 if(a->ping>b->ping) return 1;
425 if(a->ping<b->ping) return -1;
426 return strcmp(a->name, b->name);
429 void refreshservers()
431 static int lastrefresh = 0;
432 if(lastrefresh==totalmillis) return;
433 lastrefresh = totalmillis;
435 checkresolver();
436 checkpings();
437 if(totalmillis - lastinfo >= 5000) pingservers();
438 servers.sort(sicompare);
441 const char *showservers(g3d_gui *cgui)
443 refreshservers();
444 const char *name = NULL;
445 for(int start = 0; start < servers.length();)
447 if(start > 0) cgui->tab();
448 int end = servers.length();
449 cgui->pushlist();
450 loopi(10)
452 if(!cl->serverinfostartcolumn(cgui, i)) break;
453 for(int j = start; j < end; j++)
455 if(!i && cgui->shouldtab()) { end = j; break; }
456 serverinfo &si = *servers[j];
457 const char *sdesc = si.sdesc;
458 if(si.address.host == ENET_HOST_ANY) sdesc = "[unknown host]";
459 else if(si.ping == 999) sdesc = "[waiting for response]";
460 if(cl->serverinfoentry(cgui, i, si.name, sdesc, si.map, sdesc == si.sdesc ? si.ping : -1, si.attr, si.numplayers))
461 name = si.name;
463 cl->serverinfoendcolumn(cgui, i);
465 cgui->poplist();
466 start = end;
468 return name;
471 void clearservers()
473 resolverclear();
474 servers.deletecontentsp();
477 void updatefrommaster()
479 uchar buf[32000];
480 uchar *reply = retrieveservers(buf, sizeof(buf));
481 if(!*reply || strstr((char *)reply, "<html>") || strstr((char *)reply, "<HTML>")) conoutf("master server not replying");
482 else
484 clearservers();
485 execute((char *)reply);
487 refreshservers();
490 COMMAND(addserver, "s");
491 COMMAND(clearservers, "");
492 COMMAND(updatefrommaster, "");
494 void writeservercfg()
496 if(!cl->savedservers()) return;
497 FILE *f = openfile(path(cl->savedservers(), true), "w");
498 if(!f) return;
499 fprintf(f, "// servers connected to are added here automatically\n\n");
500 loopvrev(servers) fprintf(f, "addserver %s\n", servers[i]->name);
501 fclose(f);