Load cards from database on startup and admin request only; don't bother reloading for every user or game.
This commit is contained in:
parent
4dcf39f5b7
commit
3d77bd341a
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue