Load cards from database on startup and admin request only; don't bother reloading for every user or game.

This commit is contained in:
uecasm 2014-04-11 22:52:17 +12:00
parent 4dcf39f5b7
commit 3d77bd341a
5 changed files with 181 additions and 71 deletions

View File

@ -123,6 +123,10 @@ String reloadProps = request.getParameter("reloadProps");
if ("true".equals(reloadProps)) {
StartupUtils.reloadProperties(this.getServletContext());
}
String reloadCards = request.getParameter("reloadCards");
if ("true".equals(reloadCards)) {
StartupUtils.reloadCardSets(this.getServletContext());
}
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
@ -240,6 +244,9 @@ boolean verboseDebug = verboseDebugObj != null ? verboseDebugObj.booleanValue()
<p>
<a href="?reloadProps=true">Reload pyx.properties.</a>
</p>
<p>
<a href="?reloadCards=true">Reload card sets.</a>
</p>
</body>
</html>

View File

@ -33,6 +33,8 @@ import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import net.socialgamer.cah.data.CardSets;
import org.apache.log4j.PropertyConfigurator;
import com.google.inject.Guice;
@ -108,6 +110,7 @@ public class StartupUtils extends GuiceServletContextListener {
reconfigureLogging(contextEvent.getServletContext());
reloadProperties(contextEvent.getServletContext());
reloadCardSets(contextEvent.getServletContext());
}
public static void reloadProperties(final ServletContext context) {
@ -128,6 +131,14 @@ public class StartupUtils extends GuiceServletContextListener {
"/WEB-INF/log4j.properties"));
}
public static void reloadCardSets(final ServletContext context) {
final Injector injector = (Injector) context.getAttribute(INJECTOR);
final CardSets cardSets = injector.getInstance(CardSets.class);
// get the list of card sets
cardSets.reloadAll();
}
@Override
protected Injector getInjector() {
return Guice.createInjector(new CahModule());

View File

@ -0,0 +1,138 @@
package net.socialgamer.cah.data;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import net.socialgamer.cah.Constants.CardSetData;
import net.socialgamer.cah.db.CardSet;
import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.hibernate.Transaction;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
/**
* An in-memory cache for all card sets. Loaded at server startup to prevent unnecessary database traffic.
*
* @author Gavin Lambert (uecasm)
*
*/
@Singleton
public class CardSets {
private static final Logger logger = Logger.getLogger(CardSets.class);
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
private final Map<Integer, CardSet> sets = new HashMap<Integer, CardSet>();
private final Provider<Session> sessionProvider;
private final Properties properties;
private List<Map<CardSetData, Object>> cardSetsData = new ArrayList<Map<CardSetData, Object>>();
@Inject
public CardSets(final Provider<Session> sessionProvider, final Properties properties) {
this.sessionProvider = sessionProvider;
this.properties = properties;
}
/**
* Reload all card sets from the backing database.
*/
public void reloadAll() {
Session session = null;
try {
session = sessionProvider.get();
final Transaction transaction = session.beginTransaction();
@SuppressWarnings("unchecked")
final List<CardSet> cardSets = session
.createQuery(CardSet.getCardsetQuery(properties))
.setReadOnly(true)
.list();
final List<Map<CardSetData, Object>> data = getClientMetadata(cardSets);
transaction.commit();
lock.writeLock().lock();
try {
sets.clear();
for (final CardSet cardSet : cardSets) {
sets.put(cardSet.getId(), cardSet);
}
cardSetsData = data;
} finally {
lock.writeLock().unlock();
}
} catch (final Exception e) {
logger.error("Unable to load cards", e);
} finally {
if (null != session) {
session.close();
}
}
}
/**
* Given a set of card-set ids, returns the actual card sets. Used when starting a game.
*
* @param ids The ids of the card-sets to return.
* @return The specified subset of card sets.
*/
public Set<CardSet> findById(final Collection<Integer> ids)
{
if (ids.isEmpty()) {
return Collections.emptySet();
}
final Set<CardSet> cardSets = new HashSet<CardSet>(ids.size());
lock.readLock().lock();
try {
for (final Integer i : ids) {
final CardSet set = sets.get(i);
if (set != null) {
cardSets.add(set);
}
}
} finally {
lock.readLock().unlock();
}
return cardSets;
}
/**
* Returns basic deck metadata for all card sets. Used at login to populate the game options.
*
* @return The list of card-set client-side metadata.
*/
public List<Map<CardSetData, Object>> getClientMetadata() {
lock.readLock().lock();
try {
return cardSetsData;
} finally {
lock.readLock().unlock();
}
}
/**
* Returns basic deck metadata for the specified card sets. As a side effect, also
* loads internal collections.
*
* @param cardSets The list of card sets for which to retrieve metadata.
* @return The metadata for the specified sets.
*/
private List<Map<CardSetData, Object>> getClientMetadata(final List<CardSet> cardSets) {
final List<Map<CardSetData, Object>> data = new ArrayList<Map<CardSetData, Object>>();
for (final CardSet cardSet : cardSets) {
data.add(cardSet.getClientMetadata());
}
return data;
}
}

View File

@ -59,10 +59,8 @@ import net.socialgamer.cah.db.CardSet;
import net.socialgamer.cah.db.WhiteCard;
import org.apache.log4j.Logger;
import org.hibernate.Session;
import com.google.inject.Inject;
import com.google.inject.Provider;
/**
@ -115,7 +113,7 @@ public class Game {
private final ConnectedUsers connectedUsers;
private final GameManager gameManager;
private Player host;
private final Provider<Session> sessionProvider;
private final CardSets cardSets;
private BlackDeck blackDeck;
private BlackCard blackCard;
private final Object blackCardLock = new Object();
@ -184,18 +182,18 @@ public class Game {
* @param gameManager
* The game manager, for broadcasting game list refresh notices and destroying this game
* when everybody leaves.
* @param hibernateSession Hibernate session from which to load cards.
* @param globalTimer The global timer on which to schedule tasks.
* @param cardSets The card-set manager.
*/
@Inject
public Game(@GameId final Integer id, final ConnectedUsers connectedUsers,
final GameManager gameManager, final ScheduledThreadPoolExecutor globalTimer,
final Provider<Session> sessionProvider) {
final CardSets cardSets) {
this.id = id;
this.connectedUsers = connectedUsers;
this.gameManager = gameManager;
this.globalTimer = globalTimer;
this.sessionProvider = sessionProvider;
this.cardSets = cardSets;
state = GameState.LOBBY;
}
@ -672,25 +670,15 @@ public class Game {
id, cardSetIds, blanksInDeck, playerLimit, spectatorLimit, scoreGoal, players));
// do this stuff outside the players lock; they will lock players again later for much less
// time, and not at the same time as trying to lock users, which has caused deadlocks
Set<CardSet> sets;
synchronized (cardSetIds) {
Session session = null;
try {
session = sessionProvider.get();
@SuppressWarnings("unchecked")
final List<CardSet> cardSets = session.createQuery("from CardSet where id in (:ids)")
.setParameterList("ids", cardSetIds).list();
blackDeck = new BlackDeck(cardSets);
whiteDeck = new WhiteDeck(cardSets, blanksInDeck);
} catch (final Exception e) {
logger.error(String.format("Unable to load cards to start game %d", id), e);
return false;
} finally {
if (null != session) {
session.close();
}
}
sets = cardSets.findById(options.cardSetIds);
}
blackDeck = new BlackDeck(sets);
whiteDeck = new WhiteDeck(sets, blanksInDeck);
startNextRound();
gameManager.broadcastGameListRefresh();
}
@ -698,28 +686,16 @@ public class Game {
}
public boolean hasBaseDeck() {
Set<CardSet> sets;
synchronized (cardSetIds) {
if (cardSetIds.isEmpty()) {
return false;
}
Session session = null;
try {
session = sessionProvider.get();
final Number baseDeckCount = (Number) session
.createQuery("select count(*) from CardSet where id in (:ids) and base_deck = true")
.setParameterList("ids", cardSetIds).uniqueResult();
return baseDeckCount.intValue() > 0;
} catch (final Exception e) {
logger.error(String.format("Unable to determine if game %d has base deck", id), e);
return false;
} finally {
if (null != session) {
session.close();
}
sets = cardSets.findById(cardSetIds);
}
for (final CardSet set : sets) {
if (set.isBaseDeck()) {
return true;
}
}
return false;
}
/**

View File

@ -23,11 +23,9 @@
package net.socialgamer.cah.handlers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpSession;
@ -38,11 +36,8 @@ import net.socialgamer.cah.Constants.ReconnectNextAction;
import net.socialgamer.cah.Constants.ReturnableData;
import net.socialgamer.cah.Constants.SessionAttribute;
import net.socialgamer.cah.RequestWrapper;
import net.socialgamer.cah.data.CardSets;
import net.socialgamer.cah.data.User;
import net.socialgamer.cah.db.CardSet;
import org.hibernate.Session;
import org.hibernate.Transaction;
import com.google.inject.Inject;
@ -57,13 +52,11 @@ public class FirstLoadHandler extends Handler {
public static final String OP = AjaxOperation.FIRST_LOAD.toString();
private final Session hibernateSession;
private final Properties properties;
private final CardSets cardSets;
@Inject
public FirstLoadHandler(final Session hibernateSession, final Properties properties) {
this.hibernateSession = hibernateSession;
this.properties = properties;
public FirstLoadHandler(final CardSets cardSets) {
this.cardSets = cardSets;
}
@Override
@ -78,6 +71,7 @@ public class FirstLoadHandler extends Handler {
} else {
// They already have a session in progress, we need to figure out what they were doing
// and tell the client where to continue from.
// Right now we just tell them what their name is.
ret.put(AjaxResponse.IN_PROGRESS, Boolean.TRUE);
ret.put(AjaxResponse.NICKNAME, user.getNickname());
@ -89,24 +83,8 @@ public class FirstLoadHandler extends Handler {
}
}
try {
// get the list of card sets
final Transaction transaction = hibernateSession.beginTransaction();
@SuppressWarnings("unchecked")
final List<CardSet> cardSets = hibernateSession
.createQuery(CardSet.getCardsetQuery(properties))
.setReadOnly(true)
.list();
final List<Map<CardSetData, Object>> cardSetsData =
new ArrayList<Map<CardSetData, Object>>(cardSets.size());
for (final CardSet cardSet : cardSets) {
cardSetsData.add(cardSet.getClientMetadata(hibernateSession));
}
ret.put(AjaxResponse.CARD_SETS, cardSetsData);
transaction.commit();
} finally {
hibernateSession.close();
}
final List<Map<CardSetData, Object>> cardSetsData = cardSets.getClientMetadata();
ret.put(AjaxResponse.CARD_SETS, cardSetsData);
return ret;
}