1 // serverbrowser.cpp: eihrul's concurrent resolver, and server browser window management
5 #include "SDL_thread.h"
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())
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();
56 SDL_CondSignal(resultcond
);
58 SDL_UnlockMutex(resolvermutex
);
65 resolvermutex
= SDL_CreateMutex();
66 querycond
= SDL_CreateCond();
67 resultcond
= SDL_CreateCond();
69 SDL_LockMutex(resolvermutex
);
70 loopi(RESOLVERTHREADS
)
72 resolverthread
&rt
= resolverthreads
.add();
75 rt
.thread
= SDL_CreateThread(resolverloop
, &rt
);
77 SDL_UnlockMutex(resolvermutex
);
80 void resolverstop(resolverthread
&rt
)
82 SDL_LockMutex(resolvermutex
);
86 SDL_KillThread(rt
.thread
);
88 rt
.thread
= SDL_CreateThread(resolverloop
, &rt
);
92 SDL_UnlockMutex(resolvermutex
);
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
];
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();
128 address
->host
= rr
.address
.host
;
131 else loopv(resolverthreads
)
133 resolverthread
&rt
= resolverthreads
[i
];
134 if(rt
.query
&& totalmillis
- rt
.starttime
> RESOLVERLIMIT
)
141 SDL_UnlockMutex(resolvermutex
);
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;
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
);
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
);
186 SDL_Thread
*connthread
= NULL
;
187 SDL_mutex
*connmutex
= NULL
;
188 SDL_cond
*conncond
= NULL
;
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
);
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
);
219 ((connectdata
*)data
)->result
= result
;
220 SDL_CondSignal(conncond
);
221 SDL_UnlockMutex(connmutex
);
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;
242 if(!SDL_CondWaitTimeout(conncond
, connmutex
, 250))
244 if(cd
.result
<0) enet_socket_destroy(sock
);
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
257 SDL_UnlockMutex(connmutex
);
262 enum { UNRESOLVED
= 0, RESOLVING
, RESOLVED
};
269 int numplayers
, ping
, resolved
;
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
;
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)
306 void addserver(char *servername
)
308 loopv(servers
) if(!strcmp(servers
[i
]->name
, servername
)) return;
309 newserver(servername
);
312 VAR(searchlan
, 0, 0, 1);
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
;
324 enet_socket_set_option(pingsock
, ENET_SOCKOPT_NONBLOCK
, 1);
325 enet_socket_set_option(pingsock
, ENET_SOCKOPT_BROADCAST
, 1);
328 uchar ping
[MAXTRANS
];
329 ucharbuf
p(ping
, sizeof(ping
));
330 putint(p
, totalmillis
);
333 serverinfo
&si
= *servers
[i
];
334 if(si
.address
.host
== ENET_HOST_ANY
) continue;
336 buf
.dataLength
= p
.length();
337 enet_socket_send(pingsock
, &si
.address
, &buf
, 1);
342 address
.host
= ENET_HOST_BROADCAST
;
343 address
.port
= sv
->serverinfoport();
345 buf
.dataLength
= p
.length();
346 enet_socket_send(pingsock
, &address
, &buf
, 1);
348 lastinfo
= totalmillis
;
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
); }
364 if(!resolving
) return;
366 const char *name
= NULL
;
367 ENetAddress addr
= { ENET_HOST_ANY
, sv
->serverinfoport() };
368 while(resolvercheck(&name
, &addr
))
372 serverinfo
&si
= *servers
[i
];
375 si
.resolved
= RESOLVED
;
377 addr
.host
= ENET_HOST_ANY
;
386 if(pingsock
==ENET_SOCKET_NULL
) return;
387 enet_uint32 events
= ENET_SOCKET_WAIT_RECEIVE
;
390 uchar ping
[MAXTRANS
];
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);
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
);
402 ucharbuf
p(ping
, len
);
403 si
->ping
= totalmillis
- getint(p
);
404 si
->numplayers
= getint(p
);
405 int numattr
= getint(p
);
407 loopj(numattr
) si
->attr
.add(getint(p
));
409 filtertext(si
->map
, text
);
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
);
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
;
437 if(totalmillis
- lastinfo
>= 5000) pingservers();
438 servers
.sort(sicompare
);
441 const char *showservers(g3d_gui
*cgui
)
444 const char *name
= NULL
;
445 for(int start
= 0; start
< servers
.length();)
447 if(start
> 0) cgui
->tab();
448 int end
= servers
.length();
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
))
463 cl
->serverinfoendcolumn(cgui
, i
);
474 servers
.deletecontentsp();
477 void updatefrommaster()
480 uchar
*reply
= retrieveservers(buf
, sizeof(buf
));
481 if(!*reply
|| strstr((char *)reply
, "<html>") || strstr((char *)reply
, "<HTML>")) conoutf("master server not replying");
485 execute((char *)reply
);
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");
499 fprintf(f
, "// servers connected to are added here automatically\n\n");
500 loopvrev(servers
) fprintf(f
, "addserver %s\n", servers
[i
]->name
);