// serverbrowser.cpp: eihrul's concurrent resolver, and server browser window management #include "cube.h" #ifdef __APPLE__ #include #endif #include "SDL_thread.h" extern bool isdedicated; struct resolverthread { SDL_Thread *thread; const char *query; int starttime; }; struct resolverresult { const char *query; ENetAddress address; }; vector resolverthreads; vector resolverqueries; vector resolverresults; SDL_mutex *resolvermutex; SDL_cond *querycond, *resultcond; #define RESOLVERTHREADS 1 #define RESOLVERLIMIT 3000 int resolverloop(void * data) { resolverthread *rt = (resolverthread *)data; SDL_LockMutex(resolvermutex); SDL_Thread *thread = rt->thread; SDL_UnlockMutex(resolvermutex); if(!thread || SDL_GetThreadID(thread) != SDL_ThreadID()) return 0; while(thread == rt->thread) { SDL_LockMutex(resolvermutex); while(resolverqueries.empty()) SDL_CondWait(querycond, resolvermutex); rt->query = resolverqueries.pop(); rt->starttime = lastmillis; SDL_UnlockMutex(resolvermutex); ENetAddress address = { ENET_HOST_ANY, CUBE_SERVINFO_PORT }; enet_address_set_host(&address, rt->query); SDL_LockMutex(resolvermutex); if(thread == rt->thread) { resolverresult &rr = resolverresults.add(); rr.query = rt->query; rr.address = address; rt->query = NULL; rt->starttime = 0; SDL_CondSignal(resultcond); } SDL_UnlockMutex(resolvermutex); } return 0; } void resolverinit() { resolvermutex = SDL_CreateMutex(); querycond = SDL_CreateCond(); resultcond = SDL_CreateCond(); SDL_LockMutex(resolvermutex); loopi(RESOLVERTHREADS) { resolverthread &rt = resolverthreads.add(); rt.query = NULL; rt.starttime = 0; rt.thread = SDL_CreateThread(resolverloop, &rt); } SDL_UnlockMutex(resolvermutex); } void resolverstop(resolverthread &rt) { SDL_LockMutex(resolvermutex); if(rt.query) { #ifndef __APPLE__ SDL_KillThread(rt.thread); #endif rt.thread = SDL_CreateThread(resolverloop, &rt); } rt.query = NULL; rt.starttime = 0; SDL_UnlockMutex(resolvermutex); } void resolverclear() { if(resolverthreads.empty()) return; SDL_LockMutex(resolvermutex); resolverqueries.setsize(0); resolverresults.setsize(0); loopv(resolverthreads) { resolverthread &rt = resolverthreads[i]; resolverstop(rt); } SDL_UnlockMutex(resolvermutex); } void resolverquery(const char *name) { if(resolverthreads.empty()) resolverinit(); SDL_LockMutex(resolvermutex); resolverqueries.add(name); SDL_CondSignal(querycond); SDL_UnlockMutex(resolvermutex); } bool resolvercheck(const char **name, ENetAddress *address) { bool resolved = false; SDL_LockMutex(resolvermutex); if(!resolverresults.empty()) { resolverresult &rr = resolverresults.pop(); *name = rr.query; address->host = rr.address.host; resolved = true; } else loopv(resolverthreads) { resolverthread &rt = resolverthreads[i]; if(rt.query && lastmillis - rt.starttime > RESOLVERLIMIT) { resolverstop(rt); *name = rt.query; resolved = true; } } SDL_UnlockMutex(resolvermutex); return resolved; } extern bool isdedicated; bool resolverwait(const char *name, ENetAddress *address) { if(isdedicated) return enet_address_set_host(address, name) >= 0; if(resolverthreads.empty()) resolverinit(); s_sprintfd(text)("resolving %s... (esc to abort)", name); show_out_of_renderloop_progress(0, text); SDL_LockMutex(resolvermutex); resolverqueries.add(name); SDL_CondSignal(querycond); int starttime = SDL_GetTicks(), timeout = 0; bool resolved = false; for(;;) { SDL_CondWaitTimeout(resultcond, resolvermutex, 250); loopv(resolverresults) if(resolverresults[i].query == name) { address->host = resolverresults[i].address.host; resolverresults.remove(i); resolved = true; break; } if(resolved) break; timeout = SDL_GetTicks() - starttime; show_out_of_renderloop_progress(min(float(timeout)/RESOLVERLIMIT, 1), text); SDL_Event event; while(SDL_PollEvent(&event)) { if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) timeout = RESOLVERLIMIT + 1; } if(timeout > RESOLVERLIMIT) break; } if(!resolved && timeout > RESOLVERLIMIT) { loopv(resolverthreads) { resolverthread &rt = resolverthreads[i]; if(rt.query == name) { resolverstop(rt); break; } } } SDL_UnlockMutex(resolvermutex); return resolved; } SDL_Thread *connthread = NULL; SDL_mutex *connmutex = NULL; SDL_cond *conncond = NULL; struct connectdata { ENetSocket sock; ENetAddress address; int result; }; // do this in a thread to prevent timeouts // could set timeouts on sockets, but this is more reliable and gives more control int connectthread(void *data) { SDL_LockMutex(connmutex); if(!connthread || SDL_GetThreadID(connthread) != SDL_ThreadID()) { SDL_UnlockMutex(connmutex); return 0; } connectdata cd = *(connectdata *)data; SDL_UnlockMutex(connmutex); int result = enet_socket_connect(cd.sock, &cd.address); SDL_LockMutex(connmutex); if(!connthread || SDL_GetThreadID(connthread) != SDL_ThreadID()) { enet_socket_destroy(cd.sock); SDL_UnlockMutex(connmutex); return 0; } ((connectdata *)data)->result = result; SDL_CondSignal(conncond); SDL_UnlockMutex(connmutex); return 0; } #define CONNLIMIT 20000 int connectwithtimeout(ENetSocket sock, char *hostname, ENetAddress &address) { if(isdedicated) { int result = enet_socket_connect(sock, &address); if(result<0) enet_socket_destroy(sock); return result; } s_sprintfd(text)("connecting to %s... (esc to abort)", hostname); show_out_of_renderloop_progress(0, text); if(!connmutex) connmutex = SDL_CreateMutex(); if(!conncond) conncond = SDL_CreateCond(); SDL_LockMutex(connmutex); connectdata cd = { sock, address, -1 }; connthread = SDL_CreateThread(connectthread, &cd); int starttime = SDL_GetTicks(), timeout = 0; for(;;) { if(!SDL_CondWaitTimeout(conncond, connmutex, 250)) { if(cd.result<0) enet_socket_destroy(sock); break; } timeout = SDL_GetTicks() - starttime; show_out_of_renderloop_progress(min(float(timeout)/CONNLIMIT, 1), text); SDL_Event event; while(SDL_PollEvent(&event)) { if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) timeout = CONNLIMIT + 1; } if(timeout > CONNLIMIT) break; } /* thread will actually timeout eventually if its still trying to connect * so just leave it (and let it destroy socket) instead of causing problems on some platforms by killing it */ connthread = NULL; SDL_UnlockMutex(connmutex); return cd.result; } struct serverinfo { char *name; string full; string map; string sdesc; string cmd; int mode, numplayers, maxclients, ping, protocol, minremain, resolved; ENetAddress address; }; enum { UNRESOLVED = 0, RESOLVING, RESOLVED }; vector servers; ENetSocket pingsock = ENET_SOCKET_NULL; int lastinfo = 0; char *getservername(int n) { return servers[n].name; } void addserver(char *servername) { loopv(servers) if(strcmp(servers[i].name, servername)==0) return; serverinfo &si = servers.insert(0, serverinfo()); si.name = newstring(servername); si.full[0] = 0; si.mode = 0; si.numplayers = 0; si.maxclients = 0; si.ping = 9999; si.protocol = 0; si.minremain = 0; si.map[0] = 0; si.sdesc[0] = 0; si.resolved = UNRESOLVED; si.address.host = ENET_HOST_ANY; si.address.port = CUBE_SERVINFO_PORT; } void pingservers() { ENetBuffer buf; uchar ping[MAXTRANS]; loopv(servers) { serverinfo &si = servers[i]; if(si.address.host == ENET_HOST_ANY) continue; ucharbuf p(ping, sizeof(ping)); putint(p, lastmillis); buf.data = ping; buf.dataLength = p.length(); enet_socket_send(pingsock, &si.address, &buf, 1); } lastinfo = lastmillis; } void checkresolver() { int resolving = 0; loopv(servers) { serverinfo &si = servers[i]; if(si.resolved == RESOLVED) continue; if(si.address.host == ENET_HOST_ANY) { if(si.resolved == UNRESOLVED) { si.resolved = RESOLVING; resolverquery(si.name); } resolving++; } } if(!resolving) return; const char *name = NULL; ENetAddress addr = { ENET_HOST_ANY, CUBE_SERVINFO_PORT }; while(resolvercheck(&name, &addr)) { loopv(servers) { serverinfo &si = servers[i]; if(name == si.name) { si.resolved = RESOLVED; si.address = addr; addr.host = ENET_HOST_ANY; break; } } } } void checkpings() { enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; ENetBuffer buf; ENetAddress addr; uchar ping[MAXTRANS]; char text[MAXTRANS]; buf.data = ping; buf.dataLength = sizeof(ping); while(enet_socket_wait(pingsock, &events, 0) >= 0 && events) { int len = enet_socket_receive(pingsock, &addr, &buf, 1); if(len <= 0) return; loopv(servers) { serverinfo &si = servers[i]; if(addr.host == si.address.host) { ucharbuf p(ping, len); si.ping = lastmillis - getint(p); si.protocol = getint(p); if(si.protocol!=PROTOCOL_VERSION) si.ping = 9998; si.mode = getint(p); si.numplayers = getint(p); si.minremain = getint(p); getstring(text, p); s_strcpy(si.map, text); getstring(text, p); s_strcpy(si.sdesc, text); si.maxclients = getint(p); break; } } } } int sicompare(const serverinfo *a, const serverinfo *b) { if((a->protocol==PROTOCOL_VERSION) > (b->protocol==PROTOCOL_VERSION)) return -1; if((b->protocol==PROTOCOL_VERSION) > (a->protocol==PROTOCOL_VERSION)) return 1; if(a->numplayersnumplayers) return 1; if(a->numplayers>b->numplayers) return -1; if(a->ping>b->ping) return 1; if(a->pingping) return -1; return strcmp(a->name, b->name); } void *servmenu = NULL; void refreshservers(void *menu, bool init) { if(init) { if(pingsock == ENET_SOCKET_NULL) { pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, NULL); resolverinit(); } resolverclear(); loopv(servers) resolverquery(servers[i].name); } checkresolver(); checkpings(); if(lastmillis - lastinfo >= 5000) pingservers(); servers.sort(sicompare); loopv(servers) { serverinfo &si = servers[i]; if(si.address.host != ENET_HOST_ANY && si.ping != 9999) { if(si.protocol!=PROTOCOL_VERSION) s_sprintf(si.full)("%s [%s protocol]", si.name, si.protocol") || strstr((char *)reply, "")) conoutf("master server not replying"); else { loopv(servers) delete[] servers[i].name; servers.setsize(0); execute((char *)reply); } } COMMAND(addserver, ARG_1STR); COMMAND(updatefrommaster, ARG_NONE); void writeservercfg() { FILE *f = fopen("config/servers.cfg", "w"); if(!f) return; fprintf(f, "// servers connected to are added here automatically\n\n"); loopvrev(servers) fprintf(f, "addserver %s\n", servers[i].name); fclose(f); }