forked from External/mage
Improved server's reconnection and drafts stability:
* draft: fixed miss or empty draft panels on reconnect; * draft: fixed tourney freezes for richman drafts on disconnects; * draft: fixed tourney freezes on rare use cases with bad connection;
This commit is contained in:
parent
5e27be4dfa
commit
30d44ce869
17 changed files with 201 additions and 133 deletions
|
|
@ -34,6 +34,7 @@ public class BoosterDraft extends DraftImpl {
|
|||
}
|
||||
boosterNum++;
|
||||
}
|
||||
this.boosterSendingEnd();
|
||||
this.fireEndDraftEvent();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ public interface Draft extends MageItem, Serializable {
|
|||
int getCardNum();
|
||||
boolean addPick(UUID playerId, UUID cardId, Set<UUID> hiddenCards);
|
||||
void setBoosterLoaded(UUID playerID);
|
||||
void boosterSendingStart();
|
||||
void start();
|
||||
boolean isStarted();
|
||||
void setStarted();
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import java.util.concurrent.ScheduledFuture;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||
*/
|
||||
public abstract class DraftImpl implements Draft {
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ public abstract class DraftImpl implements Draft {
|
|||
protected int cardNum = 1; // starts with card number 1, increases by +1 after each picking
|
||||
protected TimingOption timing;
|
||||
protected int boosterLoadingCounter; // number of times the boosters have been sent to players until all are confirmed to have received them
|
||||
protected final int BOOSTER_LOADING_INTERVAL = 2; // interval in seconds
|
||||
protected final int BOOSTER_LOADING_INTERVAL_SECS = 2; // interval in seconds
|
||||
|
||||
protected boolean abort = false;
|
||||
protected boolean started = false;
|
||||
|
|
@ -44,8 +44,8 @@ public abstract class DraftImpl implements Draft {
|
|||
protected transient TableEventSource tableEventSource = new TableEventSource();
|
||||
protected transient PlayerQueryEventSource playerQueryEventSource = new PlayerQueryEventSource();
|
||||
|
||||
protected ScheduledFuture<?> boosterLoadingHandle;
|
||||
protected ScheduledExecutorService boosterLoadingExecutor = null;
|
||||
protected ScheduledFuture<?> boosterSendingWorker;
|
||||
protected ScheduledExecutorService boosterSendingExecutor = null;
|
||||
|
||||
public DraftImpl(DraftOptions options, List<ExpansionSet> sets) {
|
||||
this.id = UUID.randomUUID();
|
||||
|
|
@ -83,7 +83,6 @@ public abstract class DraftImpl implements Draft {
|
|||
if (newPlayer != null) {
|
||||
DraftPlayer newDraftPlayer = new DraftPlayer(newPlayer);
|
||||
DraftPlayer oldDraftPlayer = players.get(oldPlayer.getId());
|
||||
newDraftPlayer.setBooster(oldDraftPlayer.getBooster());
|
||||
Map<UUID, DraftPlayer> newPlayers = new LinkedHashMap<>();
|
||||
synchronized (players) {
|
||||
for (Map.Entry<UUID, DraftPlayer> entry : players.entrySet()) {
|
||||
|
|
@ -110,12 +109,14 @@ public abstract class DraftImpl implements Draft {
|
|||
|
||||
table.setCurrent(currentId);
|
||||
}
|
||||
|
||||
// boosters send to all players by timeout, so don't need to send it manually here
|
||||
newDraftPlayer.setBoosterAndLoad(oldDraftPlayer.getBooster());
|
||||
if (oldDraftPlayer.isPicking()) {
|
||||
newDraftPlayer.setPicking();
|
||||
if (!newDraftPlayer.getBooster().isEmpty()) {
|
||||
newDraftPlayer.getPlayer().pickCard(newDraftPlayer.getBooster(), newDraftPlayer.getDeck(), this);
|
||||
}
|
||||
newDraftPlayer.setPickingAndSending();
|
||||
}
|
||||
boosterSendingStart(); // if it's AI then make pick from it
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -124,7 +125,7 @@ public abstract class DraftImpl implements Draft {
|
|||
@Override
|
||||
public Collection<DraftPlayer> getPlayers() {
|
||||
synchronized (players) {
|
||||
return players.values();
|
||||
return new ArrayList<>(players.values());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +189,7 @@ public abstract class DraftImpl implements Draft {
|
|||
List<Card> currentBooster = current.booster;
|
||||
while (true) {
|
||||
List<Card> nextBooster = next.booster;
|
||||
next.setBooster(currentBooster);
|
||||
next.setBoosterAndLoad(currentBooster);
|
||||
if (Objects.equals(nextId, startId)) {
|
||||
break;
|
||||
}
|
||||
|
|
@ -209,7 +210,7 @@ public abstract class DraftImpl implements Draft {
|
|||
List<Card> currentBooster = current.booster;
|
||||
while (true) {
|
||||
List<Card> prevBooster = prev.booster;
|
||||
prev.setBooster(currentBooster);
|
||||
prev.setBoosterAndLoad(currentBooster);
|
||||
if (Objects.equals(prevId, startId)) {
|
||||
break;
|
||||
}
|
||||
|
|
@ -221,70 +222,71 @@ public abstract class DraftImpl implements Draft {
|
|||
}
|
||||
|
||||
protected void openBooster() {
|
||||
if (boosterNum <= numberBoosters) {
|
||||
for (DraftPlayer player : players.values()) {
|
||||
if (draftCube != null) {
|
||||
player.setBooster(draftCube.createBooster());
|
||||
} else {
|
||||
player.setBooster(sets.get(boosterNum - 1).createBooster());
|
||||
synchronized (players) {
|
||||
if (boosterNum <= numberBoosters) {
|
||||
for (DraftPlayer player : players.values()) {
|
||||
if (draftCube != null) {
|
||||
player.setBoosterAndLoad(draftCube.createBooster());
|
||||
} else {
|
||||
player.setBoosterAndLoad(sets.get(boosterNum - 1).createBooster());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean pickCards() {
|
||||
for (DraftPlayer player : players.values()) {
|
||||
if (player.getBooster().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
player.setPicking();
|
||||
player.setBoosterNotLoaded();
|
||||
}
|
||||
setupBoosterLoadingHandle();
|
||||
synchronized (this) {
|
||||
while (!donePicking()) {
|
||||
try {
|
||||
this.wait(10000); // checked every 10s to make sure the draft moves on
|
||||
} catch (InterruptedException ex) {
|
||||
synchronized (players) {
|
||||
for (DraftPlayer player : players.values()) {
|
||||
if (player.getBooster().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
player.setPickingAndSending();
|
||||
}
|
||||
}
|
||||
|
||||
while (!donePicking()) {
|
||||
boosterSendingStart();
|
||||
picksWait();
|
||||
}
|
||||
|
||||
cardNum++;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void setupBoosterLoadingHandle() {
|
||||
cancelBoosterLoadingHandle();
|
||||
boosterLoadingCounter = 0;
|
||||
|
||||
if (this.boosterLoadingExecutor == null) {
|
||||
this.boosterLoadingExecutor = Executors.newSingleThreadScheduledExecutor(
|
||||
new XmageThreadFactory(ThreadUtils.THREAD_PREFIX_TOURNEY_BOOSTERS_SEND)
|
||||
public void boosterSendingStart() {
|
||||
if (this.boosterSendingExecutor == null) {
|
||||
this.boosterSendingExecutor = Executors.newSingleThreadScheduledExecutor(
|
||||
new XmageThreadFactory(ThreadUtils.THREAD_PREFIX_TOURNEY_BOOSTERS_SEND + " " + this.getId())
|
||||
);
|
||||
}
|
||||
boosterLoadingCounter = 0;
|
||||
|
||||
boosterLoadingHandle = boosterLoadingExecutor.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
if (loadBoosters()) {
|
||||
cancelBoosterLoadingHandle();
|
||||
} else {
|
||||
boosterLoadingCounter++;
|
||||
if (boosterSendingWorker == null) {
|
||||
boosterSendingWorker = boosterSendingExecutor.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
if (isAbort() || sendBoostersToPlayers()) {
|
||||
boosterSendingEnd();
|
||||
} else {
|
||||
boosterLoadingCounter++;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.fatal("Fatal boosterLoadingHandle error in draft " + id + " pack " + boosterNum + " pick " + cardNum, ex);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.fatal("Fatal boosterLoadingHandle error in draft " + id + " pack " + boosterNum + " pick " + cardNum, ex);
|
||||
}
|
||||
}, 0, BOOSTER_LOADING_INTERVAL, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
protected void cancelBoosterLoadingHandle() {
|
||||
if (boosterLoadingHandle != null) {
|
||||
boosterLoadingHandle.cancel(true);
|
||||
}, 0, BOOSTER_LOADING_INTERVAL_SECS, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean loadBoosters() {
|
||||
protected void boosterSendingEnd() {
|
||||
if (boosterSendingWorker != null) {
|
||||
boosterSendingWorker.cancel(true);
|
||||
boosterSendingWorker = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean sendBoostersToPlayers() {
|
||||
boolean allBoostersLoaded = true;
|
||||
for (DraftPlayer player : players.values()) {
|
||||
for (DraftPlayer player : getPlayers()) {
|
||||
if (player.isPicking() && !player.isBoosterLoaded()) {
|
||||
allBoostersLoaded = false;
|
||||
player.getPlayer().pickCard(player.getBooster(), player.getDeck(), this);
|
||||
|
|
@ -297,16 +299,20 @@ public abstract class DraftImpl implements Draft {
|
|||
if (isAbort()) {
|
||||
return true;
|
||||
}
|
||||
return players.values()
|
||||
.stream()
|
||||
.noneMatch(DraftPlayer::isPicking);
|
||||
|
||||
synchronized (players) {
|
||||
return players.values()
|
||||
.stream()
|
||||
.noneMatch(DraftPlayer::isPicking);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allJoined() {
|
||||
return players.values().stream()
|
||||
.allMatch(DraftPlayer::isJoined);
|
||||
synchronized (players) {
|
||||
return players.values().stream()
|
||||
.allMatch(DraftPlayer::isJoined);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -342,11 +348,32 @@ public abstract class DraftImpl implements Draft {
|
|||
// if the pack is re-sent to a player because they haven't been able to successfully load it, the pick time is reduced appropriately because of the elapsed time
|
||||
// the time is always at least 1 second unless it's set to 0, i.e. unlimited time
|
||||
if (time > 0) {
|
||||
time = Math.max(1, time - boosterLoadingCounter * BOOSTER_LOADING_INTERVAL);
|
||||
time = Math.max(1, time - boosterLoadingCounter * BOOSTER_LOADING_INTERVAL_SECS);
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
public void picksCheckDone() {
|
||||
// notify main thread about changes, can be called from user's thread
|
||||
synchronized (this) {
|
||||
this.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
protected void picksWait() {
|
||||
// main thread waiting any picks or changes
|
||||
synchronized (this) {
|
||||
try {
|
||||
this.wait(10000); // checked every 10s to make sure the draft moves on
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
if (donePicking()) {
|
||||
boosterSendingEnd();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addPick(UUID playerId, UUID cardId, Set<UUID> hiddenCards) {
|
||||
DraftPlayer player = players.get(playerId);
|
||||
|
|
@ -354,13 +381,10 @@ public abstract class DraftImpl implements Draft {
|
|||
for (Card card : player.booster) {
|
||||
if (card.getId().equals(cardId)) {
|
||||
player.addPick(card, hiddenCards);
|
||||
player.booster.remove(card);
|
||||
break;
|
||||
}
|
||||
}
|
||||
synchronized (this) {
|
||||
this.notifyAll();
|
||||
}
|
||||
picksCheckDone();
|
||||
}
|
||||
return !player.isPicking();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ public class DraftPlayer {
|
|||
protected Deck deck;
|
||||
protected List<Card> booster;
|
||||
protected boolean picking;
|
||||
protected boolean boosterLoaded;
|
||||
protected boolean boosterLoaded; // client confirmed that it got a booster data (for computer must be always false)
|
||||
protected boolean joined = false;
|
||||
protected Set<UUID> hiddenCards;
|
||||
|
||||
|
|
@ -64,14 +64,13 @@ public class DraftPlayer {
|
|||
if (hiddenCards != null) {
|
||||
this.hiddenCards = hiddenCards;
|
||||
}
|
||||
synchronized (booster) {
|
||||
booster.remove(card);
|
||||
}
|
||||
booster.remove(card);
|
||||
picking = false;
|
||||
}
|
||||
|
||||
public void setBooster(List<Card> booster) {
|
||||
public void setBoosterAndLoad(List<Card> booster) {
|
||||
this.booster = booster;
|
||||
this.boosterLoaded = false; // human will receive new pick, computer with choose new pick
|
||||
}
|
||||
|
||||
public List<Card> getBooster() {
|
||||
|
|
@ -83,8 +82,9 @@ public class DraftPlayer {
|
|||
}
|
||||
}
|
||||
|
||||
public void setPicking() {
|
||||
picking = true;
|
||||
public void setPickingAndSending() {
|
||||
this.picking = true;
|
||||
this.boosterLoaded = false;
|
||||
}
|
||||
|
||||
public boolean isPicking() {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public class RandomBoosterDraft extends BoosterDraft {
|
|||
protected void openBooster() {
|
||||
if (boosterNum <= numberBoosters) {
|
||||
for (DraftPlayer player: players.values()) {
|
||||
player.setBooster(getNextBooster().create15CardBooster());
|
||||
player.setBoosterAndLoad(getNextBooster().create15CardBooster());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public class ReshuffledBoosterDraft extends BoosterDraft {
|
|||
protected void openBooster() {
|
||||
if (boosterNum <= numberBoosters) {
|
||||
for (DraftPlayer player: players.values()) {
|
||||
player.setBooster(reshuffledSet.createBooster());
|
||||
player.setBoosterAndLoad(reshuffledSet.createBooster());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
|
||||
package mage.game.draft;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.ExpansionSet;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author spjspj
|
||||
*/
|
||||
public class RichManBoosterDraft extends DraftImpl {
|
||||
|
|
@ -38,6 +37,7 @@ public class RichManBoosterDraft extends DraftImpl {
|
|||
}
|
||||
boosterNum++;
|
||||
}
|
||||
this.boosterSendingEnd();
|
||||
this.fireEndDraftEvent();
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ public class RichManBoosterDraft extends DraftImpl {
|
|||
DraftPlayer next = players.get(nextId);
|
||||
while (true) {
|
||||
List<Card> nextBooster = sets.get((cardNum - 1) % sets.size()).createBooster();
|
||||
next.setBooster(nextBooster);
|
||||
next.setBoosterAndLoad(nextBooster);
|
||||
if (Objects.equals(nextId, startId)) {
|
||||
break;
|
||||
}
|
||||
|
|
@ -62,22 +62,21 @@ public class RichManBoosterDraft extends DraftImpl {
|
|||
|
||||
@Override
|
||||
protected boolean pickCards() {
|
||||
for (DraftPlayer player : players.values()) {
|
||||
if (cardNum > 36) {
|
||||
return false;
|
||||
}
|
||||
player.setPicking();
|
||||
player.getPlayer().pickCard(player.getBooster(), player.getDeck(), this);
|
||||
}
|
||||
cardNum++;
|
||||
synchronized (this) {
|
||||
while (!donePicking()) {
|
||||
try {
|
||||
this.wait();
|
||||
} catch (InterruptedException ex) {
|
||||
synchronized (players) {
|
||||
for (DraftPlayer player : players.values()) {
|
||||
if (cardNum > 36) {
|
||||
return false;
|
||||
}
|
||||
player.setPickingAndSending();
|
||||
}
|
||||
}
|
||||
|
||||
while (!donePicking()) {
|
||||
boosterSendingStart();
|
||||
picksWait();
|
||||
}
|
||||
|
||||
cardNum++;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -85,6 +84,7 @@ public class RichManBoosterDraft extends DraftImpl {
|
|||
public void firePickCardEvent(UUID playerId) {
|
||||
DraftPlayer player = players.get(playerId);
|
||||
int cardNum = Math.min(36, this.cardNum);
|
||||
|
||||
// richman uses custom times
|
||||
int time = (int) Math.ceil(customProfiTimes[cardNum - 1] * timing.getCustomTimeoutFactor());
|
||||
playerQueryEventSource.pickCard(playerId, "Pick card", player.getBooster(), time);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
package mage.game.draft;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import mage.cards.Card;
|
||||
import mage.cards.ExpansionSet;
|
||||
import mage.game.draft.DraftCube.CardIdentity;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author spjspj
|
||||
*/
|
||||
public class RichManCubeBoosterDraft extends DraftImpl {
|
||||
|
|
@ -36,6 +35,7 @@ public class RichManCubeBoosterDraft extends DraftImpl {
|
|||
}
|
||||
boosterNum++;
|
||||
}
|
||||
this.boosterSendingEnd();
|
||||
this.fireEndDraftEvent();
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ public class RichManCubeBoosterDraft extends DraftImpl {
|
|||
}
|
||||
|
||||
List<Card> nextBooster = draftCube.createBooster();
|
||||
next.setBooster(nextBooster);
|
||||
next.setBoosterAndLoad(nextBooster);
|
||||
if (Objects.equals(nextId, startId)) {
|
||||
break;
|
||||
}
|
||||
|
|
@ -77,22 +77,21 @@ public class RichManCubeBoosterDraft extends DraftImpl {
|
|||
|
||||
@Override
|
||||
protected boolean pickCards() {
|
||||
for (DraftPlayer player : players.values()) {
|
||||
if (cardNum > 36) {
|
||||
return false;
|
||||
}
|
||||
player.setPicking();
|
||||
player.getPlayer().pickCard(player.getBooster(), player.getDeck(), this);
|
||||
}
|
||||
cardNum++;
|
||||
synchronized (this) {
|
||||
while (!donePicking()) {
|
||||
try {
|
||||
this.wait();
|
||||
} catch (InterruptedException ex) {
|
||||
synchronized (players) {
|
||||
for (DraftPlayer player : players.values()) {
|
||||
if (cardNum > 36) {
|
||||
return false;
|
||||
}
|
||||
player.setPickingAndSending();
|
||||
}
|
||||
}
|
||||
|
||||
while (!donePicking()) {
|
||||
boosterSendingStart();
|
||||
picksWait();
|
||||
}
|
||||
|
||||
cardNum++;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -336,7 +336,7 @@ public abstract class MatchImpl implements Match {
|
|||
while (!isDoneSideboarding()) {
|
||||
try {
|
||||
this.wait();
|
||||
} catch (InterruptedException ex) {
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue