// serverbrowser.cpp: eihrul's concurrent resolver, and server browser window
// management
#include "SDL_thread.h"
#include "cube.h"
struct resolverthread {
SDL_Thread *thread;
char *query;
int starttime;
};
struct resolverresult {
char *query;
ENetAddress address;
};
vector<resolverthread> resolverthreads;
vector<char *> resolverqueries;
vector<resolverresult> resolverresults;
SDL_mutex *resolvermutex;
SDL_sem *resolversem;
int resolverlimit = 1000;
int
resolverloop(void *data)
{
resolverthread *rt = (resolverthread *)data;
for (;;) {
SDL_SemWait(resolversem);
SDL_LockMutex(resolvermutex);
if (resolverqueries.empty()) {
SDL_UnlockMutex(resolvermutex);
continue;
}
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);
resolverresult &rr = resolverresults.add();
rr.query = rt->query;
rr.address = address;
rt->query = NULL;
rt->starttime = 0;
SDL_UnlockMutex(resolvermutex);
};
return 0;
};
void
resolverinit(int threads, int limit)
{
resolverlimit = limit;
resolversem = SDL_CreateSemaphore(0);
resolvermutex = SDL_CreateMutex();
while (threads > 0) {
resolverthread &rt = resolverthreads.add();
rt.query = NULL;
rt.starttime = 0;
rt.thread = SDL_CreateThread(resolverloop, &rt);
--threads;
};
};
void
resolverstop(resolverthread &rt, bool restart)
{
SDL_LockMutex(resolvermutex);
SDL_KillThread(rt.thread);
rt.query = NULL;
rt.starttime = 0;
rt.thread = NULL;
if (restart)
rt.thread = SDL_CreateThread(resolverloop, &rt);
SDL_UnlockMutex(resolvermutex);
};
void
resolverclear()
{
SDL_LockMutex(resolvermutex);
resolverqueries.setsize(0);
resolverresults.setsize(0);
while (SDL_SemTryWait(resolversem) == 0)
;
loopv(resolverthreads)
{
resolverthread &rt = resolverthreads[i];
resolverstop(rt, true);
};
SDL_UnlockMutex(resolvermutex);
};
void
resolverquery(char *name)
{
SDL_LockMutex(resolvermutex);
resolverqueries.add(name);
SDL_SemPost(resolversem);
SDL_UnlockMutex(resolvermutex);
};
bool
resolvercheck(char **name, ENetAddress *address)
{
SDL_LockMutex(resolvermutex);
if (!resolverresults.empty()) {
resolverresult &rr = resolverresults.pop();
*name = rr.query;
*address = rr.address;
SDL_UnlockMutex(resolvermutex);
return true;
}
loopv(resolverthreads)
{
resolverthread &rt = resolverthreads[i];
if (rt.query) {
if (lastmillis - rt.starttime > resolverlimit) {
resolverstop(rt, true);
*name = rt.query;
SDL_UnlockMutex(resolvermutex);
return true;
};
};
};
SDL_UnlockMutex(resolvermutex);
return false;
};
struct serverinfo {
string name;
string full;
string map;
string sdesc;
int mode, numplayers, ping, protocol, minremain;
ENetAddress address;
};
vector<serverinfo> 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());
strcpy_s(si.name, servername);
si.full[0] = 0;
si.mode = 0;
si.numplayers = 0;
si.ping = 9999;
si.protocol = 0;
si.minremain = 0;
si.map[0] = 0;
si.sdesc[0] = 0;
si.address.host = ENET_HOST_ANY;
si.address.port = CUBE_SERVINFO_PORT;
};
void
pingservers()
{
ENetBuffer buf;
uchar ping[MAXTRANS];
uchar *p;
loopv(servers)
{
serverinfo &si = servers[i];
if (si.address.host == ENET_HOST_ANY)
continue;
p = ping;
putint(p, lastmillis);
buf.data = ping;
buf.dataLength = p - ping;
enet_socket_send(pingsock, &si.address, &buf, 1);
};
lastinfo = lastmillis;
};
void
checkresolver()
{
char *name = NULL;
ENetAddress addr = {ENET_HOST_ANY, CUBE_SERVINFO_PORT};
while (resolvercheck(&name, &addr)) {
if (addr.host == ENET_HOST_ANY)
continue;
loopv(servers)
{
serverinfo &si = servers[i];
if (name == si.name) {
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], *p;
char text[MAXTRANS];
buf.data = ping;
buf.dataLength = sizeof(ping);
while (enet_socket_wait(pingsock, &events, 0) >= 0 && events) {
if (enet_socket_receive(pingsock, &addr, &buf, 1) <= 0)
return;
loopv(servers)
{
serverinfo &si = servers[i];
if (addr.host == si.address.host) {
p = ping;
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);
sgetstr();
strcpy_s(si.map, text);
sgetstr();
strcpy_s(si.sdesc, text);
break;
};
};
};
};
int
sicompare(const serverinfo *a, const serverinfo *b)
{
return a->ping > b->ping
? 1
: (a->ping < b->ping ? -1 : strcmp(a->name, b->name));
};
void
refreshservers()
{
checkresolver();
checkpings();
if (lastmillis - lastinfo >= 5000)
pingservers();
servers.sort((void *)sicompare);
int maxmenu = 16;
loopv(servers)
{
serverinfo &si = servers[i];
if (si.address.host != ENET_HOST_ANY && si.ping != 9999) {
if (si.protocol != PROTOCOL_VERSION)
sprintf_s(si.full)(
"%s [different cube protocol]", si.name);
else
sprintf_s(si.full)("%d\t%d\t%s, %s: %s %s",
si.ping, si.numplayers,
si.map[0] ? si.map : "[unknown]",
modestr(si.mode), si.name, si.sdesc);
} else {
sprintf_s(si.full)(
si.address.host != ENET_HOST_ANY
? "%s [waiting for server response]"
: "%s [unknown host]\t",
si.name);
}
si.full[50] = 0; // cut off too long server descriptions
menumanual(1, i, si.full);
if (!--maxmenu)
return;
};
};
void
servermenu()
{
if (pingsock == ENET_SOCKET_NULL) {
pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, NULL);
resolverinit(1, 1000);
};
resolverclear();
loopv(servers) resolverquery(servers[i].name);
refreshservers();
menuset(1);
};
void
updatefrommaster()
{
const int MAXUPD = 32000;
uchar buf[MAXUPD];
uchar *reply = retrieveservers(buf, MAXUPD);
if (!*reply || strstr((char *)reply, "<html>") ||
strstr((char *)reply, "<HTML>"))
conoutf(@"master server not replying");
else {
servers.setsize(0);
execute((char *)reply);
};
servermenu();
};
COMMAND(addserver, ARG_1STR);
COMMAND(servermenu, ARG_NONE);
COMMAND(updatefrommaster, ARG_NONE);
void
writeservercfg()
{
FILE *f = fopen("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);
};