Tidied things up, avoid doing things on the invoker thread that should be done

on the dobj thread.


git-svn-id: svn+ssh://src.earth.threerings.net/vilya/trunk@408 c613c5cb-e716-0410-b11b-feb51c14d237
This commit is contained in:
Michael Bayne
2007-08-22 00:09:57 +00:00
parent df1b8277fb
commit f6481922f6
@@ -27,11 +27,12 @@ import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import com.samskivert.io.PersistenceException; import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.RepositoryUnit;
import com.samskivert.util.ArrayIntSet;
import com.samskivert.util.ArrayUtil; import com.samskivert.util.ArrayUtil;
import com.samskivert.util.HashIntMap; import com.samskivert.util.HashIntMap;
import com.samskivert.util.IntMap; import com.samskivert.util.IntMap;
import com.samskivert.util.Invoker;
import com.threerings.crowd.data.PlaceObject; import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.server.CrowdServer; import com.threerings.crowd.server.CrowdServer;
@@ -48,22 +49,15 @@ import com.threerings.util.Name;
/** /**
* Rates players after each game and handles persisting the results. * Rates players after each game and handles persisting the results.
*/ */
public abstract class RatingManagerDelegate public abstract class RatingManagerDelegate extends GameManagerDelegate
extends GameManagerDelegate
{ {
/** /** The minimum rating value. */
* The minimum rating value.
*/
public static final int MINIMUM_RATING = 1000; public static final int MINIMUM_RATING = 1000;
/** /** The default rating value. */
* The default rating value.
*/
public static final int DEFAULT_RATING = 1200; public static final int DEFAULT_RATING = 1200;
/** /** The maximum rating value. */
* The maximum rating value.
*/
public static final int MAXIMUM_RATING = 3000; public static final int MAXIMUM_RATING = 3000;
/** /**
@@ -73,19 +67,17 @@ public abstract class RatingManagerDelegate
{ {
super(gmgr); super(gmgr);
_gmgr = gmgr; _gmgr = gmgr;
_repo = getRatingRepository(); _repo = getRatingRepository();
} }
@Override @Override // from PlaceManagerDelegate
public void didStartup (PlaceObject plobj) public void didStartup (PlaceObject plobj)
{ {
super.didStartup(plobj); super.didStartup(plobj);
_gobj = (GameObject) plobj; _gobj = (GameObject) plobj;
} }
@Override @Override // from GameManagerDelegate
public void gameWillStart () public void gameWillStart ()
{ {
super.gameWillStart(); super.gameWillStart();
@@ -93,64 +85,57 @@ public abstract class RatingManagerDelegate
// note the time at which we started // note the time at which we started
_startStamp = (int) (System.currentTimeMillis() / 1000); _startStamp = (int) (System.currentTimeMillis() / 1000);
// read ratings from persistent store final int gameId = _gmgr.getGameConfig().getGameId();
CrowdServer.invoker.postUnit(new Invoker.Unit() {
public boolean invoke () {
int gameId = _gmgr.getGameConfig().getGameId();
// enumerate our non-guest players // enumerate our non-guest players
Integer[] allPlayers = new Integer[_gmgr.getPlayerCount()]; final Integer[] allPlayers = new Integer[_gmgr.getPlayerCount()];
Integer[] ratedPlayers = new Integer[allPlayers.length]; ArrayIntSet ratedSet = new ArrayIntSet();
int jj = 0; for (int ii = 0; ii < allPlayers.length; ii ++) {
for (int ii = 0; ii < allPlayers.length; ii ++) { allPlayers[ii] = _gmgr.getPlayerPersistentId(_gmgr.getPlayer(ii));
allPlayers[ii] = _gmgr.getPlayerPersistentId(_gmgr.getPlayer(ii)); if (allPlayers[ii] != 0) {
if (allPlayers[ii] != 0) { // this is a player with a persistent id, a player we can rate
// this is a player with a persistent id, a player we can rate ratedSet.add(allPlayers[ii]);
ratedPlayers[jj ++] = allPlayers[ii];
}
}
ratedPlayers = ArrayUtil.splice(ratedPlayers, jj);
try {
// fetch the previous ratings of our persistent users, for this game
List<RatingRecord> records = _repo.getRatings(gameId, ratedPlayers);
// and make it easy to look them up by their id
IntMap<RatingRecord> map = new HashIntMap<RatingRecord>();
for (RatingRecord record : records) {
map.put(record.playerId, record);
}
// now build the array we keep around until the end of the game
_ratings = new Rating[allPlayers.length];
for (int ii = 0; ii < _ratings.length; ii ++) {
if (allPlayers[ii] == 0) {
// for guests we let the slot remain null
continue;
}
RatingRecord record = map.get(allPlayers[ii]);
// if the player had no previous record, initiate them at default values
_ratings[ii] = record != null ?
new Rating(allPlayers[ii], record.rating, record.experience) :
new Rating(allPlayers[ii]);
}
return true;
} catch (PersistenceException pe) {
log.log(Level.WARNING,
"Failed to load ratings [where=" + where() + ", id=" + gameId + "].", pe);
return false;
}
};
public void handleResult () {
// nothing to do here?
} }
}
final Integer[] ratedPlayers = ratedSet.toArray(new Integer[ratedSet.size()]);
// read ratings from persistent store
CrowdServer.invoker.postUnit(new RepositoryUnit("loadRatings") {
public void invokePersist () throws Exception {
// fetch the previous ratings of our persistent users and map them by player id
IntMap<RatingRecord> map = new HashIntMap<RatingRecord>();
for (RatingRecord record : _repo.getRatings(gameId, ratedPlayers)) {
map.put(record.playerId, record);
}
// now build the array we keep around until the end of the game
_ratings = new Rating[allPlayers.length];
for (int ii = 0; ii < _ratings.length; ii ++) {
if (allPlayers[ii] == 0) {
continue; // for guests we let the slot remain null
}
RatingRecord record = map.get(allPlayers[ii]);
// if the player had no previous record, initiate them at default values
_ratings[ii] = (record == null) ? new Rating(allPlayers[ii]) :
new Rating(allPlayers[ii], record.rating, record.experience);
}
}
public void handleSuccess () {
// stick our ratings into our manager
_ratings = _tratings;
}
public void handleFailure (Exception e) {
log.log(Level.WARNING, "Failed to load ratings [where=" + where() +
", id=" + gameId + "].", e);
}
protected Rating[] _tratings;
}); });
} }
@Override @Override // from GameManagerDelegate
public void gameDidEnd () public void gameDidEnd ()
{ {
super.gameDidEnd(); super.gameDidEnd();
@@ -188,34 +173,28 @@ public abstract class RatingManagerDelegate
// else persist the result // else persist the result
final int gameId = _gmgr.getGameConfig().getGameId(); final int gameId = _gmgr.getGameConfig().getGameId();
CrowdServer.invoker.postUnit(new Invoker.Unit() { CrowdServer.invoker.postUnit(new RepositoryUnit("updateRatings") {
public boolean invoke () { public void invokePersist () throws Exception {
try { for (Rating rating : _ratings) {
for (int ii = 0; ii < _ratings.length; ii ++) { if (rating != null) {
if (_ratings[ii] != null) { _repo.setRating(gameId, rating.playerId, rating.rating, rating.experience);
// for each rated player, update or create the rating record
// TODO: reorganize things so this can be a single db request?
_repo.setRating(gameId, _ratings[ii].playerId,
_ratings[ii].rating, _ratings[ii].experience);
}
} }
return true;
} catch (PersistenceException pe) {
log.log(Level.WARNING,
"Failed to load ratings [where=" + where() + ", id=" + gameId + "].", pe);
return false;
} }
}; }
public void handleResult () { public void handleSuccess () {
// let subclasses store away the new ratings if they so desire for (int ii = 0; ii < _ratings.length; ii++) {
for (int ii = 0; ii < _ratings.length; ii ++) {
if (_ratings[ii] != null) { if (_ratings[ii] != null) {
// let subclasses publish the new ratings if they so desire
updateRatingInMemory(gameId, _gmgr.getPlayerName(ii), _ratings[ii]); updateRatingInMemory(gameId, _gmgr.getPlayerName(ii), _ratings[ii]);
} }
} }
} }
public void handleFailure (Exception e) {
log.log(Level.WARNING, "Failed to update ratings [where=" + where() +
", id=" + gameId + "].", e);
}
}); });
} }
@@ -237,13 +216,13 @@ public abstract class RatingManagerDelegate
{ {
float totDeltaR = 0; // the total delta rating float totDeltaR = 0; // the total delta rating
int opponents = 0; int opponents = 0;
for (int ii = 0; ii < _ratings.length; ii++) { for (int ii = 0; ii < _ratings.length; ii++) {
// we don't care how we did against ourselves, or against guests... // we don't care how we did against ourselves, or against guests...
if (pidx == ii || _ratings[ii] == null) { if (pidx == ii || _ratings[ii] == null) {
continue; continue;
} }
// if we are non-provisional, and the opponent is provisional, we // if we are non-provisional, and the opponent is provisional, we
// max the opponent out at the default rating to avoid potentially // max the opponent out at the default rating to avoid potentially
// inflating a real rating with one that has a lot of uncertainty // inflating a real rating with one that has a lot of uncertainty
@@ -263,7 +242,7 @@ public abstract class RatingManagerDelegate
} else { } else {
K = 16; // experience >= 20 && rating >= 2400 K = 16; // experience >= 20 && rating >= 2400
} }
// calculate W, the win value representing the actual result of the game // calculate W, the win value representing the actual result of the game
float W = _gobj.isDraw() ? 0.5f : _gobj.isWinner(pidx) ? 1f : 0f; float W = _gobj.isDraw() ? 0.5f : _gobj.isWinner(pidx) ? 1f : 0f;
// calculate We, the expected win value given the players' respective ratings // calculate We, the expected win value given the players' respective ratings
@@ -274,7 +253,7 @@ public abstract class RatingManagerDelegate
totDeltaR += K * (W - We); totDeltaR += K * (W - We);
opponents++; opponents++;
} }
// if we have no valid opponents, we cannot compute a rating; // if we have no valid opponents, we cannot compute a rating;
if (opponents == 0) { if (opponents == 0) {
return -1; return -1;
@@ -285,41 +264,6 @@ public abstract class RatingManagerDelegate
return MathUtil.bound(MINIMUM_RATING, nrat, MAXIMUM_RATING); return MathUtil.bound(MINIMUM_RATING, nrat, MAXIMUM_RATING);
} }
/**
* Simply encapsulates the rating/experience tuple representing a player's rating for a game.
*/
protected static class Rating
{
/** The id of the rated player. */
public int playerId;
/** The player's rating for our game. */
public int rating;
/** The number of times the player's played our game. */
public int experience;
/**
* Sets up a new {@link Rating} object with default values.
*/
public Rating (int playerId)
{
this.playerId = playerId;
this.rating = DEFAULT_RATING;
this.experience = 0;
}
/**
* Sets up a new {@link Rating} object with the given values.
*/
public Rating (int playerId, int rating, int experience)
{
this.playerId = playerId;
this.rating = rating;
this.experience = experience;
}
}
/** /**
* Return the minimum time (in seconds) a game must've lasted for it to count towards rating. * Return the minimum time (in seconds) a game must've lasted for it to count towards rating.
*/ */
@@ -332,18 +276,52 @@ public abstract class RatingManagerDelegate
/** /**
* Optionally store update ratings in memory e.g. in the user object. * Optionally store update ratings in memory e.g. in the user object.
* *
* This method is called on the dobj thread. * This method is called on the dobj thread.
*/ */
protected abstract void updateRatingInMemory (int gameId, Name playerName, Rating rating); protected abstract void updateRatingInMemory (int gameId, Name playerName, Rating rating);
/**
* Simply encapsulates the rating/experience tuple representing a player's rating for a game.
*/
protected static class Rating
{
/** The id of the rated player. */
public int playerId;
/** The player's rating for our game. */
public int rating;
/** The number of times the player's played our game. */
public int experience;
/**
* Sets up a new {@link Rating} object with default values.
*/
public Rating (int playerId)
{
this.playerId = playerId;
this.rating = DEFAULT_RATING;
this.experience = 0;
}
/**
* Sets up a new {@link Rating} object with the given values.
*/
public Rating (int playerId, int rating, int experience)
{
this.playerId = playerId;
this.rating = rating;
this.experience = experience;
}
}
/** An appropriately casted reference to our GameManager. */ /** An appropriately casted reference to our GameManager. */
protected GameManager _gmgr; protected GameManager _gmgr;
/** An appropriately casted reference to our GameObject. */ /** An appropriately casted reference to our GameObject. */
protected GameObject _gobj; protected GameObject _gobj;
/** The RatingRepository that holds our data. */ /** The RatingRepository that holds our data. */
protected RatingRepository _repo; protected RatingRepository _repo;