Making table draft thread safe and reducing the number of public method calls

This commit is contained in:
jakub.salavec
2025-03-24 13:58:52 +01:00
parent c672b43d7e
commit b0815ee601
4 changed files with 235 additions and 226 deletions

View File

@@ -218,101 +218,6 @@ public class TableDraftRequestHandler extends LotroServerRequestHandler implemen
}
private void writeXml(ResponseWriter responseWriter, DraftPlayer draftPlayer) throws ParserConfigurationException {
DraftDocBuilder draftDocBuilder = new DraftDocBuilder();
responseWriter.writeXmlResponse(draftDocBuilder.getDocument(draftPlayer));
}
private class DraftDocBuilder{
DraftPlayer draftPlayer;
public Document getDocument(DraftPlayer draftPlayer) throws ParserConfigurationException {
this.draftPlayer = draftPlayer;
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document doc = documentBuilder.newDocument();
Element rootElement = doc.createElement("draftStatus");
doc.appendChild(rootElement);
appendPlayersInfo(doc, rootElement);
if (draftPlayer.getCollection() != null) {
appendPickedCards(doc, rootElement);
}
if (draftPlayer.getCardsToPickFrom() != null && !draftPlayer.getCardsToPickFrom().isEmpty()) {
appendAvailablePicks(doc, rootElement);
}
try {
int timeRemaining = draftPlayer.getSecondsRemainingForPick();
appendTimeRemaining(doc, rootElement, timeRemaining);
} catch (IllegalStateException ignore) {
// No timer
}
appendRoundsInfo(doc, rootElement);
return doc;
}
private void appendPlayersInfo(Document doc, Element rootElement) {
TableDraft.TableStatus tableStatus = draftPlayer.getTableStatus();
for (TableDraft.PlayerStatus playerStatus : tableStatus.getPlayerStatuses()) {
Element playerElement = doc.createElement("player");
playerElement.setAttribute("name", playerStatus.getName());
playerElement.setAttribute("hasChosen", String.valueOf(playerStatus.hasChosenCard()));
rootElement.appendChild(playerElement);
}
Element pickOrder = doc.createElement("pickOrderAscending");
pickOrder.setAttribute("value", String.valueOf(tableStatus.isPickOrderAscending()));
rootElement.appendChild(pickOrder);
}
private void appendTimeRemaining(Document doc, Element rootElement, int timeRemaining) {
Element time = doc.createElement("timeRemaining");
time.setAttribute("value", "" + timeRemaining);
rootElement.appendChild(time);
}
private void appendRoundsInfo(Document doc, Element rootElement) {
Element time = doc.createElement("roundsInfo");
time.setAttribute("currentRound", "" + draftPlayer.getTableStatus().getCurrentRound());
time.setAttribute("roundsTotal", "" + draftPlayer.getTableStatus().getRoundsTotal());
rootElement.appendChild(time);
}
private void appendPickedCards(Document doc, Element rootElement) {
SortAndFilterCards sortAndFilterCards = new SortAndFilterCards();
List<CardCollection.Item> pickedCards = sortAndFilterCards.process(
"sort:cardType,culture,name",
draftPlayer.getCollection().getAll(),
_cardLibrary,
_formatLibrary
);
for (CardCollection.Item item : pickedCards) {
Element pickedCard = doc.createElement("pickedCard");
pickedCard.setAttribute("blueprintId", item.getBlueprintId());
pickedCard.setAttribute("count", String.valueOf(item.getCount()));
rootElement.appendChild(pickedCard);
}
}
private void appendAvailablePicks(Document doc, Element rootElement) {
for (String availableChoice : draftPlayer.getCardsToPickFrom()) {
Element availablePick = doc.createElement("availablePick");
availablePick.setAttribute("id", availableChoice);
availablePick.setAttribute("blueprintId", availableChoice);
if (draftPlayer.getChosenCard() != null && draftPlayer.getChosenCard().equals(availableChoice)) {
availablePick.setAttribute("chosen", "true");
}
rootElement.appendChild(availablePick);
}
}
responseWriter.writeXmlResponse(draftPlayer.getDocument(_cardLibrary, _formatLibrary));
}
}

View File

@@ -1,7 +1,11 @@
package com.gempukku.lotro.draft3;
import com.gempukku.lotro.game.CardCollection;
import com.gempukku.lotro.game.LotroCardBlueprintLibrary;
import com.gempukku.lotro.game.formats.LotroFormatLibrary;
import org.w3c.dom.Document;
import javax.xml.parsers.ParserConfigurationException;
import java.util.List;
import java.util.Objects;
@@ -38,14 +42,14 @@ public class DraftPlayer {
return table.getChosenCard(this);
}
public TableDraft.TableStatus getTableStatus() {
return table.getTableStatus();
}
public boolean draftFinished() {
return table.isFinished();
}
public Document getDocument(LotroCardBlueprintLibrary cardLibrary, LotroFormatLibrary formatLibrary) throws ParserConfigurationException {
return table.getDocument(this, cardLibrary, formatLibrary);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@@ -1,7 +1,11 @@
package com.gempukku.lotro.draft3;
import com.gempukku.lotro.game.CardCollection;
import com.gempukku.lotro.game.LotroCardBlueprintLibrary;
import com.gempukku.lotro.game.formats.LotroFormatLibrary;
import org.w3c.dom.Document;
import javax.xml.parsers.ParserConfigurationException;
import java.util.List;
public interface TableDraft {
@@ -15,53 +19,5 @@ public interface TableDraft {
boolean isFinished();
int getSecondsRemainingForPick() throws IllegalStateException;
CardCollection getPickedCards(DraftPlayer draftPlayer);
TableStatus getTableStatus();
class PlayerStatus {
private final String name;
private final boolean hasChosenCard;
public PlayerStatus(String name, boolean hasChosenCard) {
this.name = name;
this.hasChosenCard = hasChosenCard;
}
public String getName() {
return name;
}
public boolean hasChosenCard() {
return hasChosenCard;
}
}
class TableStatus {
private final List<PlayerStatus> playerStatuses;
private final boolean pickOrderAscending;
private final int currentRound;
private final int roundsTotal;
public TableStatus(List<PlayerStatus> playerStatuses, boolean pickOrderAscending, int currentRound, int roundsTotal) {
this.playerStatuses = playerStatuses;
this.pickOrderAscending = pickOrderAscending;
this.currentRound = currentRound;
this.roundsTotal = roundsTotal;
}
public List<PlayerStatus> getPlayerStatuses() {
return playerStatuses;
}
public boolean isPickOrderAscending() {
return pickOrderAscending;
}
public int getCurrentRound() {
return currentRound;
}
public int getRoundsTotal() {
return roundsTotal;
}
}
Document getDocument(DraftPlayer draftPlayer, LotroCardBlueprintLibrary cardLibrary, LotroFormatLibrary formatLibrary) throws ParserConfigurationException;
}

View File

@@ -5,10 +5,14 @@ import com.gempukku.lotro.db.vo.CollectionType;
import com.gempukku.lotro.draft3.bot.DraftBot;
import com.gempukku.lotro.draft3.bot.WeightDraftBot;
import com.gempukku.lotro.draft3.timer.DraftTimer;
import com.gempukku.lotro.game.CardCollection;
import com.gempukku.lotro.game.DefaultCardCollection;
import com.gempukku.lotro.game.MutableCardCollection;
import com.gempukku.lotro.game.*;
import com.gempukku.lotro.game.formats.LotroFormatLibrary;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.sql.SQLException;
import java.util.*;
@@ -16,12 +20,18 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TableDraftClassic implements TableDraft{
//TODO thread locks
private static final int MISSED_CARDS_ALLOWED = 4;
private final ReadWriteLock lock = new ReentrantReadWriteLock(true);
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
private final StartingCollectionProducer startingCollectionProducer;
private final BoosterProducer boosterProducer;
private final int maxPlayers;
@@ -71,48 +81,63 @@ public class TableDraftClassic implements TableDraft{
@Override
public List<String> getCardsToPickFrom(DraftPlayer draftPlayer) {
if (!assignedBoosters.containsKey(draftPlayer)) {
return List.of();
readLock.lock();
try {
if (!assignedBoosters.containsKey(draftPlayer)) {
return List.of();
}
return assignedBoosters.get(draftPlayer).getCardsInPack();
} finally {
readLock.unlock();
}
return assignedBoosters.get(draftPlayer).getCardsInPack();
}
@Override
public String getChosenCard(DraftPlayer draftPlayer) {
return chosenCards.get(draftPlayer);
readLock.lock();
try {
return chosenCards.get(draftPlayer);
} finally {
readLock.unlock();
}
}
@Override
public void advanceDraft() {
if (isFinished()) {
return;
}
writeLock.lock();
try {
if (isFinished()) {
return;
}
if (currentRound == 0) {
startDraft();
startTimer();
makeBotsDeclare();
} else if (haveAllPlayersDeclared()) {
pickDeclaredCards();
if (areBoostersEmpty()) {
if (currentRound < rounds) {
openNextBoosters();
if (currentRound == 0) {
startDraft();
startTimer();
makeBotsDeclare();
} else if (haveAllPlayersDeclared()) {
pickDeclaredCards();
if (areBoostersEmpty()) {
if (currentRound < rounds) {
openNextBoosters();
} else {
// Draft finished, shutdown timer scheduler
scheduler.shutdown();
return;
}
} else {
// Draft finished, shutdown timer scheduler
scheduler.shutdown();
return;
passBoosters();
}
startTimer();
makeBotsDeclare();
makeAfkersDeclare();
} else {
passBoosters();
}
startTimer();
makeBotsDeclare();
makeAfkersDeclare();
} else {
if (choiceTimePassed()) {
forcePlayerDeclares();
advanceDraft();
if (choiceTimePassed()) {
forcePlayerDeclares();
advanceDraft();
}
}
} finally {
writeLock.unlock();
}
}
@@ -277,11 +302,16 @@ public class TableDraftClassic implements TableDraft{
@Override
public int getSecondsRemainingForPick() throws IllegalStateException {
if (draftTimer == null || timerStartTime == 0) {
throw new IllegalStateException("No timer set.");
readLock.lock();
try {
if (draftTimer == null || timerStartTime == 0) {
throw new IllegalStateException("No timer set.");
}
int elapsedTime = (int) ((System.currentTimeMillis() - timerStartTime) / 1000); // Convert to seconds
return timerDuration - elapsedTime;
} finally {
readLock.unlock();
}
int elapsedTime = (int) ((System.currentTimeMillis() - timerStartTime) / 1000); // Convert to seconds
return timerDuration - elapsedTime;
}
private void forcePlayerDeclares() {
@@ -314,42 +344,62 @@ public class TableDraftClassic implements TableDraft{
@Override
public void chooseCard(DraftPlayer who, String what) {
// Is player at this table
if (!players.contains(who)) {
return;
}
writeLock.lock();
try {
// Is player at this table
if (!players.contains(who)) {
return;
}
// Is chosen card in his booster
if (!assignedBoosters.get(who).getCardsInPack().contains(what)) {
return;
}
// Is chosen card in his booster
if (!assignedBoosters.get(who).getCardsInPack().contains(what)) {
return;
}
chosenCards.put(who, what);
missedPicksInARow.put(who, 0);
chosenCards.put(who, what);
missedPicksInARow.put(who, 0);
//Check if all players chose
if (!(who instanceof DraftBot)) {
advanceDraft();
//Check if all players chose
if (!(who instanceof DraftBot)) {
advanceDraft();
}
} finally {
writeLock.unlock();
}
}
@Override
public boolean isFinished() {
return areBoostersEmpty() && currentRound == rounds;
readLock.lock();
try {
return areBoostersEmpty() && currentRound == rounds;
} finally {
readLock.unlock();
}
}
@Override
public DraftPlayer getPlayer(String name) {
return players.stream().filter(draftPlayer -> draftPlayer.getName().equals(name)).findFirst().orElse(null);
readLock.lock();
try {
return players.stream().filter(draftPlayer -> draftPlayer.getName().equals(name)).findFirst().orElse(null);
} finally {
readLock.unlock();
}
}
@Override
public DraftPlayer registerPlayer(String name) {
// Only before draft starts
if (currentRound != 0 || name == null) {
return null;
writeLock.lock();
try {
// Only before draft starts
if (currentRound != 0 || name == null) {
return null;
}
return registerPlayer(name, false);
} finally {
writeLock.unlock();
}
return registerPlayer(name, false);
}
private DraftPlayer registerPlayer(String name, boolean bot) {
@@ -369,37 +419,131 @@ public class TableDraftClassic implements TableDraft{
@Override
public boolean removePlayer(String name) {
// Only before draft starts
if (currentRound != 0) {
return false;
}
DraftPlayer toRemove = null;
for (DraftPlayer player : players) {
if (player.getName().equals(name)) {
toRemove = player;
writeLock.lock();
try {
// Only before draft starts
if (currentRound != 0) {
return false;
}
DraftPlayer toRemove = null;
for (DraftPlayer player : players) {
if (player.getName().equals(name)) {
toRemove = player;
}
}
if (players.remove(toRemove)) {
missedPicksInARow.remove(toRemove);
return true;
}
return false;
} finally {
writeLock.unlock();
}
if (players.remove(toRemove)) {
missedPicksInARow.remove(toRemove);
return true;
}
return false;
}
@Override
public CardCollection getPickedCards(DraftPlayer draftPlayer) {
if (draftPlayer instanceof DraftBot) {
return botCollections.get(((DraftBot) draftPlayer));
}
readLock.lock();
try {
if (draftPlayer instanceof DraftBot) {
return botCollections.get(((DraftBot) draftPlayer));
}
return collectionsManager.getPlayerCollection(draftPlayer.getName(), collectionType.getCode());
return collectionsManager.getPlayerCollection(draftPlayer.getName(), collectionType.getCode());
} finally {
readLock.unlock();
}
}
@Override
public TableStatus getTableStatus() {
List<PlayerStatus> statuses = new ArrayList<>();
players.forEach(draftPlayer -> statuses.add(new PlayerStatus(draftPlayer.getName(), chosenCards.containsKey(draftPlayer))));
return new TableStatus(statuses, currentRound % 2 == 1, currentRound, rounds); // Alternate pick order with each booster
public Document getDocument(DraftPlayer draftPlayer, LotroCardBlueprintLibrary cardLibrary, LotroFormatLibrary formatLibrary) throws ParserConfigurationException {
readLock.lock();
try {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document doc = documentBuilder.newDocument();
Element rootElement = doc.createElement("draftStatus");
doc.appendChild(rootElement);
appendPlayersInfo(doc, rootElement);
appendPickedCards(doc, rootElement, getPickedCards(draftPlayer), cardLibrary, formatLibrary);
appendAvailablePicks(doc, rootElement, getCardsToPickFrom(draftPlayer), getChosenCard(draftPlayer));
try {
appendTimeRemaining(doc, rootElement, getSecondsRemainingForPick());
} catch (IllegalStateException ignore) {
// No timer
}
appendRoundsInfo(doc, rootElement);
return doc;
} finally {
readLock.unlock();
}
}
private void appendPlayersInfo(Document doc, Element rootElement) {
players.forEach(draftPlayer -> {
Element playerElement = doc.createElement("player");
playerElement.setAttribute("name", draftPlayer.getName());
playerElement.setAttribute("hasChosen", String.valueOf(chosenCards.containsKey(draftPlayer)));
rootElement.appendChild(playerElement);
});
Element pickOrder = doc.createElement("pickOrderAscending");
pickOrder.setAttribute("value", String.valueOf(currentRound % 2 == 1)); // Alternate pick order with each booster
rootElement.appendChild(pickOrder);
}
private void appendTimeRemaining(Document doc, Element rootElement, int timeRemaining) {
Element time = doc.createElement("timeRemaining");
time.setAttribute("value", "" + timeRemaining);
rootElement.appendChild(time);
}
private void appendRoundsInfo(Document doc, Element rootElement) {
Element time = doc.createElement("roundsInfo");
time.setAttribute("currentRound", "" + currentRound);
time.setAttribute("roundsTotal", "" + rounds);
rootElement.appendChild(time);
}
private void appendPickedCards(Document doc, Element rootElement, CardCollection toAppend, LotroCardBlueprintLibrary cardLibrary, LotroFormatLibrary formatLibrary) {
if (toAppend == null) {
return;
}
SortAndFilterCards sortAndFilterCards = new SortAndFilterCards();
List<CardCollection.Item> pickedCards = sortAndFilterCards.process(
"sort:cardType,culture,name",
toAppend.getAll(),
cardLibrary,
formatLibrary
);
for (CardCollection.Item item : pickedCards) {
Element pickedCard = doc.createElement("pickedCard");
pickedCard.setAttribute("blueprintId", item.getBlueprintId());
pickedCard.setAttribute("count", String.valueOf(item.getCount()));
rootElement.appendChild(pickedCard);
}
}
private void appendAvailablePicks(Document doc, Element rootElement, List<String> availablePicks, String chosenCard) {
if (availablePicks == null || availablePicks.isEmpty()) {
return;
}
for (String availableChoice : availablePicks) {
Element availablePick = doc.createElement("availablePick");
availablePick.setAttribute("id", availableChoice);
availablePick.setAttribute("blueprintId", availableChoice);
if (chosenCard != null && chosenCard.equals(availableChoice)) {
availablePick.setAttribute("chosen", "true");
}
rootElement.appendChild(availablePick);
}
}
}