Skip to content

Commit

Permalink
libshell: Improved ServerFinder for continuous operation
Browse files Browse the repository at this point in the history
The finder is now started automatically after construction. Old entries
expire if nothing is heard from the server. ServerFinder will also
parse the contents of the Beacon's message (which contains a Record).
  • Loading branch information
skyjake committed Feb 4, 2013
1 parent 47e2daa commit ec74ffc
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 21 deletions.
16 changes: 14 additions & 2 deletions doomsday/libshell/include/de/shell/serverfinder.h
Expand Up @@ -20,6 +20,7 @@
#define LIBSHELL_SERVERFINDER_H

#include <QObject>
#include <de/Error>
#include <de/Address>
#include <de/Record>

Expand All @@ -33,14 +34,25 @@ class ServerFinder : public QObject
{
Q_OBJECT

public:
/// Specified server was not found. @ingroup errors
DENG2_ERROR(NotFoundError);

public:
ServerFinder();
virtual ~ServerFinder();

void start();
/**
* Forgets all servers found so far.
*/
void clear();

QList<Address> foundServers() const;

String name(Address const &server) const;
int playerCount(Address const &server) const;
int maxPlayers(Address const &server) const;

/**
* Returns the message sent by a server's beacon.
*
Expand All @@ -53,10 +65,10 @@ class ServerFinder : public QObject

protected slots:
void found(de::Address address, de::Block info);
void expire();

signals:
void updated();
void finished();

private:
struct Instance;
Expand Down
112 changes: 93 additions & 19 deletions doomsday/libshell/src/serverfinder.cpp
Expand Up @@ -19,80 +19,154 @@
#include "de/shell/ServerFinder"
#include <de/Beacon>
#include <de/Reader>
#include <de/TextValue>
#include <de/NumberValue>
#include <QMap>
#include <QTimer>

namespace de {
namespace shell {

static TimeDelta MSG_EXPIRATION_SECS = 10;

struct ServerFinder::Instance
{
Beacon beacon;
QMap<Address, Record *> messages;
struct Found
{
Record *message;
Time at;

Found() : message(0), at(Time()) {}
};
QMap<Address, Found> servers;

Instance() : beacon(53209) {}
Instance() : beacon(13209) {}

~Instance()
{
clearMessages();
clearServers();
}

void clearMessages()
void clearServers()
{
foreach(Record *msg, messages.values()) delete msg;
messages.clear();
foreach(Found const &found, servers.values())
{
delete found.message;
}
servers.clear();
}

bool removeExpired()
{
bool changed = false;

QMutableMapIterator<Address, Found> iter(servers);
while(iter.hasNext())
{
Found &found = iter.next().value();
if(found.at.since() > MSG_EXPIRATION_SECS)
{
delete found.message;
iter.remove();
changed = true;
}
}

return changed;
}
};

ServerFinder::ServerFinder() : d(new Instance)
{
connect(&d->beacon, SIGNAL(found(de::Address, de::Block)), this, SLOT(found(de::Address, de::Block)));
connect(&d->beacon, SIGNAL(finished()), this, SIGNAL(finished()));
QTimer::singleShot(1000, this, SLOT(expire()));

d->beacon.discover(0 /* no timeout */, 2);
}

ServerFinder::~ServerFinder()
{
delete d;
}

void ServerFinder::start()
void ServerFinder::clear()
{
d->clearMessages();
d->beacon.discover(20);
d->clearServers();
}

QList<Address> ServerFinder::foundServers() const
{
return d->beacon.foundHosts();
return d->servers.keys();
}

String ServerFinder::name(Address const &server) const
{
return messageFromServer(server)["name"].value<TextValue>();
}

int ServerFinder::playerCount(Address const &server) const
{
return messageFromServer(server)["nump"].value<NumberValue>().as<int>();
}

int ServerFinder::maxPlayers(Address const &server) const
{
return messageFromServer(server)["maxp"].value<NumberValue>().as<int>();
}

Record const &ServerFinder::messageFromServer(Address const &address) const
{
return *d->messages[address];
if(!d->servers.contains(address))
{
/// @throws NotFoundError @a address not found in the registry of server responses.
throw NotFoundError("ServerFinder::messageFromServer",
"No message from server " + address.asText());
}
return *d->servers[address].message;
}

void ServerFinder::found(Address host, Block block)
{
try
{
LOG_DEBUG("Received a server message from %s with %i bytes")
LOG_TRACE("Received a server message from %s with %i bytes")
<< host << block.size();

// Replace or insert the information for this host.
Record *rec;
if(d->messages.contains(host))
Instance::Found found;
if(d->servers.contains(host))
{
rec = d->messages[host];
found.message = d->servers[host].message;
d->servers[host].at = Time();
}
else
{
d->messages.insert(host, rec = new Record);
found.message = new Record;
d->servers.insert(host, found);
}
Reader(block).withHeader() >> *rec;
Reader(block).withHeader() >> *found.message;

emit updated();
}
catch(Error const &)
{}
{
// Remove the message that failed to deserialize.
if(d->servers.contains(host))
{
delete d->servers[host].message;
d->servers.remove(host);
}
}
}

void ServerFinder::expire()
{
if(d->removeExpired())
{
emit updated();
}
QTimer::singleShot(1000, this, SLOT(expire()));
}

} // namespace shell
Expand Down

0 comments on commit ec74ffc

Please sign in to comment.