I’ve finished writing a ranking server for RakNet. It is a C++ interface to a PostgreSQL database that stores a table that contains a list of matches between two participants. It’s very general – participants, games, scores, ratings, are just numbers. You can store a text string and arbitrary binary data for each match. It uses my functor system so calls are asynchronous.
There are currently 4 functors:
SubmitMatch – Submits the match results between a pair of participants.
GetRatingForParticipant – Get the most recent rating for a single participants.
GetRatingForParticipants – Get the most recent rating for a list of participants.
GetHistoryForParticipant – Get all the matches and the data therein for a particular participant.
I store IDs as a pair of unsigned integers. The intent is that the primary integer can represent a foreign key. The secondary integer can represent a sub-type. In Galactic Melee for example I store rankings for both players and squads, so the secondary integer would differentiate between these.
The class is designed in such a way as to be easily extensible while still easy to use. Generally speaking the more extensible something is, the harder it is to use because there are more pieces involved, so you have a lot more to learn. This is true of Gamebryo for example, which has good division of code but then requires a lot of steps to do anything. I wanted to avoid this, so went to a lot of extra work to make as much automatic as possible.
One nice thing is you can add more queries without changing the original source code. This is because each query is just a derived class.
The current table format is:
CREATE TABLE rankingServer (
rowId serial UNIQUE,
participantADbId_primaryKey integer NOT NULL,
participantADbId_secondaryKey integer NOT NULL,
participantBDbId_primaryKey integer NOT NULL,
participantBDbId_secondaryKey integer NOT NULL,
participantAScore real,
participantBScore integer,
participantAOldRating real,
participantANewRating real NOT NULL,
participantBOldRating real,
participantBNewRating real NOT NULL,
gameDbId_primaryKey integer NOT NULL,
gameDbId_secondaryKey integer NOT NULL,
matchTime bigint NOT NULL DEFAULT EXTRACT(EPOCH FROM current_timestamp),
matchNotes text,
matchBinaryData bytea);
Here’s an example of calling a Functor
GetRatingForParticipants_PostgreSQLImpl *functor = GetRatingForParticipants_PostgreSQLImpl::Alloc();
printf("Get the most recent rating for all participants\n");
printf("Enter the primary ID of the game (int): ");
gets(inputStr);
functor->gameDbId.primaryKey = atoi(inputStr);
printf("Enter the secondary ID of the game (int): ");
gets(inputStr);
functor->gameDbId.secondaryKey = atoi(inputStr);
/// Puts this functor on the processing queue. It will process sometime later in a thread.
rankingServer.PushFunctor(functor);
This is all part of my plan to write a complete lobby server, of which ranking and match tracking is one component.