Skip to content

Commit

Permalink
Fix game history and add Options class specifying what to include. (#…
Browse files Browse the repository at this point in the history
…10472)

* Fix game history and add Options class specifying what to include.
  • Loading branch information
asvitkine authored May 20, 2022
1 parent a0e823f commit 41aa648
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.Optional;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.triplea.game.server.HeadlessGameServer;
Expand Down Expand Up @@ -191,7 +192,7 @@ public static void saveGame(
try (OutputStream os = Files.newOutputStream(tempFile);
OutputStream bufferedOutStream = new BufferedOutputStream(os);
OutputStream zippedOutStream = new GZIPOutputStream(bufferedOutStream)) {
saveGameUncompressed(zippedOutStream, gameData, true, engineVersion);
saveGameUncompressed(zippedOutStream, gameData, Options.withEverything(), engineVersion);
}

// now write to sink (ensure sink is closed per method contract)
Expand All @@ -204,30 +205,52 @@ public static void saveGame(
}
}

@Builder
public static class Options {
@Builder.Default boolean withDelegates = false;
@Builder.Default boolean withHistory = false;
@Builder.Default boolean withAttachmentXmlData = false;

public static Options withEverything() {
return builder().withDelegates(true).withHistory(true).withAttachmentXmlData(true).build();
}

public static Options forBattleCalculator() {
return builder().build();
}
}

public static void saveGameUncompressed(
final OutputStream sink,
final GameData data,
final boolean saveDelegateInfoHistoryAndAttachmentsOrder,
final Options options,
final Version engineVersion)
throws IOException {
// write to temporary file first in case of error
try (ObjectOutputStream outStream = new ObjectOutputStream(sink)) {
outStream.writeObject(engineVersion);
data.acquireReadLock();
try {
if (saveDelegateInfoHistoryAndAttachmentsOrder) {
outStream.writeObject(data);
writeDelegates(data, outStream);
} else {
// TODO: Attachment order data is only used for XML export and takes up lots of memory.
// Could we remove it and just get the info again from the XML when exporting?
final var attachments = data.getAttachmentOrderAndValues();
final var history = data.getHistory();
data.setAttachmentOrderAndValues(null);
final var history = data.getHistory();
if (!options.withHistory) {
data.resetHistory();
outStream.writeObject(data);
}
// TODO: Attachment order data is only used for XML export and takes up lots of memory.
// Could we remove it and just get the info again from the XML when exporting?
final var attachments = data.getAttachmentOrderAndValues();
if (!options.withAttachmentXmlData) {
data.setAttachmentOrderAndValues(null);
}
outStream.writeObject(data);
if (!options.withAttachmentXmlData) {
data.setAttachmentOrderAndValues(attachments);
}
if (!options.withHistory) {
data.setHistory(history);
}
if (options.withDelegates) {
writeDelegates(data, outStream);
} else {
outStream.writeObject(DELEGATE_LIST_END);
}
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,43 @@

import games.strategy.engine.data.GameData;
import games.strategy.engine.data.GameObjectOutputStream;
import games.strategy.engine.history.History;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Optional;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.triplea.injection.Injections;
import org.triplea.io.IoUtils;
import org.triplea.util.Version;

/** A collection of useful methods for working with instances of {@link GameData}. */
@Slf4j
@UtilityClass
public final class GameDataUtils {
private GameDataUtils() {}

/**
* Create a deep copy of GameData without history as it can get large. <strong>You should have the
* game data's write lock before calling this method</strong>
*/
public static Optional<GameData> cloneGameDataWithoutHistory(
final GameData data, final boolean copyDelegates, final Version engineVersion) {
final History temp = data.getHistory();
data.resetHistory();
final Optional<GameData> dataCopy = cloneGameData(data, copyDelegates, engineVersion);
data.setHistory(temp);
return dataCopy;
}

public static Optional<GameData> cloneGameData(final GameData data) {
return cloneGameData(data, false, Injections.getInstance().getEngineVersion());
public static Optional<GameData> cloneGameData(GameData data, GameDataManager.Options options) {
return cloneGameData(data, options, Injections.getInstance().getEngineVersion());
}

/**
* Create a deep copy of GameData. <strong>You should have the game data's read or write lock
* before calling this method</strong>
*/
public static Optional<GameData> cloneGameData(
final GameData data, final boolean copyDelegates, final Version engineVersion) {
final byte[] bytes = gameDataToBytes(data, copyDelegates, engineVersion).orElse(null);
GameData data, GameDataManager.Options options, Version engineVersion) {
final byte[] bytes = gameDataToBytes(data, options, engineVersion).orElse(null);
if (bytes != null) {
return createGameDataFromBytes(bytes, engineVersion);
}
return Optional.empty();
}

public static Optional<byte[]> gameDataToBytes(
GameData data, boolean copyDelegates, Version engineVersion) {
GameData data, GameDataManager.Options options, Version engineVersion) {
try {
return Optional.of(
IoUtils.writeToMemory(
os -> GameDataManager.saveGameUncompressed(os, data, copyDelegates, engineVersion)));
os -> GameDataManager.saveGameUncompressed(os, data, options, engineVersion)));
} catch (final IOException e) {
log.error("Failed to clone game data", e);
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import games.strategy.engine.data.Territory;
import games.strategy.engine.data.Unit;
import games.strategy.engine.delegate.IDelegateBridge;
import games.strategy.engine.framework.GameDataManager;
import games.strategy.engine.framework.GameDataUtils;
import games.strategy.triplea.Properties;
import games.strategy.triplea.ai.AbstractBuiltInAi;
Expand Down Expand Up @@ -180,8 +181,10 @@ protected void purchase(
try {
data.acquireWriteLock();
dataCopy =
GameDataUtils.cloneGameDataWithoutHistory(
data, true, Injections.getInstance().getEngineVersion())
GameDataUtils.cloneGameData(
data,
GameDataManager.Options.builder().withDelegates(true).build(),
Injections.getInstance().getEngineVersion())
.orElse(null);
if (dataCopy == null) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import games.strategy.engine.data.TerritoryEffect;
import games.strategy.engine.data.Unit;
import games.strategy.engine.data.changefactory.ChangeFactory;
import games.strategy.engine.framework.GameDataManager;
import games.strategy.engine.framework.GameDataUtils;
import games.strategy.triplea.delegate.GameDelegateBridge;
import games.strategy.triplea.delegate.battle.BattleResults;
Expand Down Expand Up @@ -38,7 +39,10 @@ private BattleCalculator(@Nullable GameData data) {
}

BattleCalculator(GameData data, Version engineVersion) {
this(GameDataUtils.cloneGameData(data, false, engineVersion).orElse(null));
this(
GameDataUtils.cloneGameData(
data, GameDataManager.Options.forBattleCalculator(), engineVersion)
.orElse(null));
}

BattleCalculator(byte[] data, Version engineVersion) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import games.strategy.engine.data.Territory;
import games.strategy.engine.data.TerritoryEffect;
import games.strategy.engine.data.Unit;
import games.strategy.engine.framework.GameDataManager;
import games.strategy.engine.framework.GameDataUtils;
import java.util.Collection;
import java.util.List;
Expand Down Expand Up @@ -127,7 +128,10 @@ private void createWorkers(final GameData data) {
// Serialize the data, then release lock on it so game can continue (ie: we don't want to
// lock on it while we copy it 16 times).
data.acquireWriteLock();
serializedData = GameDataUtils.gameDataToBytes(data, false, engineVersion).orElse(null);
serializedData =
GameDataUtils.gameDataToBytes(
data, GameDataManager.Options.forBattleCalculator(), engineVersion)
.orElse(null);
if (serializedData == null) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1763,7 +1763,8 @@ private void showHistory() {
try {
// we want to use a clone of the data, so we can make changes to it as we walk up and down the
// history
clonedGameData = GameDataUtils.cloneGameData(data).orElse(null);
final var cloneOptions = GameDataManager.Options.builder().withHistory(true).build();
clonedGameData = GameDataUtils.cloneGameData(data, cloneOptions).orElse(null);
if (clonedGameData == null) {
return;
}
Expand Down Expand Up @@ -1878,7 +1879,9 @@ public void actionPerformed(final ActionEvent ae) {
try (OutputStream fileOutputStream = Files.newOutputStream(f.get())) {
final GameData datacopy =
GameDataUtils.cloneGameData(
data, true, Injections.getInstance().getEngineVersion())
data,
GameDataManager.Options.withEverything(),
Injections.getInstance().getEngineVersion())
.orElse(null);
if (datacopy != null) {
datacopy.getHistory().gotoNode(historyPanel.getCurrentPopupNode());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import games.strategy.engine.data.Resource;
import games.strategy.engine.data.UnitType;
import games.strategy.engine.data.export.GameDataExporter;
import games.strategy.engine.framework.GameDataManager;
import games.strategy.engine.framework.GameDataUtils;
import games.strategy.engine.framework.system.SystemProperties;
import games.strategy.engine.history.HistoryNode;
Expand Down Expand Up @@ -167,7 +168,8 @@ private void createAndSaveStats(final boolean showPhaseStats) {
try (PrintWriter writer =
new PrintWriter(chooser.getSelectedFile(), StandardCharsets.UTF_8.toString())) {
gameData.acquireReadLock();
final GameData clone = GameDataUtils.cloneGameData(gameData).orElse(null);
final var cloneOptions = GameDataManager.Options.builder().withHistory(true).build();
final GameData clone = GameDataUtils.cloneGameData(gameData, cloneOptions).orElse(null);
if (clone == null) {
return;
}
Expand Down Expand Up @@ -394,7 +396,8 @@ private void exportSetupCharts() {
final GameData clonedGameData;
gameData.acquireReadLock();
try {
clonedGameData = GameDataUtils.cloneGameData(gameData).orElse(null);
final var cloneOptions = GameDataManager.Options.builder().withHistory(true).build();
clonedGameData = GameDataUtils.cloneGameData(gameData, cloneOptions).orElse(null);
if (clonedGameData == null) {
return;
}
Expand Down

0 comments on commit 41aa648

Please sign in to comment.